// -*- Mode : c++ -*-
//
// SUMMARY  : 
// USAGE    :        
// ORG      : 
// AUTHOR   : Cedric Ody (not an expert in c++)
// E-MAIL   : cedric.listes@gmail.com
// from the work of  Sala Lorenzo (Dxwriter)

#include "mode_open.hpp"
#include <iostream>
#include <cfloat>
#include <cmath>
#include <iterator>
using namespace std;
#include "ff++.hpp"
//using namespace Fem2D;
#include <set>
#include <vector>
//#include "msh3.hpp"

class VtkWriter 
{
 struct tsinfo
 {
  int imesh;//!<index of the mesh
  std::string name;
  std::vector<double> vecistant;
 };
 
private:
 std::vector<Mesh3*> _vecmesh;
 //std::vector<tsinfo> _vecofts;
 std::string _nameoffile;

 /*! This string contains the name of data file with \\ where there's a \ in the path*/
 std::string _nameofdatafile;

 //!files containing the data and the timeseries
 std::ofstream _ofdata;

public:
 VtkWriter() { std::cout << "Constructor of VtkWriter" << endl;  }
 void openfiles(const std::string& s)
  {
   _nameoffile=s;
   std::string tmp=s+".vtu";
   std::cout<<tmp<<" ";
   _ofdata.open(tmp.c_str(), std::ios_base::out);
   _nameofdatafile="";
   for(int i=0;i<tmp.length();++i)
    {
     if(tmp.at(i)=='\\')
      _nameofdatafile.append(1,'\\');
     _nameofdatafile.append(1,tmp.at(i));
    }
  }

 void addmesh(Mesh3* mesh)
  {
   Mesh3& Th(*mesh);
   _vecmesh.push_back(mesh);
   _ofdata.flags(std::ios_base::scientific);
   _ofdata.precision(15);

   _ofdata << "<?xml version=\"1.0\"?>" << std::endl;
   _ofdata << "<VTKFile type=\"UnstructuredGrid\" version=\"0.1\" byte_order=\"LittleEndian\">" << std::endl;
   _ofdata << "<UnstructuredGrid>" << std::endl;
   _ofdata << "<Piece NumberOfPoints=\"" << Th.nv << "\" NumberOfCells=\"" << Th.nt << "\">" << std::endl;
   _ofdata << "<Points>" << std::endl;
   _ofdata << "<DataArray type=\"Float32\" Name=\"Position\" NumberOfComponents=\"3\" format=\"ascii\">" << std::endl;
   for(int k=0;k<Th.nv;++k) _ofdata << Th(k).x<<" "<<Th(k).y<< " "<<Th(k).z<<std::endl;
   _ofdata << "</DataArray>" << std::endl;
   _ofdata << "</Points>" << std::endl;
   _ofdata << "<Cells>" << std::endl;
   _ofdata << "<DataArray type=\"Int32\" Name=\"connectivity\" NumberOfComponents=\"1\" format=\"ascii\">" << std::endl;
   for(int i=0;i<Th.nt;++i)
    for (int j=0; j <4; j++) _ofdata << Th(i,j) << " " ;
   _ofdata << std::endl;
   _ofdata << "</DataArray>" << std::endl;
   _ofdata << "<DataArray type=\"Int32\" Name=\"offsets\" NumberOfComponents=\"1\" format=\"ascii\">" << std::endl;	
   for(int i=0;i<Th.nt;++i)  _ofdata << 4+4*(i) << " ";
   _ofdata << std::endl;
   _ofdata << "</DataArray>" << std::endl;
   _ofdata << "<DataArray type=\"UInt8\" Name=\"types\" NumberOfComponents=\"1\" format=\"ascii\">" << std::endl;	
   for(int i=0;i<Th.nt;++i)  _ofdata << 10 << " ";
   _ofdata << std::endl;
   _ofdata << "</DataArray>" << std::endl;
   _ofdata << "</Cells>" << std::endl; 
   _ofdata << "<PointData >" << endl; 
  }
 
