/*
 * dxexport2 -- for use with alberta2
 *  creates a netcdf file for the current mesh
 *  to be used in OpenDX 
 */

// This is part of ALBERTA C++ wrappers
// link with libnetcdf_c++ and libnetcdf

#include "netcdfcpp.h"
#include "alberta.h"
#include "dxexport.hh" // pick up extern "C" if necessary

static const FE_SPACE *fe_space=nil;

// output needs floats
typedef float FLOAT;

//man kann vielleicht mal alle n_ auf grid direkt zurueckfuehren und Variablennamen sparen...
typedef struct dxexport_data DXEXPORT_DATA;
struct dxexport_data
{
  const int n_vertices; //ist eigentlich alles im Mesh und wird nur im Destruktor gebraucht...
  const int n_elements;
  const int mesh_dim; // eigentlich DIM_OF_WORLD noetig
  const int n_values;
  // bit flags for cell-centered data
  const unsigned long int cflags;

  FLOAT *coords;               /* Length will be n_vertices*dim            */ 

  int *vertices /*[N_VERTICES(DIM)*/;  /* vertices[i][j]: global index of      */
                                /* jth vertex of element i              */ 

  FLOAT **values;                /* Contains n_values ptrs               */

  ~dxexport_data();
   dxexport_data(int nv, int ne, int md, int nd, unsigned long int cflags, 
		 FLAGS flag);
};



dxexport_data::~dxexport_data()
{
  delete [] coords;
  delete [] vertices;

  for(int i=0;i<n_values; ++i) 
    delete[] values[i];
	delete [] values;
  return;
}


dxexport_data::dxexport_data(int nv, int ne, int md, int nd, unsigned long int cflags, FLAGS flag)
:  n_vertices(nv), n_elements(ne), mesh_dim(md), n_values(nd), cflags(cflags)
{
  FUNCNAME("dxexport_data_constructor");
//  values = MEM_CALLOC(nd, FLOAT*);
    values = new FLOAT*[nd];
  //  FLOAT **cvaluelist = MEM_CALLOC(ncd, FLOAT*);
//  coords     = MEM_ALLOC(nv*mesh_dim, FLOAT);
//  vertices   = MEM_ALLOC(ne*N_VERTICES(mesh_dim), int);
    // eckige Klammern hilft! EP
   coords     = new FLOAT[nv*mesh_dim];
  vertices   = new int[ne*N_VERTICES(mesh_dim)];
 
  for(int i=0; i<nd; ++i)
//    values[i] = MEM_ALLOC( (cflags&(1<<i))?ne:nv, FLOAT);
    values[i] = new FLOAT[ (cflags&(1<<i))?ne:nv];

}

void init_export_dof_admin(MESH *mesh)
{
  FUNCNAME("init_export_dof_admin");
  const BAS_FCTS  *lagrange;
  lagrange = get_lagrange(mesh->dim, 1);
  TEST_EXIT(lagrange, "no lagrange BAS_FCTS\n");
  fe_space = get_fe_space(mesh, lagrange->name, nil, lagrange, 0U);
  return;
}

