//#####################################################################
// 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.
//#####################################################################
#include <Core/Arrays/ARRAYS_ND.h>
#include <Core/Arrays/FACE_ARRAYS.h>
#include <Core/Basic_Geometry/RAY_HIT.h>
#include <Core/Geometry_Particles/DEBUG_PARTICLES.h>
#include <Core/Level_Sets/LEVELSET.h>
#include <Core/Log/LOG.h>
#include <Core/Matrices/MATRIX.h>
#include <Core/Meshes/MESH_OBJECT.h>
#include <Core/Parsing/PARSE_ARGS.h>
#include <Core/Utilities/VIEWER_DIR.h>
#include <Core/Vectors/VECTOR.h>
#include "components/QT_AXES.h"
#include "components/QT_COMPONENT.h"
#include "components/QT_DEBUG_PARTICLES.h"
#include "components/QT_FACE_FIELD.h"
#include "components/QT_GRID.h"
#include "components/QT_GROUP.h"
#include "components/QT_OBJECT.h"
#include "components/QT_PARTICLES.h"
#include "components/QT_SCALAR_FIELD.h"
#include "components/QT_SEGMENTED_CURVE.h"
#include "components/QT_TETRAHEDRALIZED_VOLUME.h"
#include "components/QT_TRIANGULATED_OBJECT.h"
#include "components/QT_VECTOR_FIELD.h"
#include "QT_CAMERA.h"
#include "QT_MAIN_WINDOW.h"
#include "QT_PICK.h"
#include "QT_RENDER_UTILS.h"
#include "QT_WINDOW.h"
#include "QT_WORLD.h"
#include <fstream>

