//#####################################################################
// Copyright 2020, Craig Schroeder.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################
#ifndef __QT_RENDER_UTILS__
#define __QT_RENDER_UTILS__

#include <Core/Log/LOG.h>
#include <Core/Matrices/MATRIX.h>
#include <Core/Meshes/MESHES_FORWARD.h>
#include <Core/Vectors/VECTOR.h>
#include "QT_PICK.h"
#include "QT_SHADER_FLAGS.h"
#include <map>
//#include <QMatrix4x4>
//#include <QOpenGLFunctions>

class QOpenGLShaderProgram;
class QMatrix4x4;
class QOpenGLDebugLogger;
class QOpenGLFunctions;

#define CHECK_ERRORS(o) (o)->Dump_GL_Errors(__FILE__,__LINE__)

namespace PhysBAM{

class QT_SHADER_HOLDER;
class QT_CAMERA;
class QT_PICK;
class QT_SLICE;
class QT_WINDOW;
class SHADER;
template<class T> class QT_RENDER_OPTIONS;
template<class TV> class GRID;

class QT_RENDER_UTILS
{
    typedef VECTOR<float,3> COL;
    typedef VECTOR<float,3> FV;
    typedef VECTOR<int,3> IV;
    typedef VECTOR<float,4> FV4;
    typedef MATRIX<float,4> MAT;

    QOpenGLDebugLogger *logger = nullptr;
    VECTOR<float,3> camera_light_pos[2];
    template<class T>
    struct PUSH_HELPER
    {
        T data;
        T* ptr;
        PUSH_HELPER(T& val):data(val),ptr(&val) {}
        PUSH_HELPER(T&& p):data(p.data),ptr(p.ptr) {p.ptr=0;}
        PUSH_HELPER(const T&) = delete;
        ~PUSH_HELPER() {if(ptr) *ptr=data;}
    };
    std::map<SHADER_FLAGS,SHADER*> shaders;

public:
    QT_WINDOW* window=nullptr;

    typedef VECTOR<float,3> COLOR;
    float point_size=4;
    float highlighted_point_size=5;
    const float line_width=1;
    const float highlighted_line_width=2;
    const float arrowhead_size=.3;
    const float arrowhead_angle=.3;
    const COL highlight_color{1,1,0};
    const FV specular_color={.5f,.5f,.5f};
    const float ambient_brightness=0.3;
    const float shininess=45;

    float depth_offset=0;
    float depth_offset_unit=0.0001f;
    float scale=1;

    const QT_CAMERA& camera;
    FV world_light_pos[2];
    float obj_to_world_scale=1;
    MAT obj_to_world;
    MAT world_to_gl;
    QOpenGLFunctions& gl;
    QT_PICK pick;
    std::ostringstream info_text;

    bool wireframe=false;
    bool select=false;

    const GRID<FV>* slice_grid=nullptr;
    int slice_axis=0;
    int slice_index=0;
    FV4 clip_plane0;
    FV4 clip_plane1;

    struct RU_COLOR
    {
        COLOR color;
        ARRAY_VIEW<const VECTOR<float,3> > pf;
        ARRAY_VIEW<const VECTOR<double,3> > pd;
        bool valid=false;

        RU_COLOR()=default;
        template<class C> RU_COLOR(const C& c){Set(c);}

        void Set(const VECTOR<float,3>& c){valid=true;color=c;}
        void Set(const VECTOR<double,3>& c){valid=true;color=COLOR(c);}
        void Set(ARRAY_VIEW<const VECTOR<float,3> > c){valid=true;pf.Set(c);}
        void Set(ARRAY_VIEW<const VECTOR<double,3> > c){valid=true;pd.Set(c);}
        int Flags() const
        {
            if(!valid) return 0;
            if(pf.m || pd.m) return 2;
            return 1;
        }
    };

    QT_RENDER_UTILS(QT_WINDOW* obj, QOpenGLFunctions& gl, const QT_CAMERA& camera);
    ~QT_RENDER_UTILS();

    // Restores old value when return value goes out of scope.
    template<class T> PUSH_HELPER<T>
    Push(T& x,T new_val){PUSH_HELPER<T> p(x);x=new_val;return p;}

    void Update_Transform();
    void Dump_GL_Errors(const char* file, int line) const;
    void Reset();
    void Set_Clipping_Planes();
    const SHADER& Get_Shader(SHADER_FLAGS flags);

    template<class T>
    struct OPT_POINT
    {
        T point_size=-1; // -1 = use value from ru
    };

    template<class T>
    struct OPT_LINE
    {
        T line_width=-1; // -1 = use value from ru
        int type=0;
    };

    template<class T>
    struct OPT_TRI
    {
        RU_COLOR bgcolor={};
        T line_width=-1; // -1 = use value from ru
        int type=0; // for boxes, type=1 is diamonds
        bool fill=true;
    };