//basiert auf static MACRO_DATA *mesh2macro_data(MESH *mesh)
static int mesh2dxexport_data(MESH *mesh,  DXEXPORT_DATA *data,
                      const DOF_REAL_VEC* const * const vecs, int n_vecs)
{
  FUNCNAME("mesh2dxexport_data");
  TRAVERSE_STACK   *stack;
  static const DOF *(*get_dof)(const EL *, const DOF_ADMIN *, DOF *);
  const EL_INFO    *el_info;
  int             /* n0,*/ ne, nv, i, j, *vert_ind = nil;
  const int        vert_max = N_VERTICES(mesh->dim);
  const REAL*      uh_loc;
  U_CHAR           new_vertex;
  
 // n0 = fe_space->admin->n0_dof[VERTEX];

  static const DOF_INT_VEC* dof_vert_ind = 
    get_dof_int_vec("vertex indices", fe_space);
  
  GET_DOF_VEC(vert_ind, dof_vert_ind);
  FOR_ALL_DOFS(fe_space->admin, vert_ind[dof] = -1);
  
  get_dof    = fe_space->bas_fcts->get_dof_indices;

  nv = ne = 0;

  stack = get_traverse_stack();
/*--------------------------------------------------------------------------*/
/* The first pass counts elements and vertices, checks these against the    */
/* entries of mesh->n_elements, mesh->n_vertices, and fills data->coords.   */
/* A check on whether an element has nonzero el_type is also done.          */
/*--------------------------------------------------------------------------*/
  for(el_info = traverse_first(stack, mesh, -1, CALL_LEAF_EL | FILL_COORDS);
      el_info;
      el_info = traverse_next(stack, el_info)) 
    {
      const DOF *dof = (*get_dof)(el_info->el, fe_space->admin, nil);
      //      const DOF *dof = GET_DOF_INDICES(fe_space->bas_fcts, el_info->el,
      //					fe_space->admin,nil);
      new_vertex = false;
      for (i = 0; i < vert_max; i++) 
	{
	  //	      if (vert_ind[el_info->el->dof[i][n0]] == -1) 
	  if (vert_ind[dof[i]] == -1) 
	    {
/*--------------------------------------------------------------------------*/
/* assign a global index to each vertex                                     */
/*--------------------------------------------------------------------------*/
	      data->vertices[ne*vert_max+i] =  vert_ind[dof[i]]=  nv;    
	      new_vertex = true;
	      
	      for (j = 0; j < DIM_OF_WORLD; j++) 
		data->coords[nv*DIM_OF_WORLD+j] = (FLOAT) el_info->coord[i][j];
	      
	      nv++;
	
	      if(nv > mesh->n_vertices) 
		{
		  free_traverse_stack(stack);
		  ERROR("mesh %s: n_vertices (==%d) is too small! "
			"Writing aborted\n",
			mesh->name, mesh->n_vertices);
		  return(1);
		}
	    }
	  else      
	    {
	      data->vertices[ne*vert_max+i] =  vert_ind[dof[i]];
	    }  
	} 
      
      for (int comp=0; comp<n_vecs; ++comp)
	{
	  // cell-centered data: fr jedes Element aufrufen 
	  if (data->cflags & (1<<comp))
	    {
	      uh_loc = 
		vecs[comp]->fe_space->bas_fcts->get_real_vec(el_info->el, 
							 vecs[comp], nil);
	      data->values[comp][ne] = (FLOAT) uh_loc[0];
	    }
	  // position-dependent data: nur falls neue Ecken aufgetaucht sind
	  else if(new_vertex) for (i = 0; i < N_VERTICES(DIM); i++) 
	    {
	      uh_loc = 
		vecs[comp]->fe_space->bas_fcts->get_real_vec(el_info->el, 
							 vecs[comp], nil);
	      data->values[comp][data->vertices[ne*vert_max+i]] 
		= (FLOAT)uh_loc[i];
	    }
	}
      ne++;
      
      if(ne > mesh->n_elements) 
	{
	  free_traverse_stack(stack);
	  ERROR("mesh %s: n_elements (==%d) is too small! Writing aborted\n", 
		mesh->name, mesh->n_elements);
	  return(1);
	}
    }

  if(ne < mesh->n_elements) 
    {
      free_traverse_stack(stack);
      ERROR("mesh %s: n_elements (==%d) is too large: "
	    "only %d leaf elements counted -- writing aborted\n", 
	    mesh->name, mesh->n_elements, ne);
      return(1);
    }
  if(nv < mesh->n_vertices)
    {
      free_traverse_stack(stack);
      ERROR("mesh %s: n_vertices (==%d) is too large: "
	    "only %d vertices counted --  writing of mesh aborted\n",
	    mesh->name, mesh->n_vertices, nv);
      return(1);
    }

  free_traverse_stack(stack);

  return(0);
}


