//#####################################################################
// Copyright 2004-2009, Eran Guendelman, 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/Grids/GRID.h>
#include <Core/Grids_Uniform_Computations/MARCHING_CUBES.h>
#include <Core/Meshes/MESH_OBJECT.h>
#include <Core/Meshes/SEGMENTED_CURVE.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_GRID.h"
#include "QT_OBJECT.h"
#include "QT_SCALAR_FIELD.h"
#include <QAction>
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 T,int d> QT_SCALAR_FIELD<T,d>::
QT_SCALAR_FIELD(QT_WORLD& world,const std::string& name,const std::string& grid_file,const std::shared_ptr<QT_OBJECT<TYPE>>& obj)
    :QT_GRID_COMPONENT<d>(world,name,grid_file),opt(OPT::def),raw_opt{{},{},{},OPT::def},obj(obj)
{
}
//#####################################################################
// Constructor
//#####################################################################
template<class T,int d> QT_SCALAR_FIELD<T,d>::
QT_SCALAR_FIELD(QT_WORLD& world,const std::string& name,const std::string& file,const std::string& grid_file)
    :QT_SCALAR_FIELD(world,name,grid_file,QT_FILE_OBJECT<TYPE>::Make(world.viewer_dir,file))
{
}
//#####################################################################
// Destructor
//#####################################################################
template<class T,int d> QT_SCALAR_FIELD<T,d>::
~QT_SCALAR_FIELD()
{
    Invalidate_Cache();
}
//#####################################################################
// Function Get_Color
//#####################################################################
template<class T,int d> auto QT_SCALAR_FIELD<T,d>::
Get_Color(T value) const -> std::pair<COLOR,bool>
{
    COLOR c;
    if constexpr(std::is_same<T,bool>::value)
        return {GET(color),value};
    else if constexpr(std::is_same<T,int>::value)
    {
        if(value<0 || value>GET(color_map).m) return {{},false};
        return {(GET(color_map))(value),true};
    }
    else return {{COLOR((GET(color_map))((value-GET(min))/(GET(max)-GET(min))))},true};
}
//#####################################################################
// Function Render
//#####################################################################
template<class T,int d> void QT_SCALAR_FIELD<T,d>::
Render(QT_RENDER_UTILS& ru)
{
    if(!GET(draw)) return;
    Read();
    if(!obj->valid) return;
    auto& a=obj->Obj();
    bool draw_smooth_field=false;
    if constexpr(is_f) draw_smooth_field=GET(draw_smooth_field);
    if(!ru.select && (GET(draw_cell_colors) || GET(draw_points) || draw_smooth_field))
    {
        bool cell_centered=GET(cell_centered);
        ARRAY<TV> pts;
        ARRAY<COLOR> colors;
        for(RANGE_ITERATOR<d> it(a.domain);it.Valid();it.Next())
        {
            auto x=a(it.index);
            auto c=Get_Color(x);
            if(!c.second) continue;
            TV X(cell_centered?grid.Cell_X(it.index):grid.Node_X(it.index));
            pts.Append(X);
            colors.Append(c.first);
        }
        if(GET(draw_points))
        {
            auto old=ru.Push(ru.depth_offset,ru.depth_offset-ru.depth_offset_unit*5);
            ru.Render_Points<TV>(pts,colors);
        }
        if(GET(draw_cell_colors))
        {
            auto old=ru.Push(ru.depth_offset,ru.depth_offset+ru.depth_offset_unit);
            ru.Render_Boxes<TV>(pts,grid.dX,colors);
        }
        if constexpr(is_f)
            if(GET(draw_smooth_field))
            {
                if constexpr(d==2 && is_f)
                {
                    auto size=a.domain.Edge_Lengths();
                    TRIANGULATED_AREA<float> ta;
                    ta.mesh.Initialize_Square_Mesh(size.x,size.y);
                    ta.particles.Add_Elements(size.Product());
                    ta.particles.X=pts;
                    ru.Render_Mesh<TV>(ta,colors);
                }
            }
    }
    if constexpr(is_f)
    {
        if(GET(draw_contours))
        {
            auto num_contours=GET(num_contours);
            auto min=GET(min);
            auto max=GET(max);
            ru.pick.Push(0);
            ru.pick.Push(0);
            for(int i=0;i<num_contours;i++)
            {
                ru.pick.Set(i);
                Render_Isocontour(ru,i,(max-min)/(num_contours+1)*(i+1)+min);
            }
            ru.pick.Pop();
            ru.pick.Pop();

            if(selected_isocontour>=0)
            {
                T value=(max-min)/(num_contours+1)*(selected_isocontour+1)+min;
                LOG::fprintf(ru.info_text,"isocountour %P %P\n",selected_isocontour,value);
            }
        }
    }
}
//#####################################################################
// Function Render_Isocontour
//#####################################################################
template<class T,int d> void QT_SCALAR_FIELD<T,d>::
Render_Isocontour(QT_RENDER_UTILS& ru,int countour_index,T value) const
{
    if constexpr(is_f)
    {
        if(!obj->valid) return;
        auto& a=obj->Obj();
        if(cached_mesh.m<=countour_index) cached_mesh.Resize(countour_index+1);
        auto& p=cached_mesh(countour_index);
        if(!p.first || p.second!=value)
        {
            delete p.first;
            p={new MESH_OBJECT<VECTOR<T,d>,d-1>,value};
            if(GET(cell_centered))
                MARCHING_CUBES<VECTOR<T,d>>::Create_Surface(*p.first,grid,at_cells,a,value);
            else
                MARCHING_CUBES<VECTOR<T,d>>::Create_Surface(*p.first,grid,at_nodes,a,value);
        }
        auto col=Get_Color(value).first;
        if(!ru.select && selected_isocontour==countour_index)
            col=ru.highlight_color;
        ru.Render_Mesh(*p.first,col);
    }
}
//#####################################################################
// Function Bounding_Box
//#####################################################################
template<class T,int d> auto QT_SCALAR_FIELD<T,d>::
Bounding_Box() -> RANGE<FV>
{
    if(!GET(draw)) return RANGE<FV>::Empty_Box();
    Read();
    if(!obj->valid) return RANGE<FV>::Empty_Box();
    return this->Bounding_Box_Helper(obj->Obj().domain);
}
//#####################################################################
// Function Print_Selection_Info
//#####################################################################
template<class T,int d> void QT_SCALAR_FIELD<T,d>::
Print_Selection_Info(std::ostream& o,const IV& index,bool cell) const
{
    if(cell!=GET(cell_centered) || !obj->valid) return;
    auto& a=obj->Obj();
    if(!a.domain.Lazy_Inside_Half_Open(index))
    {
        LOG::fprintf(o,"%P: (invalid index)\n",this->name);
        return;
    }
    LOG::fprintf(o,"%P: %P\n",this->name,a(index));
}
//#####################################################################
// Function Read
//#####################################################################
template<class T,int d> void QT_SCALAR_FIELD<T,d>::
Read()
{
    this->ptr->Read();
    obj->Read();
}
//#####################################################################
// Function Invalidate
//#####################################################################
template<class T,int d> void QT_SCALAR_FIELD<T,d>::
Invalidate(bool invalidate_common)
{
    obj->Invalidate(invalidate_common);
    if(obj->stale) Invalidate_Cache();
}
//#####################################################################
// Function Set_Range_From_Data
//#####################################################################
template<class T,int d> void QT_SCALAR_FIELD<T,d>::
Set_Range_From_Data()
{
    Read();
    if(!obj->valid) return;
    if constexpr(is_f)
    {
        auto& a=obj->Obj();
        if(a.array.m)
        {
            SET(min,a.array.Min());
            SET(max,a.array.Max());
        }
        else
        {
            SET(min,0);
            SET(max,1);
        }
    }
}
//#####################################################################
// Function Invalidate_Cache
//#####################################################################
template<class T,int d> void QT_SCALAR_FIELD<T,d>::
Invalidate_Cache() const
{
    for(auto&p:cached_mesh) delete p.first;
    cached_mesh.Remove_All();
}
//#####################################################################
// Function Set_Selection
//#####################################################################
template<class T,int d> void QT_SCALAR_FIELD<T,d>::
Set_Selection(ARRAY_VIEW<int> selection)
{
    PHYSBAM_ASSERT(selection.m>=2);
    if(selection(0)==0) selected_isocontour=selection(1);
    else PHYSBAM_FATAL_ERROR();
}
//#####################################################################
// Function Clear_Selection
//#####################################################################
template<class T,int d> void QT_SCALAR_FIELD<T,d>::
Clear_Selection()
{
    selected_isocontour=-1;
}
//#####################################################################
// Function Make_Dialog
//#####################################################################
template<class T,int d> void QT_SCALAR_FIELD<T,d>::
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_cell_colors",opt.draw_cell_colors,raw_opt[0].draw_cell_colors);
    dialog->Add("draw_points",opt.draw_points,raw_opt[0].draw_points);
    dialog->Add("cell_centered",opt.cell_centered,raw_opt[0].cell_centered);
    if constexpr(is_same<T,bool>::value)
        dialog->Add("color",opt.color,raw_opt[0].color);
    else dialog->Add("color_map",opt.color_map,raw_opt[0].color_map);
    if constexpr(is_f)
    {
    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("draw as smooth scalar field",opt.draw_smooth_field,raw_opt[0].draw_smooth_field);
    dialog->Add("draw_contours",opt.draw_contours,raw_opt[0].draw_contours);
    dialog->Add("num_contours",opt.num_contours,raw_opt[0].num_contours);
    }
    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 T,int d> void QT_SCALAR_FIELD<T,d>::
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 T,int d> void QT_SCALAR_FIELD<T,d>::
Set_Draw(bool draw)
{
    SET(draw,draw);
    action->setChecked(Draw());
}
//#####################################################################
// Function Draw
//#####################################################################
template<class T,int d> bool QT_SCALAR_FIELD<T,d>::
Draw() const
{
    return GET(draw);
}
//#####################################################################
// Function Save_Settings
//#####################################################################
template<class T,int d> void QT_SCALAR_FIELD<T,d>::
Save_Settings(std::ostream& out)
{
    raw_opt[0].Write(out,name);
}
//#####################################################################
// Function Clear_Settings
//#####################################################################
template<class T,int d> void QT_SCALAR_FIELD<T,d>::
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_SCALAR_FIELD<int,2>;
template class QT_SCALAR_FIELD<int,3>;
template class QT_SCALAR_FIELD<bool,2>;
template class QT_SCALAR_FIELD<bool,3>;
template class QT_SCALAR_FIELD<float,2>;
template class QT_SCALAR_FIELD<float,3>;
template class QT_SCALAR_FIELD<double,2>;
template class QT_SCALAR_FIELD<double,3>;
}