 /*!Add a field*/
 void addscalar(const string& nameoffield, Mesh3* mesh, const KN<double>&val)
  {
   _ofdata.flags(std::ios_base::scientific);
   _ofdata.precision(15);

   _ofdata << "<DataArray type=\"Float32\" Name=\""<<nameoffield<<"\" NumberOfComponents=\"1\" format=\"ascii\">" << std::endl;
   for(int i=0;i<val.size();++i) _ofdata<<checkprecision(val[i])<<std::endl;
   _ofdata << "</DataArray>" << std::endl;
   
   _ofdata.flush();
  }
 
 double checkprecision(double val)
  {
   double tmp;
   if ( val >= 0. ) tmp=max(0.,val);
   if ( val <  0. ) tmp=min(0.,val);
   return tmp;
  }

  /*!Add a field*/
 void addvector(const string& nameoffield, Mesh3* mesh, const KN<double>&val,
                const KN<double>&val2,const KN<double>&val3 )
  {
   _ofdata.flags(std::ios_base::scientific);
   _ofdata.precision(15);

   _ofdata << "<DataArray type=\"Float32\" Name=\""<<nameoffield<<"\" NumberOfComponents=\"3\" format=\"ascii\">" << std::endl;
   for(int i=0;i<val.size();++i) _ofdata<<checkprecision(val[i])<< " " << checkprecision(val2[i]) << " " << checkprecision(val3[i]) << std::endl;
   _ofdata << "</DataArray>" << std::endl;
   _ofdata.flush();
  }


 /*!Get the mesh associated with the series nameofts*/
 Mesh3* getmeshts(const string& nameofts)
  {
   return _vecmesh[0];
  }
 
 void init()
  {
   new(this)VtkWriter(); 
  }
 
 void destroy() 
  {
   if(_ofdata.is_open())
    {
     _ofdata << "</PointData>" << endl;	
     _ofdata << "<CellData>" << endl; 	
     _ofdata << "</CellData>" << endl; 
     _ofdata << "</Piece>" << endl;
     _ofdata << "</UnstructuredGrid>" << endl;
     _ofdata << "</VTKFile>" << endl;
     _ofdata.close(); 		
    }
  } 
}; //End of class

class Vtkwritesol_Op : public E_F0mps 
{
public:
 typedef long  Result;
 Expression edx;
 Expression ename;//!<name of time series or field
 Expression et;//!<time
 long what; // 1 scalar, 2 vector, 3 symtensor
 long nbfloat; // 1 scalar, n vector (3D), n symtensor(3D)
 Expression evct,evct2,evct3;
 
public:
 Vtkwritesol_Op(const basicAC_F0 &  args) :  what(0), nbfloat(0)
  {
   evct=0;
   evct2=0;
   evct3=0;
   int nbofsol;
   int ddim=3;
   //There's no named parameter
   args.SetNameParam();
   if(args.size()!=3)
    {
     CompileError("Vtkwritesol accepts only 4 parameters");
    }
   if (BCastTo<VtkWriter *>(args[0])) edx = CastTo<VtkWriter *>(args[0]);
   if (BCastTo<string *>(args[1])) ename = CastTo<string *>(args[1]);
   
   if ( args[2].left()==atype<double>() )
    {
     what=1;
     nbfloat=1;
     evct=to<double>( args[2] );
    }
   else if ( args[2].left()==atype<double *>() )
    {
     what=1;
     nbfloat=1;
     evct=to<double>( args[2] );
    }
   else if ( BCastTo<pfer>(args[2]) )
    {
     what=1;
     nbfloat=1;
     evct=to<double>( args[2] );
    }
   else if ( args[2].left()==atype<E_Array>() )
    {
     std::cout << "Until now only scalar solution" << std::endl;
     
     int i=2;
     const E_Array * a0  = dynamic_cast<const E_Array *>( args[i].LeftValue() );
       

      if( a0->size() == 1){
      // scalar solution
      what=1;
      nbfloat=a0->size();
      evct = to<double>( (*a0)[0]);
      
     }
  
     if( a0->size() == ddim){
      // vector solution
      what=2;
      nbfloat=a0->size();
      evct = to<double>( (*a0)[0]);
      evct2 = to<double>( (*a0)[1]);
      evct3 = to<double>( (*a0)[2]);
      
     }
      cout << "Passed Until now only scalar solution" << std::endl;
    }
   else 
    {
     CompileError("savesol in 2D: Sorry no way to save this kind of data");
    }
   
  }
 static ArrayOfaType  typeargs() { return  ArrayOfaType(atype<VtkWriter *>(), atype<string *>(), true); }// all type
 static  E_F0 * f(const basicAC_F0 & args) { return new Vtkwritesol_Op(args);} 
 AnyType operator()(Stack stack)  const ;
}; // end of class


