//#####################################################################
// Copyright 2012.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################
// Class VIEWER_OUTPUT
//#####################################################################
#ifndef __VIEWER_OUTPUT__
#define __VIEWER_OUTPUT__

#include <Core/Meshes/MESHES_FORWARD.h>
#include <Core/Read_Write/STREAM_TYPE.h>
#include <Core/Utilities/VIEWER_DIR.h>
#include <Core/Vectors/VECTOR.h>
#include <Core/Viewer/VIEWER_OPTIONS.h>
#include <functional>
namespace PhysBAM{

template<class TV> class GRID;
template<class TV> class DEBUG_PARTICLES;
template<class TV> class PARTICLES;
template<class TV> class STRUCTURE;
template<int d> class FACE_INDEX;

class VIEWER_OUTPUT
{
public:
    VIEWER_DIR& viewer_dir;
    STREAM_TYPE stream_type;
    bool started_manifest=false;
    template<class T> using F=std::conditional_t<is_same<T,int>::value||is_same<T,bool>::value,T,float>;
    template<class T> using SV=std::enable_if_t<is_same<T,int>::value||is_same<T,bool>::value||IS_SCALAR<T>::value>;
    
private:
    ARRAY<std::function<void()> > entries[2];
public:

    VIEWER_OUTPUT(STREAM_TYPE stream_type,VIEWER_DIR& viewer_dir);
    ~VIEWER_OUTPUT();

    static VIEWER_OUTPUT* Singleton(VIEWER_OUTPUT* viewer_output=0);

    void Flush_Frame(const std::string& title,int substep_level=0);
    void Write_Substep(const std::string& title,int level);
    void Dump_Stored();

    // Write the data yourself
    void Add_Global(const GLOBAL_OPTIONS& opt);
    template<class TV>
    void Add_Grid(const std::string& file,
        const GRID_OPTIONS& opt);
    template<int d>
    void Add_Axes(const std::string& name,const AXES_OPTIONS& opt);
    
    template<class TV> 
    void Add_Vector_Field(const std::string& file,
        const std::string& grid_name,
        const VECTOR_FIELD_OPTIONS& opt);
    template<class TV,class T2>
    void Add_Scalar_Field(const std::string& file,
        const std::string& grid_name,
        const SCALAR_FIELD_OPTIONS<F<T2>>& opt);
    template<class TV,class T2>
    void Add_Face_Field(const std::string& file,
        const std::string& grid_name,
        const FACE_FIELD_OPTIONS<F<T2>>& opt);

    template<class TV,int d>
    void Add_Mesh(const std::string& file,
        const MESH_OPTIONS<d+0>& opt);

    template<class TV>
    void Add_Debug_Particles(const std::string& file,
        const DEBUG_PARTICLES_OPTIONS& opt);
    template<class TV>
    void Add_Particles(const std::string& file,
        const PARTICLES_OPTIONS& opt);
    void Add_Group(const std::string& name,const GROUP_OPTIONS& opt);

    // Data written automatically
    template<class TV>
    void Add(const std::string& file,
        const GRID<TV>& obj,bool is_common,
        const GRID_OPTIONS& opt);
    template<class TV>
    void Add(const std::string& file,
        DEBUG_PARTICLES<TV>& obj,bool is_common,
        const DEBUG_PARTICLES_OPTIONS& opt);
    template<class T2,int d>
    void Add(const std::string& file,const std::string& grid_name,
        const ARRAY<T2,FACE_INDEX<d> >& obj,bool is_common,
        const FACE_FIELD_OPTIONS<F<T2>>& opt);
    template<class T2,int d>
    void Add_Scalar(const std::string& file,const std::string& grid_name,
        const ARRAY<T2,FACE_INDEX<d> >& obj,bool is_common,
        const FACE_FIELD_OPTIONS<F<T2>>& opt);
    template<class T,int d>
    void Add(const std::string& file,const std::string& grid_name,
        const ARRAY<VECTOR<T,d>,VECTOR<int,d>>& obj,bool is_common,
        const VECTOR_FIELD_OPTIONS& opt);
    template<class T,int d> SV<T>
    Add(const std::string& file,const std::string& grid_name,
        const ARRAY<T,VECTOR<int,d>>& obj,bool is_common,
        const SCALAR_FIELD_OPTIONS<F<T>>& opt);
    template<class T,int d>
    void Add(const std::string& file,const std::string& grid_name,
        const ARRAY_VIEW<VECTOR<T,d>,VECTOR<int,d>>& obj,bool is_common,
        const VECTOR_FIELD_OPTIONS& opt);
    template<class T,int d> SV<T>
    Add(const std::string& file,const std::string& grid_name,
        const ARRAY_VIEW<T,VECTOR<int,d>>& obj,bool is_common,
        const SCALAR_FIELD_OPTIONS<F<T>>& opt);
    template<class TV>
    void Add(const std::string& file,
        const PARTICLES<TV>& obj,bool is_common,
        const PARTICLES_OPTIONS& opt);
    template<class TV,int d>
    void Add(const std::string& file,
        const MESH_OBJECT<TV,d>& obj,bool is_common,
        const MESH_OPTIONS<d+0>& opt);
    template<class T,int d>
    void Add_Div(const std::string& name,const std::string& data_name,
        const SCALAR_FIELD_OPTIONS<float>& opt);

    // Use these for split dumping of meshes.
    template<class TV,int d>
    void Add_Split_Mesh(const std::string& name,const std::string& mesh_file,
        const std::string& particle_file,const MESH_OPTIONS<d+0>& opt);

    template<class T,int d> void
    Add_Levelset(const std::string& file,const std::string& grid_name,
        const ARRAY<T,VECTOR<int,d>>& obj,bool is_common,
        const SCALAR_FIELD_OPTIONS<F<T>>& opt,const VECTOR<T,3>& color);
    
    // Custom writing
    void Add(std::function<void()> f,bool is_common);

    template<class OBJ>
    void Write(const std::string& file,const OBJ& obj,bool is_common)
    {
        entries[is_common].Append([this,file,&obj,is_common]()
            {Write_To_File(stream_type,is_common?viewer_dir.Common_File(file):viewer_dir.Frame_File(file),obj);});
    }
};
inline void Flush_Frame(const char* title="",int substep_level=0)
{VIEWER_OUTPUT::Singleton()->Flush_Frame(title,substep_level);}
}
#endif