// filename ohne endung
int dxexport(MESH *mesh, const char *filename, REAL time, 
		   const DOF_REAL_VEC * const *vecs, int n_vecs)
  // ,const DOF_REAL_VEC* const * const deg0vecs, int n_deg0vecs)
{
  FUNCNAME("dxexport");
  char            key[128];
  DXEXPORT_DATA  *export_data;
  unsigned long int cflags=0;
  
  if (!mesh)
  {
    ERROR("no mesh - no file created\n");
    return 1;
  }

//  dof_compress(mesh); // kriegen das die vecs mit?
//hier Daten pruefen... (assumes intbits>=32)
  TEST_EXIT(n_vecs<32,"too many datavectors (>32).\n");

  for (int i=0; i<n_vecs; ++i)
    {
      if (!vecs[i])
	{
	  ERROR_EXIT("vecs[%d] is NULL.\n", i);
	} else {
// teste auf konstanten fespace
	  if(strcmp(vecs[i]->fe_space->name,"lagrange0")==0) cflags |= 1<<i;
	}
    }
  MSG("Cell Centered data flagbits set to %08lx.\n",cflags);
  

  export_data = new dxexport_data(mesh->n_vertices, 
				  mesh->n_elements, mesh->dim, 
				  n_vecs, cflags, 0);
  if(mesh2dxexport_data(mesh,export_data, vecs, n_vecs)) {delete export_data;return 2;}
 
  snprintf(key, 127, "%s.nc", filename);
  
  NcFile Nc(key, NcFile::Replace, NULL, 0, NcFile::Classic);
  if (!Nc.is_valid()) 
  {
    ERROR("cannot create netCDF file %s\n", filename);
    delete export_data;
    return 1;
  }
  /*** Create dimensions ***/
  const int dim = DIM;
  const int dim_of_world = DIM_OF_WORLD;
  const int simplex = dim+1;  // size of a simplex (triangle,tetrahedron)
  const int n_vertices = export_data->n_vertices;
  const int n_elements = export_data->n_elements;
   
//  NcDim* Dim = Nc.add_dim("dim", dim);
  NcDim* Dim_of_world = Nc.add_dim("dim_of_world", dim_of_world);
  NcDim* Simplex = Nc.add_dim("simplex", simplex);
  NcDim* N_vertices = Nc.add_dim("n_vertices", n_vertices);
  NcDim* N_elements = Nc.add_dim("n_elements", n_elements);
// NcDim* N_phases = Nc.add_dim("n_phases", n_phases); 
 
  /*** Create variables ***/
  NcVar* Values[n_vecs];

  for (int i=0; i<n_vecs; ++i)
  {
    sprintf(key, "value%02d",i);
    Values[i] = Nc.add_var(key, ncFloat, 
			   (cflags & (1<<i))? N_elements : N_vertices);
  // DX attributes -- mark as field element (main data structure for DX)
    Values[i]->add_att("field", vecs[i]->name);

#if DIM==1
    Values[i]->add_att("connections", "elements, lines");
#elif DIM==2
    Values[i]->add_att("connections", "elements, triangles");
#elif DIM==3
    Values[i]->add_att("connections", "elements, tetrahedra");
#endif
    Values[i]->add_att("positions", "coordinates");
  }   
  NcVar* Elements = Nc.add_var("elements", ncLong, N_elements, Simplex);
  NcVar* Coordinates = Nc.add_var("coordinates", ncFloat, N_vertices, Dim_of_world);
  NcVar* Time = Nc.add_var("time", ncDouble);
 
  /*** Global attributes ***/
  Nc.add_att("Creator", ALBERTA_VERSION);

  /*** Start writing data ***/
  Time->put(&time,1);
  Coordinates->put(&export_data->coords[0], n_vertices, dim_of_world); // Coordinates->edges());
  Elements->put(&export_data->vertices[0], n_elements, simplex); // Elements->edges());
  for(int i=0;i<n_vecs; ++i)
  {
    FLOAT *val = export_data->values[i];
    Values[i]->put(&val[0], (cflags & (1<<i))? n_elements :n_vertices ); 
  }
 delete export_data;
  return(0);
}
