bool abs(bool b){return b;}
#include <Core/Arrays/ARRAYS_ND.h>
#include <Core/Arrays/FACE_ARRAYS.h>
#include <Core/Geometry_Particles/DEBUG_PARTICLES.h>
#include <Core/Grids/FACE_INDEX.h>
#include <Core/Grids/GRID.h>
#include <Core/Meshes/MESH_OBJECT.h>
#include <Core/Meshes/SEGMENTED_CURVE.h>
#include <Core/Parsing/PARSE_ARGS.h>
#include <Core/Particles/PARTICLES.h>
#include <Core/Read_Write/FILE_UTILITIES.h>
#include <Core/Utilities/VIEWER_DIR.h>
#include <Core/Vectors/VECTOR.h>
#include <fstream>
#include <iostream>
#include <map>

using namespace PhysBAM;

template<class T>
void Diff(std::string name,ARRAY_VIEW<T> a,ARRAY_VIEW<T> b)
{
    if(a.m!=b.m)
        LOG::printf("FAIL %P incompatible arrays %P %P\n",name,a.m,b.m);
    else
        LOG::printf("%P %P\n",name,(a-b).Max_Abs());
}

template<class T,int d>
void Diff(std::string name,ARRAY_VIEW<VECTOR<T,d>> a,ARRAY_VIEW<VECTOR<T,d>> b)
{
    if(a.m!=b.m)
        LOG::printf("FAIL %P incompatible arrays %P %P\n",name,a.m,b.m);
    else
        Diff(name,a.Flattened(),b.Flattened());
}

template<class T,int d>
void Diff(std::string name,const ARRAY<T,VECTOR<int,d>>& a,const ARRAY<T,VECTOR<int,d>>& b)
{
    if(a.domain!=b.domain)
        LOG::printf("FAIL %P incompatible arrays %P %P\n",name,a.domain,b.domain);
    else
        Diff(name,a.array,b.array);
}

template<class T,int d>
void Diff(std::string name,const ARRAY<T,FACE_INDEX<d>>& a,const ARRAY<T,FACE_INDEX<d>>& b)
{
    if(a.domain!=b.domain)
        LOG::printf("FAIL %P incompatible arrays %P %P\n",name,a.domain,b.domain);
    else
        Diff(name,a.array,b.array);
}

template<class TV>
void Diff(std::string name,const PARTICLES<TV>& a,const PARTICLES<TV>& b)
{
    if(a.number!=b.number)
    {
        LOG::printf("FAIL %P different numbers of particles %P %P\n",name,a.number,b.number);
        return;
    }

    ATTRIBUTE_INDEX i(0),j(0);
    while(i<a.arrays.m && j<b.arrays.m)
    {
        if(a.arrays(i)->name<b.arrays(j)->name)
            LOG::printf("FAIL %P.%P missing for sim 1\n",name,a.arrays(i++)->name);
        else if(b.arrays(j)->name<a.arrays(i)->name)
            LOG::printf("FAIL %P.%P missing for sim 0\n",name,b.arrays(j++)->name);
        else
        {
            ARRAY_COLLECTION_ELEMENT_BASE* e[2]={a.arrays(i),b.arrays(j)};
            ARRAY<typename TV::SCALAR> ar[2];
            for(int k=0;k<2;k++)
            {
                std::stringstream ss;
                RAW_OSTREAM raw(ss);
                TYPED_OSTREAM typed_output(raw,STREAM_TYPE::type_text);
                e[k]->Write(typed_output);
                std::string s(ss.str());
                std::replace(s.begin(),s.end(),'(',' ');
                std::replace(s.begin(),s.end(),')',' ');
                std::replace(s.begin(),s.end(),'[',' ');
                std::replace(s.begin(),s.end(),']',' ');
                std::replace(s.begin(),s.end(),',',' ');
                std::replace(s.begin(),s.end(),';',' ');
                std::stringstream tt(s);
                typename TV::SCALAR x;
                while(tt>>x) ar[k].Append(x);
            }
            LOG::printf("%P %P\n",name+"."+a.arrays(i)->name,(ar[0]-ar[1]).Max_Abs());
            i++;
            j++;
        }
    }
}

template<class TV,int d>
void Diff(std::string name,const MESH_OBJECT<TV,d>& a,const MESH_OBJECT<TV,d>& b)
{
    if(a.mesh.elements!=b.mesh.elements) 
        LOG::printf("FAIL %P incompatible mesh\n",name);
    else if(a.particles.number!=b.particles.number)
        LOG::printf("FAIL %P incompatible numbers of particles %P %P\n",
            name,a.particles.number,b.particles.number);
    else Diff(name+"particles",a.particles,b.particles);
}

template<class OBJ>
void Compare(const VIEWER_DIR& vd0,const VIEWER_DIR& vd1,const ARRAY<std::string>& a)
{
    auto s0=vd0.Frame_File(a(2));
    auto s1=vd1.Frame_File(a(2));
    OBJ o0,o1;
    int status=0;
    try{Read_From_File(s0,o0);}catch(...){status|=1;}
    try{Read_From_File(s1,o1);}catch(...){status|=2;}
    if(status==0)
    {
        LOG::printf("");
        Diff(a(1),o0,o1);
    }
    else if(status==1) LOG::printf("FAIL missing %P\n",s0);
    else if(status==2) LOG::printf("FAIL missing %P\n",s0);
}