namespace PhysBAM{
#define SET(name,value) (opt.name.Set(value),raw_opt[0].name=opt.name)
#define GET(name) opt.name.Value()
template<int num_args,class TYPE> void
Make_Object(QT_WORLD& world,const std::string& name,bool common,
    std::stringstream& ss)
{
    std::string a0,a1;
    TYPE* t=nullptr;
    if constexpr(num_args==0) t=new TYPE(world,name);
    else ss>>std::quoted(a0);
    if constexpr(num_args==1) t=new TYPE(world,name,a0);
    else ss>>std::quoted(a1);
    if constexpr(num_args==2) t=new TYPE(world,name,a0,a1);
    auto s=std::shared_ptr<QT_COMPONENT>{t};
    world.Add_Object(s,common);
}
template<int num_args,class TYPE> void
Make_Div(QT_WORLD& world,const std::string& name,bool common,
    std::stringstream& ss)
{
    std::string a0;
    ss>>std::quoted(a0);
    typedef typename TYPE::TYPE OBJ;
    typedef typename OBJ::ELEMENT T;
    constexpr int d=OBJ::INDEX::m;
    typedef ARRAY<T,FACE_INDEX<d>> FACE_OBJ;
    typedef VECTOR<float,d> FV;
    auto ub=world.Find_Component(a0);
    auto u=dynamic_pointer_cast<QT_FACE_FIELD<T,d>>(ub);
    if(!u)
    {
        LOG::printf("suitable field %s not found\n",a0);
        return;
    }
    typedef ARRAY<std::shared_ptr<QT_OBJECT_BASE>> T_OBJECTS;
    auto w=new QT_OBJECT_WRAPPER<OBJ,true>(
        [](OBJ*& obj,const T_OBJECTS& list)
        {
            auto u=dynamic_pointer_cast<QT_OBJECT<FACE_OBJ>>(list(0));
            auto g=dynamic_pointer_cast<QT_OBJECT<GRID<FV>>>(list(1));
            if(!u || !g) return false;
            auto& uo=u->Obj();
            auto& grid=g->Obj();
            obj->Resize(uo.domain);
            for(RANGE_ITERATOR<d> it(uo.domain);it.Valid();it.Next())
            {
                T x=0;
                for(int a=0;a<d;a++)
                {
                    FACE_INDEX<d> f0(a,it.index),f1(a,it.index.Add_Axis(a,1));
                    x+=(uo(f1)-uo(f0))*grid.one_over_dX(a);
                }
                (*obj)(it.index)=x;
            }
            return true;
        });
    w->objects.Append(u->Get_Object());
    w->objects.Append(u->Get_Grid()->Get_Object());
    TYPE* t=new TYPE(world,name,u->Grid_Name(),std::shared_ptr<QT_OBJECT<OBJ>>{w});
    auto s=std::shared_ptr<QT_COMPONENT>{t};
    world.Add_Object(s,common);
}
#define MKX(name,suf,n,tmpl,mo)                 \
    {name suf,mo<n,tmpl>}
#define MKT(name,suf,n,tmpl,mo)                 \
    {name ".f" suf,mo<n,tmpl<float>>},          \
    {name ".d" suf,mo<n,tmpl<double>>}
#define MKD(name,suf,n,tmpl,mo)                 \
    {name ".2" suf,mo<n,tmpl<2>>},              \
    {name ".3" suf,mo<n,tmpl<3>>},              \
    {name ".2.f" suf,mo<n,tmpl<2>>},            \
    {name ".3.f" suf,mo<n,tmpl<3>>},            \
    {name ".2.d" suf,mo<n,tmpl<2>>},            \
    {name ".3.d" suf,mo<n,tmpl<3>>}
#define MKV(name,suf,n,tmpl,mo)                         \
    {name ".2.f" suf,mo<n,tmpl<VECTOR<float,2>>>},      \
    {name ".3.f" suf,mo<n,tmpl<VECTOR<float,3>>>},      \
    {name ".2.d" suf,mo<n,tmpl<VECTOR<double,2>>>},     \
    {name ".3.d" suf,mo<n,tmpl<VECTOR<double,3>>>}
#define MKW(name,suf,n,tmpl,mo)                 \
    {name ".2.f" suf,mo<n,tmpl<float,2>>},      \
    {name ".3.f" suf,mo<n,tmpl<float,3>>},      \
    {name ".2.d" suf,mo<n,tmpl<double,2>>},     \
    {name ".3.d" suf,mo<n,tmpl<double,3>>}
#define MKB(name,suf,n,tmpl,mo)                 \
    MKW(name,suf,n,tmpl,mo),                    \
    {name ".2.b" suf,mo<n,tmpl<bool,2>>},       \
    {name ".3.b" suf,mo<n,tmpl<bool,3>>},       \
    {name ".2.i" suf,mo<n,tmpl<int,2>>},        \
    {name ".3.i" suf,mo<n,tmpl<int,3>>}
    
static const std::pair<const char*,void(*)(QT_WORLD& world,const std::string& name,bool common,std::stringstream& ss)> factory_list[]=
{
    MKV("debug",,1,QT_DEBUG_PARTICLES,Make_Object),
    MKB("grid_scalar",,2,QT_SCALAR_FIELD,Make_Object),
    MKV("grid_vector",,2,QT_VECTOR_FIELD,Make_Object),
    MKB("grid_face",,2,QT_FACE_FIELD,Make_Object),
    MKV("mesh.1",,1,QT_SEGMENTED_CURVE,Make_Object),
    MKV("mesh.2",,1,QT_TRIANGULATED_OBJECT,Make_Object),
    MKT("mesh.3.3",,1,QT_TETRAHEDRALIZED_VOLUME,Make_Object),
    MKV("mesh.1",".sp",2,QT_SEGMENTED_CURVE,Make_Object),
    MKV("mesh.2",".sp",2,QT_TRIANGULATED_OBJECT,Make_Object),
    MKT("mesh.3.3",".sp",2,QT_TETRAHEDRALIZED_VOLUME,Make_Object),
    MKV("particles",,1,QT_PARTICLES,Make_Object),
    MKD("grid",,1,QT_GRID,Make_Object),
    MKD("axes",,0,QT_AXES,Make_Object),
    MKX("group",,0,QT_GROUP,Make_Object),
    MKW("div",,2,QT_SCALAR_FIELD,Make_Div),
};

#undef MK
#undef MKV
#undef MKT
#undef COMMA

//#####################################################################
// Constructor
//#####################################################################
QT_WORLD::
QT_WORLD(QT_MAIN_WINDOW& main_window)
    :main_window(main_window),opt(OPT::def),raw_opt{{},{},{},OPT::def},
    viewer_dir(*new VIEWER_DIR(""))
{
    for(auto p:factory_list)
        component_factory.Set(p.first,p.second);
}
//#####################################################################
// Destructor
//#####################################################################
QT_WORLD::
~QT_WORLD()
{
    delete &viewer_dir;
}
//#####################################################################
// Function Initialize
//#####################################################################
void QT_WORLD::
Initialize()
{
    Read_Manifest(true);
    main_window.Reload_Settings();
}
//#####################################################################
// Function Read
//#####################################################################
void QT_WORLD::
Read_Manifest(bool common_only)
{
    std::string manifest;
    if(common_only) manifest=viewer_dir.Common_File("manifest.txt");
    else manifest=viewer_dir.Frame_File("manifest.txt");
    std::string line,token,type,name;
    std::ifstream fin(manifest);
    while(getline(fin,line))
    {
        std::stringstream ss(line);
        ss>>std::quoted(type)>>std::quoted(name);
        if(type=="set")
        {
            if(name=="global")
                Read_Option(ss,1+common_only);
            else
            {
                auto& mp=objects[!common_only];
                auto it=mp.find(name);
                if(it!=mp.end())
                    it->second->Read_Option(ss,1+common_only);
                else LOG::printf("Object '%P' not found\n",name);
            }
        }
        else
        {
            auto& mp=objects[!common_only];
            auto it=mp.find(name);
            if(it!=mp.end())
                LOG::printf("Object '%P' already exists\n",name);
            else
            {
                if(auto* factory=component_factory.Get_Pointer(type))
                    (*factory)(*this,name,common_only,ss);
                else LOG::printf("Unknown info type %P\n",type);
            }
        }
    }
}
//#####################################################################
// Function Read
//#####################################################################
void QT_WORLD::
Read_Settings()
{
    std::string line,type,name;
    std::ifstream fin(viewer_dir.Common_File("settings.txt"));
    while(getline(fin,line))
    {
        std::stringstream ss(line);
        ss>>std::quoted(type)>>std::quoted(name);
        if(type!="set")
        {
            LOG::printf("Settings files can only set options; got %P\n",type);
            continue;
        }
        if(name=="global")
            Read_Option(ss,0);
        else
        {
            bool found=false;
            for(auto&mp:objects)
            {
                auto it=mp.find(name);
                if(it!=mp.end())
                {
                    it->second->Read_Option(ss,0);
                    found=true;
                }
            }
            if(!found) LOG::printf("Read settings for unknown object '%P'\n",name);
        }
    }
}
//#####################################################################
// Function Read
//#####################################################################
void QT_WORLD::
Read()
{
    std::string frame_title;
    std::ifstream fin(viewer_dir.Frame_File("frame_title"));
    getline(fin,frame_title);
    frame_string="Frame "+viewer_dir.Frame_As_String();
    if(frame_title.size()) frame_string+=": "+frame_title;
    frame_string+="\n";

    for(auto o:objects[0]) o.second->Invalidate();
    for(auto o:objects[1]) o.second.reset();
    objects[1].clear();
    Read_Manifest(false);
}
//#####################################################################
// Function Reset
//#####################################################################
void QT_WORLD::
Reset()
{
    objects[0].clear();
    objects[1].clear();
    Initialize();
    Read();
}
//#####################################################################
// Function Bounding_Box
//#####################################################################
auto QT_WORLD::
Bounding_Box() -> RANGE<TV>
{
    RANGE<TV> box=RANGE<TV>::Empty_Box();
    for(auto objs:objects)
        for(const auto& o:objs)
            if(!o.second->parent)
                box=box.Unite(o.second->Bounding_Box());
    if(box.Empty())
        return RANGE<TV>::Centered_Box();
    return box;
}
//#####################################################################
// Function Render
//#####################################################################
void QT_WORLD::
Render(QT_RENDER_UTILS& ru,bool select)
{
    ru.Reset();
    ru.select=select;
    ru.Update_Transform();
    
    const qreal retinaScale = main_window.devicePixelRatio();
    glViewport(0, 0, main_window.width() * retinaScale, main_window.height() * retinaScale);
    CHECK_ERRORS(&ru);

    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    if(ru.slice_grid)
    {
        glEnable(GL_CLIP_DISTANCE0);
        glEnable(GL_CLIP_DISTANCE1);
    }
    CHECK_ERRORS(&ru);

    ru.info_text<<frame_string;

    if(selected) LOG::fprintf(ru.info_text,"Selected: %P\n",selected->Name());

    for(int i=0;i<2;i++)
    {
        ru.pick.Push(i);
        int j=0;
        for(const auto& o:objects[i])
        {
            ru.pick.Push(j++);
            if(!o.second->parent)
                o.second->Render(ru);
            ru.pick.Pop();
        }
        ru.pick.Pop();
    }

    if(ru.slice_grid)
    {
        glDisable(GL_CLIP_DISTANCE0);
        glDisable(GL_CLIP_DISTANCE1);
    }
    glDisable(GL_DEPTH_TEST);
    ru.select=false;
}
//#####################################################################
// Function Next_Frame
//#####################################################################
bool QT_WORLD::
Next_Frame(bool use_substeps)
{
    if(!viewer_dir.Find_Next_Directory(use_substeps?GET(substeps_level):0,true))
        return false;
    Read();
    return true;
}
//#####################################################################
// Function Prev_Frame
//#####################################################################
bool QT_WORLD::
Prev_Frame()
{
    if(!viewer_dir.Find_Prev_Directory(GET(substeps_level),true))
        return false;
    Read();
    return true;
}
//#####################################################################
// Function Set_Frame
//#####################################################################
void QT_WORLD::
Set_Frame(int frame)
{
    viewer_dir.Set(frame);
    Read();
}
//#####################################################################
// Function Set_Frame
//#####################################################################
void QT_WORLD::
Set_Frame(const std::string& frame)
{
    viewer_dir.Set(frame);
    Read();
}
//#####################################################################
// Function First_Frame
//#####################################################################
void QT_WORLD::
First_Frame()
{
    viewer_dir.Set(0);
    Read();
}
//#####################################################################
// Function Last_Frame
//#####################################################################
void QT_WORLD::
Last_Frame()
{
    viewer_dir.Read_Last_Frame();
    Read();
}
//#####################################################################
// Function Find_Component
//#####################################################################
std::shared_ptr<QT_COMPONENT> QT_WORLD::
Find_Component(const std::string& name) const
{
    for(auto& o:objects)
    {
        auto it=o.find(name);
        if(it!=o.end())
            return it->second;
    }
    return nullptr;
}
//#####################################################################
// Function Find_Component
//#####################################################################
void QT_WORLD::
Remove_Component(const std::string& name)
{
    for(auto& o:objects)
    {
        auto it=o.find(name);
        if(it!=o.end())
            o.erase(it);
    }
}
//#####################################################################
// Function Add_Object
//#####################################################################
void QT_WORLD::
Add_Object(const std::shared_ptr<QT_COMPONENT>& o,bool common)
{
    objects[!common][o->Name()]=o;
}
//#####################################################################
// Function Save_Settings
//#####################################################################
void QT_WORLD::
Save_Settings(std::ostream& out)
{
    raw_opt[0].view_scale=main_window.camera.scale;
    raw_opt[0].target_position=main_window.camera.target_position;
    raw_opt[0].rotation_matrix=main_window.camera.rotation_matrix;
    raw_opt[0].window_size={main_window.width(),main_window.height()};
    raw_opt[0].Write(out,"global");
}
//#####################################################################
// Function Update_Settings
//#####################################################################
void QT_WORLD::
Update_Settings()
{
    opt=raw_opt[3];
    opt.Merge(raw_opt[2]);
    opt.Merge(raw_opt[1]);
    opt.Merge(raw_opt[0]);
    main_window.Set_2D_Mode(GET(is_2d));
    if(auto ru=main_window.window.ru)
    {
        ru->scale=GET(scale);
        ru->point_size=GET(point_size);
        ru->highlighted_point_size=GET(point_size)*1.25f;
    }
    if(raw_opt[0].view_scale.Valid()) main_window.camera.scale=raw_opt[0].view_scale.Value();
    if(raw_opt[0].target_position.Valid()) main_window.camera.target_position=raw_opt[0].target_position.Value();
    if(raw_opt[0].rotation_matrix.Valid()) main_window.camera.rotation_matrix=raw_opt[0].rotation_matrix.Value();
    VECTOR<int,2> size(640,480);
    if(raw_opt[0].window_size.Valid()) size=raw_opt[0].window_size.Value();
    main_window.resize(size.x,size.y);
}
//#####################################################################
// Function Clear_Settings
//#####################################################################
void QT_WORLD::
Clear_Settings()
{
    raw_opt[0]={};
    Update_Settings();
}
//#####################################################################
// Function Read_Option
//#####################################################################
void QT_WORLD::
Read_Option(std::istream& in,int level)
{
    auto& o=raw_opt[level];
    o.Read_Entry(in);
    Update_Settings();
}
}
