//#####################################################################
// Copyright 2011-2020, Craig Schroeder.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################
#include <Core/Arrays/ARRAYS_ND.h>
#include <Core/Basic_Geometry/TRIANGLE_2D.h>
#include <Core/Geometry_Particles/DEBUG_PARTICLES.h>
#include <Core/Geometry_Particles/GEOMETRY_PARTICLES.h>
#include <Core/Log/LOG.h>
#include <Core/Read_Write/FILE_UTILITIES.h>
#include <Core/Utilities/VIEWER_DIR.h>
#include "../QT_FRAGMENT_SHADER.h"
#include "../QT_RENDER_UTILS.h"
#include "../QT_SHADER.h"
#include "../QT_VERTEX_SHADER.h"
#include "../QT_WINDOW.h"
#include "../QT_WORLD.h"
#include "QT_COMPONENT_OPTIONS.h"
#include "QT_DEBUG_PARTICLES.h"
#include "QT_OBJECT.h"
#include <QAction>
#include <sstream>
#include <stdexcept>
namespace PhysBAM{
#define SET(name,value) (opt.name.Set(value),raw_opt[0].name=opt.name)
#define GET(name) opt.name.Value()
namespace
{
constexpr int id_tri=0;
constexpr int id_sphere=id_tri+2;
constexpr int id_poly=id_sphere+4;
constexpr int id_seg=id_poly+4;
constexpr int id_arrow=id_seg+1;
constexpr int id_pt=id_arrow+4;
}
//#####################################################################
// Constructor
//#####################################################################
template<class TV> QT_DEBUG_PARTICLES<TV>::
QT_DEBUG_PARTICLES(QT_WORLD& world,const std::string& name,const std::shared_ptr<QT_OBJECT<TYPE>>& obj)
    :QT_COMPONENT(world,name),opt(OPT::def),raw_opt{{},{},{},OPT::def},obj(obj)
{
}
//#####################################################################
// Constructor
//#####################################################################
template<class TV> QT_DEBUG_PARTICLES<TV>::
QT_DEBUG_PARTICLES(QT_WORLD& world,const std::string& name,const std::string& file)
    :QT_DEBUG_PARTICLES(world,name,QT_FILE_OBJECT<TYPE>::Make(world.viewer_dir,file))
{
}
//#####################################################################
// Destructor
//#####################################################################
template<class TV> QT_DEBUG_PARTICLES<TV>::
~QT_DEBUG_PARTICLES()
{
}
//#####################################################################
// Function Render
//#####################################################################
template<class TV> void QT_DEBUG_PARTICLES<TV>::
Render(QT_RENDER_UTILS& ru)
{
    if(!GET(draw)) return;
    Read();
    if(!obj->valid) return;

    auto& dp=obj->Obj();
    ru.pick.Push(0);
    for(int f=0;f<2;f++)
    {
        ru.pick.Set(id_tri+f);
        Render_Triangles(ru,f);
        for(int s=0;s<2;s++)
        {
            auto& a=dp.balls(f)(s);
            if(!a.X(0).m) continue;
            ru.pick.Set(id_sphere+2*f+s);
            ru.Render_Spheres<TV>(a.X(0),a.t(0),a.color,
                {.scale=(s?-1.f:1.f),.fill=(bool)f});
        }
        for(int s=0;s<2;s++)
        {
            ru.pick.Set(id_poly+2*f+s);
            ru.pick.Push(0);
            auto& p=dp.regular_polys(f)(s);
            for(int i=0;i<p.X(0).m;i++)
            {
                if(!p.X(0)(i).m) continue;
                ru.pick.Set(i);
                ru.Render_Regular_Poly(p.X(0)(i),p.t(0)(i),p.i(0)(i),
                    p.color(i),{.fill=(bool)f,.scale=(s?-1.f:1.f),.angle=p.i(1)(i)});
            }
            ru.pick.Pop();
        }
    }
    ru.pick.Set(id_seg);
    Render_Segments(ru);
    for(int a=0;a<2;a++)
        for(int s=0;s<2;s++)
        {
            auto& o=dp.arrows(a)(s);
            if(!o.X(0).m) continue;
            ru.pick.Set(id_arrow+2*a+s);
            ru.Render_Arrows<TV>(o.X(0),o.X(1),o.color,
                {.scale=(s?-1.f:1.f),.use_arrowhead=(bool)a});
        }
    ru.pick.Set(id_pt);
    if(dp.points.X(0).m)
        ru.Render_Points<TV>(dp.points.X(0),dp.points.color);
    ru.pick.Pop();
    if(ru.select) return;

    for(int i=0;i<dp.text.m;i++)
        ru.Render_Text<TV>(dp.text(i).X,dp.text(i).str,
            dp.text(i).color);

    if(selected_type<0 || selected_index<0) return;

    LOG::fprintf(ru.info_text,"index %P\n",selected_index);
    int i=selected_index;
    auto old=ru.Push(ru.depth_offset,ru.depth_offset-ru.depth_offset_unit);
    switch(selected_type)
    {
        case id_tri:case id_tri+1:
            {
                const auto& p=dp.tris(selected_type-id_tri);
                ru.Render_Triangle<TV>(p.X(0)(i),p.X(1)(i),p.X(2)(i),
                    ru.highlight_color,{.fill=false});
                LOG::fprintf(ru.info_text,"X0 %P\nX1 %P\nX2 %P\ncolor %P\n",
                    p.X(0)(i),p.X(1)(i),p.X(2)(i),p.color(i));
            }
            break;
        case id_sphere:case id_sphere+1:case id_sphere+2:case id_sphere+3:
            {
                bool f=(selected_type-id_sphere)/2,s=(selected_type-id_sphere)%2;
                const auto& p=dp.balls(f)(s);
                ru.Render_Sphere<TV>(p.X(0)(i),p.t(0)(i),
                    ru.highlight_color,{.scale=(s?-1.f:1.f),.fill=f});
                LOG::fprintf(ru.info_text,"X %P\nradius %P\ncolor %P\n",p.X(0)(i),p.t(0)(i),p.color(i));
            }
            break;
        case id_poly:case id_poly+1:case id_poly+2:case id_poly+3:
            {
                bool f=(selected_type-id_poly)/2,s=(selected_type-id_poly)%2;
                const auto& p=dp.regular_polys(f)(s);
                ru.Render_Regular_Poly<TV>(p.X(0)(i),p.t(0)(i),p.i(0)(i),
                    ru.highlight_color,
                    {.fill=f,.scale=(s?-1.f:1.f),.angle=p.i(1)(i)});
                LOG::fprintf(ru.info_text,"X %P\nradius %P\ncolor %P\n",p.X(0)(i),p.t(0)(i),p.color(i));
            }
            break;
        case id_seg:
            {
                const auto& p=dp.segments;
                ru.Render_Line<TV>(p.X(0)(i),p.X(1)(i),ru.highlight_color);
                LOG::fprintf(ru.info_text,"X0 %P\nX1 %P\ncolor %P\n",
                    p.X(0)(i),p.X(1)(i),p.color(i));
            }
            break;
        case id_arrow:case id_arrow+1:case id_arrow+2:case id_arrow+3:
            {
                bool a=(selected_type-id_arrow)/2,s=(selected_type-id_arrow)%2;
                auto& p=dp.arrows(a)(s);
                ru.Render_Arrow<TV>(p.X(0)(i),p.X(1)(i),ru.highlight_color,
                    {.scale=(s?-1.f:1.f),.use_arrowhead=(bool)a});
                LOG::fprintf(ru.info_text,"X %P\ndX %P\ncolor %P\n",
                    p.X(0)(i),p.X(1)(i),p.color(i));
            }
            break;
        case id_pt:
            {
                const auto& p=dp.points;
                ru.Render_Point<TV>(p.X(0)(i),ru.highlight_color);
                LOG::fprintf(ru.info_text,"X %P\ncolor %P\n",p.X(0)(i),p.color(i));
            }
            break;
    }
}
//#####################################################################
// Function Make_Shader
//#####################################################################
static const SHADER&
Make_Shader(QT_RENDER_UTILS& ru,bool filled,bool flat,int dim,bool is_tri)
{
    static SHADER shaders[2][2][2][2][2];
    static bool ready[2][2][2][2][2]={};
    auto& s=shaders[filled][ru.select][flat][dim-2][is_tri];
    auto& r=ready[filled][ru.select][flat][dim-2][is_tri];
    if(!r)
    {
        r=true;
        SHADER_FLAGS flags{1,1+filled,2,0,ru.select,flat,dim};
        flags.Simplify();
        
        ARRAY<std::pair<const char*,const char*>> attr_list=
            {{"vec3","X0"},{"vec3","X1"}};
        if(is_tri) attr_list.Append({"vec3","X2"});
        attr_list.Append({"vec3","col"});

        auto vs=Generate_Vertex_Shader(false,attr_list,nullptr);
        auto fs=Generate_Fragment_Shader(flags);

        bool n=!flags.flat_col && dim==3;
        bool t=is_tri;
        std::string gs;

        gs+="#version 420                                              \n";
        gs+="layout(points) in;                                        \n";
        if(filled) gs+="layout(triangle_strip,max_vertices=3) out;     \n";
        else gs+="layout(line_strip,max_vertices=4) out;               \n";
        gs+="                                                          \n";
        gs+="in vec3 gs_X0[];                                          \n";
        gs+="in vec3 gs_X1[];                                          \n";
        if(t) gs+="in vec3 gs_X2[];                                    \n";
        gs+="in vec3 gs_col[];                                         \n";
        gs+="                                                          \n";
        gs+="out vec4 world_pos;                                       \n";
        if(n) gs+="out vec3 normal;                                    \n";
        gs+="out vec3 col;                                             \n";
        gs+="                                                          \n";
        gs+="uniform mat4 obj_to_world;                                \n";
        gs+="uniform mat4 world_to_gl;                                 \n";
        gs+="                                                          \n";
        if(n) gs+="vec3 N;                                             \n";
        gs+="void do_vert(vec4 W,vec4 G)                               \n";
        gs+="{                                                         \n";
        gs+="    world_pos = W;                                        \n";
        gs+="    gl_Position = G;                                      \n";
        gs+="    gl_PrimitiveID = gl_PrimitiveIDIn;                    \n";
        gs+="    col = gs_col[0];                                      \n";
        if(n) gs+="    normal = N;                                     \n";
        gs+="    EmitVertex();                                         \n";
        gs+="}                                                         \n";
        gs+="                                                          \n";
        gs+="void main(void)                                           \n";
        gs+="{                                                         \n";
        gs+="    vec4 A = obj_to_world * vec4(gs_X0[0],1);             \n";
        gs+="    vec4 B = obj_to_world * vec4(gs_X1[0],1);             \n";
        if(t) gs+="    vec4 C = obj_to_world * vec4(gs_X2[0],1);       \n";
        gs+="    vec4 AG = world_to_gl * A;                            \n";
        gs+="    vec4 BG = world_to_gl * B;                            \n";
        if(t) gs+="    vec4 CG = world_to_gl * C;                      \n";
        if(n) gs+="    N = normalize(cross(B-A,C-A));                  \n";
        gs+="    do_vert(A,AG);                                        \n";
        gs+="    do_vert(B,BG);                                        \n";
        if(t) gs+="    do_vert(C,CG);                                  \n";
        if(t && !filled) gs+="    do_vert(A,AG);                       \n";
        gs+="    EndPrimitive();                                       \n";
        gs+="}                                                         \n";

        s.Init(ru.window,flags,false,attr_list,{},vs.c_str(),gs.c_str(),fs.c_str());
    }
    return s;
}
//#####################################################################
// Function Render_Triangles
//#####################################################################
template<class TV> void QT_DEBUG_PARTICLES<TV>::
Render_Triangles(QT_RENDER_UTILS& ru,bool filled) const
{
    auto& t=obj->Obj().tris[filled];
    if(!t.X(0).m) return;
    SHADER_FLAGS flags{1,2-(ru.wireframe||!filled),2,0,ru.select,TV::m==2,TV::m};

    SHADER::SH sh(Make_Shader(ru,filled,TV::m==2,TV::m,true),ru,flags);
    SHADER::ATTR X0(sh,"X0",ARRAY_VIEW<const TV>(t.X(0)));
    SHADER::ATTR X1(sh,"X1",ARRAY_VIEW<const TV>(t.X(1)));
    SHADER::ATTR X2(sh,"X2",ARRAY_VIEW<const TV>(t.X(2)));
    SHADER::ATTR col(sh,"col",t.color);

    glLineWidth(ru.line_width*(1+ru.select));
    glDrawArrays(GL_POINTS,0,t.X(0).m);
    if(ru.select) ru.pick.Commit_Names(t.X(0).m);
    CHECK_ERRORS(&ru);
}
//#####################################################################
// Function Render_Segments
//#####################################################################
template<class TV> void QT_DEBUG_PARTICLES<TV>::
Render_Segments(QT_RENDER_UTILS& ru) const
{
    auto& dp=obj->Obj();
    const auto& s=dp.segments;
    if(!s.X(0).m) return;
    SHADER_FLAGS flags{1,1,2,0,ru.select,TV::m==2,TV::m};

    SHADER::SH sh(Make_Shader(ru,false,true,TV::m,false),ru,flags);
    SHADER::ATTR X0(sh,"X0",ARRAY_VIEW<const TV>(s.X(0)));
    SHADER::ATTR X1(sh,"X1",ARRAY_VIEW<const TV>(s.X(1)));
    SHADER::ATTR col(sh,"col",s.color);

    glLineWidth(ru.line_width*(1+ru.select));
    glDrawArrays(GL_POINTS,0,s.X(0).m);
    if(ru.select) ru.pick.Commit_Names(s.X(0).m);
    CHECK_ERRORS(&ru);
}
//#####################################################################
// Function Bounding_Box
//#####################################################################
template<class TV> auto QT_DEBUG_PARTICLES<TV>::
Bounding_Box() -> RANGE<FV>
{
    if(!GET(draw)) return RANGE<FV>::Empty_Box();
    Read();
    if(!obj->valid) return RANGE<FV>::Empty_Box();
    auto box=obj->Obj().Bounding_Box();
    return RANGE<FV>(FV(box.min_corner),FV(box.max_corner));
}
//#####################################################################
// Function Get_Selection
//#####################################################################
template<class TV> void QT_DEBUG_PARTICLES<TV>::
Set_Selection(ARRAY_VIEW<int> selection)
{
    PHYSBAM_ASSERT(selection.m==2 || selection.m==3);
    selected_type=selection(0);
    selected_index=selection(1);
}
//#####################################################################
// Function Clear_Selection
//#####################################################################
template<class TV> void QT_DEBUG_PARTICLES<TV>::
Clear_Selection()
{
    selected_type=-1;
    selected_index=-1;
}
//#####################################################################
// Function Reinitialize
//#####################################################################
template<class TV> void QT_DEBUG_PARTICLES<TV>::
Read()
{
    obj->Read();
}
//#####################################################################
// Function Invalidate
//#####################################################################
template<class TV> void QT_DEBUG_PARTICLES<TV>::
Invalidate(bool invalidate_common)
{
    obj->Invalidate(invalidate_common);
    selected_type=-1;
    selected_index=-1;
}
//#####################################################################
// Function Selection_Point
//#####################################################################
template<class TV> auto QT_DEBUG_PARTICLES<TV>::
Selection_Point() -> FV
{
    Read();
    PHYSBAM_ASSERT(obj->valid);
    auto& dp=obj->Obj();
    int i=selected_index;
    switch(selected_type)
    {
        case id_tri:case id_tri+1:
            {
                const auto& p=dp.tris(selected_type-id_tri);
                return FV((p.X(0)(i)+p.X(1)(i)+p.X(2)(i))/3);
            }
        case id_sphere:case id_sphere+1:case id_sphere+2:case id_sphere+3:
            {
                bool f=(selected_type-id_sphere)/2,s=(selected_type-id_sphere)%2;
                const auto& p=dp.balls(f)(s);
                return FV(p.X(0)(i));
            }
        case id_poly:case id_poly+1:case id_poly+2:case id_poly+3:
            {
                bool f=(selected_type-id_poly)/2,s=(selected_type-id_poly)%2;
                const auto& p=dp.regular_polys(f)(s);
                return FV(p.X(0)(i));
            }
        case id_seg:
            {
                const auto& p=dp.segments;
                return FV((p.X(0)(i)+p.X(1)(i))/2);
            }
        case id_arrow:case id_arrow+1:case id_arrow+2:case id_arrow+3:
            {
                bool a=(selected_type-id_arrow)/2,s=(selected_type-id_arrow)%2;
                auto& p=dp.arrows(a)(s);
                return FV(p.X(0)(i));
            }
        case id_pt:
            {
                const auto& p=dp.points;
                return FV(p.X(0)(i));
            }
    }
    PHYSBAM_FATAL_ERROR();
}
//#####################################################################
// Function Make_Dialog
//#####################################################################
template<class TV> void QT_DEBUG_PARTICLES<TV>::
Make_Dialog(QT_WINDOW& window)
{
    QT_COMPONENT_OPTIONS* dialog=new QT_COMPONENT_OPTIONS(window,*this);
    dialog->setWindowTitle(QString::fromStdString(name));
    OPT opt2,raw_opt2;
    opt2=opt;
    raw_opt2=raw_opt[0];
    dialog->Add("Show object in viewer",opt.draw,raw_opt[0].draw);
    dialog->Add("Name to display in viewer",opt.name,raw_opt[0].name);
    dialog->Add_End_Buttons();
    int result=dialog->exec();
    if(result!=QDialog::Accepted)
    {
        opt=opt2;
        raw_opt[0]=raw_opt2;
    }
    window.update();
}
//#####################################################################
// Function Read_Option
//#####################################################################
template<class TV> void QT_DEBUG_PARTICLES<TV>::
Read_Option(std::istream& in,int level)
{
    auto& o=raw_opt[level];
    o.Read_Entry(in);
    opt=raw_opt[3];
    opt.Merge(raw_opt[2]);
    opt.Merge(raw_opt[1]);
    opt.Merge(raw_opt[0]);
}
//#####################################################################
// Function Set_Draw
//#####################################################################
template<class TV> void QT_DEBUG_PARTICLES<TV>::
Set_Draw(bool draw)
{
    SET(draw,draw);
    action->setChecked(Draw());
}
//#####################################################################
// Function Draw
//#####################################################################
template<class TV> bool QT_DEBUG_PARTICLES<TV>::
Draw() const
{
    return GET(draw);
}
//#####################################################################
// Function Save_Settings
//#####################################################################
template<class TV> void QT_DEBUG_PARTICLES<TV>::
Save_Settings(std::ostream& out)
{
    raw_opt[0].Write(out,name);
}
//#####################################################################
// Function Clear_Settings
//#####################################################################
template<class TV> void QT_DEBUG_PARTICLES<TV>::
Clear_Settings()
{
    raw_opt[0]={};
    opt=raw_opt[3];
    opt.Merge(raw_opt[2]);
    opt.Merge(raw_opt[1]);
    opt.Merge(raw_opt[0]);
}
//#####################################################################
template class QT_DEBUG_PARTICLES<VECTOR<float,2> >;
template class QT_DEBUG_PARTICLES<VECTOR<double,2> >;
template class QT_DEBUG_PARTICLES<VECTOR<float,3> >;
template class QT_DEBUG_PARTICLES<VECTOR<double,3> >;
}