std::map<std::string,void(*)(const VIEWER_DIR& vd0,const VIEWER_DIR& vd1,const ARRAY<std::string>& tokens)> lookup;

#define MKX(name,suf,tmpl) lookup[name suf]=&Compare<tmpl>;
#define MKT(name,suf,tmpl)                              \
    lookup[name ".f" suf]=&Compare<tmpl<float>>;        \
    lookup[name ".d" suf]=&Compare<tmpl<double>>;
#define MKD(name,suf,tmpl)                      \
    lookup[name ".2" suf]=&Compare<tmpl<2>>;    \
    lookup[name ".3" suf]=&Compare<tmpl<3>>;    \
    lookup[name ".2.f" suf]=&Compare<tmpl<2>>;  \
    lookup[name ".3.f" suf]=&Compare<tmpl<3>>;  \
    lookup[name ".2.d" suf]=&Compare<tmpl<2>>;  \
    lookup[name ".3.d" suf]=&Compare<tmpl<3>>;
#define MKV(name,suf,tmpl)                                      \
    lookup[name ".2.f" suf]=&Compare<tmpl<VECTOR<float,2>>>;    \
    lookup[name ".3.f" suf]=&Compare<tmpl<VECTOR<float,3>>>;    \
    lookup[name ".2.d" suf]=&Compare<tmpl<VECTOR<double,2>>>;   \
    lookup[name ".3.d" suf]=&Compare<tmpl<VECTOR<double,3>>>;
#define MKW(name,suf,tmpl)                              \
    lookup[name ".2.f" suf]=&Compare<tmpl<float,2>>;    \
    lookup[name ".3.f" suf]=&Compare<tmpl<float,3>>;    \
    lookup[name ".2.d" suf]=&Compare<tmpl<double,2>>;   \
    lookup[name ".3.d" suf]=&Compare<tmpl<double,3>>;
#define MKB(name,suf,tmpl)                              \
    MKW(name,suf,tmpl)                                 \
    lookup[name ".2.b" suf]=&Compare<tmpl<bool,2>>;     \
    lookup[name ".3.b" suf]=&Compare<tmpl<bool,3>>;     \
    lookup[name ".2.i" suf]=&Compare<tmpl<int,2>>;      \
    lookup[name ".3.i" suf]=&Compare<tmpl<int,3>>;

template<class T,int d> using SCALAR_FIELD=ARRAY<T,VECTOR<int,d>>;
template<class TV> using VECTOR_FIELD=ARRAY<TV,VECTOR<int,TV::m>>;
template<class T,int d> using FACE_FIELD=ARRAY<T,FACE_INDEX<d>>;
template<int d> using GRID_=GRID<VECTOR<double,d>>;

int main(int argc,char *argv[])
{
    std::string dir0,dir1;
    PARSE_ARGS parse_args(argc,argv);
    parse_args.Extra(&dir0,"file","first simulation directory");
    parse_args.Extra(&dir1,"file","second simulation directory");
    parse_args.Parse();

//    MKV("debug",,DEBUG_PARTICLES);
    MKB("grid_scalar",,SCALAR_FIELD);
    MKV("grid_vector",,VECTOR_FIELD);
    MKB("grid_face",,FACE_FIELD);
    MKV("mesh.1",,SEGMENTED_CURVE);
    MKT("mesh.2.2",,TRIANGULATED_AREA);
    MKT("mesh.2.3",,TRIANGULATED_SURFACE);
    MKT("mesh.3.3",,TETRAHEDRALIZED_VOLUME);
    MKV("mesh.1",".sp",SEGMENTED_CURVE);
    MKT("mesh.2.2",".sp",TRIANGULATED_AREA);
    MKT("mesh.2.3",".sp",TRIANGULATED_SURFACE);
    MKT("mesh.3.3",".sp",TETRAHEDRALIZED_VOLUME);
    MKV("particles",,PARTICLES);
//    MKD("grid",,GRID_);
    
    VIEWER_DIR vd0(dir0),vd1(dir1);

    std::string manifest=vd0.Common_File("manifest.txt");
    std::ifstream m(manifest);
    std::string line;
    ARRAY<ARRAY<std::string>> object_list;
    while(getline(m,line))
    {
        std::stringstream ss(line);
        ARRAY<std::string> tokens;
        std::string token;
        while(ss>>std::quoted(token)) tokens.Append(token);
        if(!lookup.contains(tokens(0))) continue;
        object_list.Append(tokens);
    }
    vd0.Set(0);
    vd1.Set(0);
    
    while(1)
    {
        bool has0=vd0.Find_Next_Directory(0,true);
        bool has1=vd1.Find_Next_Directory(0,true);
        if(!has0 && !has0) break;
        if(!has0)
        {
            LOG::printf("FAIL first sim missing frame %P\n",vd1.frame_stack(0));
            break;
        }
        if(!has1)
        {
            LOG::printf("FAIL second sim missing frame %P\n",vd0.frame_stack(0));
            break;
        }
        LOG::printf("FRAME %P\n",vd0.frame_stack(0));
        for(auto& a:object_list)
        {
            if(a.Size()<3) continue;
            auto f=lookup[a(0)];
            f(vd0,vd1,a);
        }
    }

    return 0;
}