    template<class T>
    struct OPT_SPHERE
    {
        RU_COLOR bgcolor={};
        T line_width=-1; // -1 = use value from ru
        T scale=-1; // -1 = use value from ru
        bool fill=true;
    };

    template<class T>
    struct OPT_ARROW
    {
        T line_width=-1; // -1 = use value from ru
        T scale=-1; // -1 = use value from ru
        bool use_arrowhead=true;
    };

    template<class T>
    struct OPT_REG_POLY
    {
        RU_COLOR bgcolor={};
        T line_width=-1; // -1 = use value from ru
        bool fill=true;
        T scale=-1; // -1 = use value from ru
        int angle=0; // rotate by angle*Pi/4; angle=0..7
    };

    template<class T>
    struct OPT_MATRIX
    {
        T line_width=-1; // -1 = use value from ru
        T scale=-1; // -1 = use value from ru
    };

    template<class TV> void
    Render_Triangle(const TV& A,const TV& B,const TV& C,
        const RU_COLOR& color,OPT_TRI<typename TV::SCALAR> opt={});

    // type=0 -> 3 pts per triangle (GL_TRIANGLES)
    // type=1 -> fan (GL_TRIANGLE_FAN)
    // type=2 -> strip (GL_TRIANGLE_STRIP)
    template<class TV> void
    Render_Triangles(ARRAY_VIEW<const TV> pts,
        const RU_COLOR& color,OPT_TRI<typename TV::SCALAR> opt={});

    template<class TV,int d> void
    Render_Mesh(const MESH_OBJECT<TV,d>& obj,
        const RU_COLOR& color,OPT_TRI<typename TV::SCALAR> opt={});

    template<class TV> void
    Render_Line(const TV& A,const TV& B,
        const RU_COLOR& color,OPT_LINE<typename TV::SCALAR> opt={});

    // type=0 -> 2 pts per line (GL_LINES)
    // type=1 -> connected lines (GL_LINE_STRIP)
    // type=2 -> line loop (GL_LINE_LOOP)
    template<class TV> void
    Render_Lines(ARRAY_VIEW<const TV> pts,
        const RU_COLOR& color,OPT_LINE<typename TV::SCALAR> opt={});

    template<class TV> void
    Render_Point(const TV& pt,const RU_COLOR& color,
        OPT_POINT<typename TV::SCALAR> opt={});

    template<class TV> void
    Render_Points(ARRAY_VIEW<const TV> pts,const RU_COLOR& color,
        OPT_POINT<typename TV::SCALAR> opt={});

    template<class TV> void
    Render_Text(const TV& pt,const std::string& text,const RU_COLOR& color);

    template<class TV> void
    Render_Arrow(const TV& X,const TV& dX,const RU_COLOR& color,
        OPT_ARROW<typename TV::SCALAR> opt={});

    template<class TV> void
    Render_Arrows(ARRAY_VIEW<const TV> X,ARRAY_VIEW<const TV> dX,
        const RU_COLOR& color,OPT_ARROW<typename TV::SCALAR> opt={});

    template<class TV> void
    Render_Sphere(const TV& X,typename TV::SCALAR radius,const RU_COLOR& color,
        OPT_SPHERE<typename TV::SCALAR> opt={});

    template<class TV> void
    Render_Spheres(ARRAY_VIEW<const TV> X,ARRAY_VIEW<const typename TV::SCALAR> radius,
        const RU_COLOR& color,OPT_SPHERE<typename TV::SCALAR> opt={});

    template<class TV> void
    Render_Box(const TV& X,const TV& size,
        const RU_COLOR& color,OPT_TRI<typename TV::SCALAR> opt={});

    template<class TV> void
    Render_Box(const RANGE<TV>& range,const RU_COLOR& color,
        OPT_TRI<typename TV::SCALAR> opt={});

    template<class TV> void
    Render_Boxes(ARRAY_VIEW<const TV> X,const TV& size,const RU_COLOR& color,
        OPT_TRI<typename TV::SCALAR> opt={});

    template<class TV> void
    Render_Diamonds(ARRAY_VIEW<const TV> X,const TV& size,const RU_COLOR& color,
        OPT_TRI<typename TV::SCALAR> opt={});

    template<class TV> void
    Render_Regular_Poly(const TV& X,typename TV::SCALAR radius,int sides,
        const RU_COLOR& color,OPT_REG_POLY<typename TV::SCALAR> opt={});

    template<class TV> void
    Render_Regular_Polys(ARRAY_VIEW<const TV> X,
        ARRAY_VIEW<const typename TV::SCALAR> radius,int sides,
        const RU_COLOR& color,OPT_REG_POLY<typename TV::SCALAR> opt={});

    template<class TV> void
    Render_Matrices(ARRAY_VIEW<const TV> X,
        ARRAY_VIEW<const MATRIX<typename TV::SCALAR,TV::m>> M,
        OPT_MATRIX<typename TV::SCALAR> opt={});
};
}
#endif