AnyType Vtkwritesol_Op::operator()(Stack stack)  const 
{ 
 MeshPoint *mp(MeshPointStack(stack)) , mps=*mp;
 VtkWriter &dx=*(GetAny<VtkWriter *>((*edx)(stack)));
 string &name=*(GetAny<string *>((*ename)(stack)));
 //double t=GetAny<double>((*et)(stack));
 Mesh3 &Th=*(dx.getmeshts(name));
 
 int nt = Th.nt;
 int nv = Th.nv;
 
 int nbsol=nv;
 long longdefault;
 
 KN<double> valsol(nbsol);
 valsol=0.;
 KN<int> takemesh(nbsol);
 takemesh=0;
 MeshPoint *mp3(MeshPointStack(stack));
 for (int it=0;it<nt;it++)
  {
  for(int iv=0;iv<4;iv++)
   {
    int i=Th(it,iv);
    mp3->setP(&Th,it,iv);					
    valsol[i] = valsol[i] + GetAny< double >((*evct)(stack));			
    ++takemesh[i];
   }
  }
 for(int i=0; i<nbsol; i++)
  {
   valsol[i] /= takemesh[i]; 
  }
 
 //Writes valsol on the file file
 if (what==1) 
   dx.addscalar(name,&Th,valsol);

 if (what == 2)
  {
   KN<double> valsol2(nbsol);
   valsol2=0.;
   KN<int> takemesh(nbsol);
   takemesh=0;
   MeshPoint *mp3(MeshPointStack(stack));
   for (int it=0;it<nt;it++)
    {
     for(int iv=0;iv<4;iv++)
      {
       int i=Th(it,iv);
       mp3->setP(&Th,it,iv);					
       valsol2[i] = valsol2[i] + GetAny< double >((*evct2)(stack));			
       ++takemesh[i];
      }
    }
   for(int i=0; i<nbsol; i++)
    {
     valsol2[i] /= takemesh[i]; 
    }
   
   {
    KN<double> valsol3(nbsol);
    valsol3=0.;
    KN<int> takemesh(nbsol);
    takemesh=0;
    MeshPoint *mp3(MeshPointStack(stack));
    for (int it=0;it<nt;it++)
     {
      for(int iv=0;iv<4;iv++)
       {
        int i=Th(it,iv);
        mp3->setP(&Th,it,iv);					
        valsol3[i] = valsol3[i] + GetAny< double >((*evct3)(stack));			
        ++takemesh[i];
       }
     }
    for(int i=0; i<nbsol; i++)
     {
      valsol3[i] /= takemesh[i]; 
     }

   //Writes valsol on the file file
   dx.addvector(name,&Th,valsol,valsol2,valsol3);

      }
   
  }

 return longdefault;

}



// le vrai constructeur est la
VtkWriter* init_VtkWriter(VtkWriter * const &a, string * const & s)
{
 std::cout << "start init_VtkWriter" << std::endl;
 a->init();
 a->openfiles(*s);
 std::cout << "end init_VtkWriter" << std::endl;
 return a;
} 

void* call_addmesh( VtkWriter * const & mt, Mesh3* const & pTh) {
  mt->addmesh(pTh);
  return NULL;
}
 
//   Add the function name to the freefem++ table 
class Init 
{ 
public:
 Init();
};

LOADINIT(Init);
Init::Init()
{
 
 Dcl_Type<VtkWriter*>(InitP<VtkWriter>,Destroy<VtkWriter>); // declare deux nouveau type pour freefem++  un pointeur et 
 
 zzzfff->Add("VtkWriter",atype<VtkWriter*>()); // ajoute le type myType a freefem++ 
 // constructeur  d'un type myType  dans freefem 
 TheOperators->Add("<-", new OneOperator2_<VtkWriter*, VtkWriter* ,string*>(&init_VtkWriter));
 Global.Add("Vtkaddmesh","(",new OneOperator2_<void *, VtkWriter*, Mesh3*>(call_addmesh)); 
 Global.Add("Vtkaddscalar","(",new OneOperatorCode< Vtkwritesol_Op> );
 
}
