//#####################################################################
// Copyright 2004-2009, Eran Guendelman, Frank Losasso, Andrew Selle.
// 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/Log/LOG.h>
#include <Core/Particles/PARTICLES.h>
#include <Core/Read_Write/FILE_UTILITIES.h>
#include <Core/Utilities/VIEWER_DIR.h>
#include "../QT_RENDER_UTILS.h"
#include "../QT_WORLD.h"
#include "QT_COMPONENT_OPTIONS.h"
#include "QT_OBJECT.h"
#include "QT_PARTICLES.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()
//#####################################################################
// Constructor
//#####################################################################
template<class TV> QT_PARTICLES<TV>::
QT_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)
{
//    opt.set_range_from_data=[](void* t){((SELF*)t)->Set_Range_From_Data();};
}
//#####################################################################
// Constructor
//#####################################################################
template<class TV> QT_PARTICLES<TV>::
QT_PARTICLES(QT_WORLD& world,const std::string& name,const std::string& file)
    :QT_PARTICLES(world,name,QT_FILE_OBJECT<TYPE>::Make(world.viewer_dir,file))
{
}
//#####################################################################
// Destructor
//#####################################################################
template<class TV> QT_PARTICLES<TV>::
~QT_PARTICLES()
{
    delete pa;
}
//#####################################################################
// Function Display
//#####################################################################
template<class TV> void QT_PARTICLES<TV>::
Render(QT_RENDER_UTILS& ru)
{
    typedef VECTOR<float,3> C;
    if(!GET(draw)) return;
    Read();
    if(!obj->valid || !GET(position_attr).size()) return;
    auto& particles=obj->Obj();
    ARRAY_VIEW<TV>* X=particles.template Get_Array<TV>(GET(position_attr));
    if(!X) return;
    QT_RENDER_UTILS::RU_COLOR co;
    ARRAY<C> colors;
    if(GET(color_attr).size())
    {
        auto do_scalar=[this,&co,&colors](auto& array)
        {
            for(auto a:array)
                colors.Append((GET(color_map))((a-GET(min))/(GET(max)-GET(min))));
            co.Set(colors);
        };

        if(auto array=particles.template Get_Array<VECTOR<float,3>>(GET(color_attr)))
            co.Set(*array);
        else if(auto array=particles.template Get_Array<VECTOR<double,3>>(GET(color_attr)))
            co.Set(*array);
        else if(auto array=particles.template Get_Array<float>(GET(color_attr)))
            do_scalar(*array);
        else if(auto array=particles.template Get_Array<double>(GET(color_attr)))
            do_scalar(*array);
        else co.Set(GET(default_color));
    }
    else co.Set(GET(default_color));
    ru.pick.Push(0);
    ru.Render_Points<TV>(*X,co);
    if(GET(draw_vectors) && GET(vector_attr).size())
        if(ARRAY_VIEW<TV>* V=particles.template Get_Array<TV>(GET(vector_attr)))
            if(X->m==V->m)
            {
                QT_RENDER_UTILS::RU_COLOR cv;
                cv.Set(GET(vector_color));
                ru.Render_Arrows<TV>(*X,*V,GET(use_vector_color)?cv:co);
            }
    if(GET(visualize_matrix) && GET(matrix_attr).size())
        if(ARRAY_VIEW<MATRIX<T,TV::m>>* M=particles.template Get_Array<MATRIX<T,TV::m>>(GET(matrix_attr)))
            if(X->m==M->m)
            {
                QT_RENDER_UTILS::RU_COLOR cv;
                cv.Set(GET(vector_color));
                ru.Render_Matrices<TV>(*X,*M);
            }

    ru.pick.Pop();

    if(selected_particle>=0)
    {
        LOG::fprintf(ru.info_text,"particle %P\n",selected_particle);
        particles.Print(ru.info_text,selected_particle);
        auto old=ru.Push(ru.depth_offset,ru.depth_offset-ru.depth_offset_unit);
        ru.Render_Point((*X)(selected_particle),ru.highlight_color,
            {.point_size=ru.highlighted_point_size});
        if(GET(draw_vectors) && GET(vector_attr).size())
            if(ARRAY_VIEW<TV>* V=particles.template Get_Array<TV>(GET(vector_attr)))
                if(X->m==V->m)
                    ru.Render_Arrow((*X)(selected_particle),
                        (*V)(selected_particle),ru.highlight_color);
    }
}
//#####################################################################
// Function Bounding_Box
//#####################################################################
template<class TV> auto QT_PARTICLES<TV>::
Bounding_Box() -> RANGE<FV>
{
    if(!GET(draw)) return RANGE<FV>::Empty_Box();
    Read();
    if(!obj->valid) return RANGE<FV>::Empty_Box();
    ARRAY_VIEW<TV>* X=obj->Obj().template Get_Array<TV>(GET(position_attr));
    if(!X) return RANGE<FV>::Empty_Box();
    return RANGE<FV>(RANGE<TV>::Bounding_Box(*X));
}
//#####################################################################
// Function Get_Selection_Priority
//#####################################################################
// template<class TV> int QT_PARTICLES<TV>::
// Get_Selection_Priority(ARRAY_VIEW<GLuint> indices)
// {
//     if(!indices.m) return -1;
//     return 100;
// }
//#####################################################################
// Function Get_Selection
//#####################################################################
template<class TV> void QT_PARTICLES<TV>::
Set_Selection(ARRAY_VIEW<int> selection)
{
    PHYSBAM_ASSERT(selection.m==2 && selection(0)==0);
    selected_particle=selection(1);
}
//#####################################################################
// Function Clear_Highlight
//#####################################################################
template<class TV> void QT_PARTICLES<TV>::
Clear_Selection()
{
    selected_particle=-1;
}
//#####################################################################
// Function Reinitialize
//#####################################################################
template<class TV> void QT_PARTICLES<TV>::
Read()
{
    obj->Read();
    if(pa)
    {
        try
        {
            pa->Compute_Attributes();
        }
        catch(std::runtime_error& e)
        {
            LOG::printf("color program error: %P\n",e.what());
            delete pa;
            pa=nullptr;
            return;
        }
    }
}
//#####################################################################
// Function Invalidate
//#####################################################################
template<class TV> void QT_PARTICLES<TV>::
Invalidate(bool invalidate_common)
{
    obj->Invalidate(invalidate_common);
}
//#####################################################################
// Function Selection_Point
//#####################################################################
template<class TV> auto QT_PARTICLES<TV>::
Selection_Point() -> FV
{
    Read();
    PHYSBAM_ASSERT(obj->valid);
    auto& particles=obj->Obj();
    ARRAY_VIEW<TV>* X=particles.template Get_Array<TV>(GET(position_attr));
    PHYSBAM_ASSERT(X);
    return FV((*X)(selected_particle));
}
//#####################################################################
// Function Get_Frame
//#####################################################################
template<class TV> auto QT_PARTICLES<TV>::
Get_Frame(const std::string& attr,int p) -> std::pair<FRAME<FV>,bool>
{
    Read();
    if(obj->valid && attr.size())
    {
        auto& particles=obj->Obj();
        if(p<particles.Size())
            if(auto a=particles.template Get_Array<FRAME<TV>>(attr))
            {
                auto f=(*a)(p);
                if constexpr(TV::m==3) return {FRAME<FV>(f),true};
                else
                {
                    FRAME<FV> r;
                    r.t=VECTOR<float,3>(f.t);
                    r.r=ROTATION<FV>(ROTATION<VECTOR<float,2>>(f.r));
                }
            }
    }
    return {};
}
//#####################################################################
// Function Print_Particle
//#####################################################################
template<class TV> void QT_PARTICLES<TV>::
Print_Particle(std::ostream& o,int p)
{
    Read();
    auto& particles=obj->Obj();
    particles.Print(o,p);
}
//#####################################################################
// Function Set_Scale_Range
//#####################################################################
template<class TV> void QT_PARTICLES<TV>::
Set_Range_From_Data()
{
    Read();
    if(!obj->valid || !GET(color_attr).size()) return;
    auto& particles=obj->Obj();
    auto set_min_max=[this](auto& array)
    {
        if(array.m)
        {
            SET(min,array.Min());
            SET(max,array.Max());
        }
        else
        {
            SET(min,0);
            SET(max,1);
        }
    };

    if(auto array=particles.template Get_Array<float>(GET(color_attr)))
        set_min_max(*array);
    else if(auto array=particles.template Get_Array<double>(GET(color_attr)))
        set_min_max(*array);
}
//#####################################################################
// Function Make_Dialog
//#####################################################################
template<class TV> void QT_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("draw vectors",opt.draw_vectors,raw_opt[0].draw_vectors);
    dialog->Add("default_color",opt.default_color,raw_opt[0].default_color);
    dialog->Add("Map this value to 0 before color map",opt.min,raw_opt[0].min);
    dialog->Add("Map this value to 1 before color map",opt.max,raw_opt[0].max);
    dialog->Add_Button("Compute min and max from data",[this](){Set_Range_From_Data();});
    dialog->Add("Construct color from scalar data",opt.color_map,raw_opt[0].color_map);
    dialog->Add("use fixed vector colors",opt.use_vector_color,raw_opt[0].use_vector_color);
    dialog->Add("fixed vector colors",opt.vector_color,raw_opt[0].vector_color);
    dialog->Add("visualize matrix columns",opt.visualize_matrix,raw_opt[0].visualize_matrix);
    dialog->Add("Particle position attribute name",opt.position_attr,raw_opt[0].position_attr);
    dialog->Add("Particle color attribute name",opt.color_attr,raw_opt[0].color_attr);
    dialog->Add("Particle vector attribute name",opt.vector_attr,raw_opt[0].vector_attr);
    dialog->Add("Particle matrix attribute name",opt.matrix_attr,raw_opt[0].matrix_attr);
    dialog->Add("Use program to compute value or color",opt.use_color_prog,raw_opt[0].use_color_prog);
    dialog->Add("Program to compute value or color",opt.color_prog,raw_opt[0].color_prog);
    dialog->Add_Button("Apply program",[this](){Apply_Program();});
    dialog->Add_End_Buttons();
    int result=dialog->exec();
    if(result!=QDialog::Accepted)
    {
        opt=opt2;
        raw_opt[0]=raw_opt2;
    }
    if(pa && !GET(use_color_prog))
    {
        delete pa;
        pa=nullptr;
    }
    window.update();
}
//#####################################################################
// Function Read_Option
//#####################################################################
template<class TV> void QT_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_PARTICLES<TV>::
Set_Draw(bool draw)
{
    SET(draw,draw);
    action->setChecked(Draw());
}
//#####################################################################
// Function Draw
//#####################################################################
template<class TV> bool QT_PARTICLES<TV>::
Draw() const
{
    return GET(draw);
}
//#####################################################################
// Function Save_Settings
//#####################################################################
template<class TV> void QT_PARTICLES<TV>::
Save_Settings(std::ostream& out)
{
    raw_opt[0].Write(out,name);
}
//#####################################################################
// Function Clear_Settings
//#####################################################################
template<class TV> void QT_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]);
}
//#####################################################################
// Function Apply_Program
//#####################################################################
template<class TV> void QT_PARTICLES<TV>::
Apply_Program()
{
    if(!GET(use_color_prog)) return;
    std::string code=GET(color_prog);
    if(!code.size()) return;
    code="val=-1e10;col=vec(val,val,val);"+code;
    delete pa;
    auto& particles=obj->Obj();
    particles.template Add_Array<VECTOR<T,3>>("col");
    particles.template Add_Array<T>("val");
    try
    {
        pa=new PARTICLE_ATTRIBUTE_PROGRAM<TV>(particles,code,{"col","val"});
        pa->Compute_Attributes();
    }
    catch(std::runtime_error& e)
    {
        LOG::printf("color program error: %P\n",e.what());
        delete pa;
        pa=nullptr;
        return;
    }
}
template class QT_PARTICLES<VECTOR<float,2>>;
template class QT_PARTICLES<VECTOR<float,3>>;
template class QT_PARTICLES<VECTOR<double,2>>;
template class QT_PARTICLES<VECTOR<double,3>>;
}
