/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/* file: write_mesh_gmv.c                                                   */
/*                                                                          */
/*                                                                          */
/* description: This program converts ALBERTA meshes and dof_real_[d_]vecs  */
/*              to a GMV 4.0 format (ASCII or "iecxi?r?", one of the binary */
/*              format versions, NOT NECESSARILY PORTABLE)                  */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Daniel Koester                                               */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*  http://www.mathematik.uni-freiburg.de/IAM/ALBERTA                       */
/*                                                                          */
/*  (c) by D. Koester (2004)                                                */
/*--------------------------------------------------------------------------*/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "alberta.h"
#include "alberta_intern.h"

#include <ctype.h>

#define VERT_IND(dim,i,j) ((i)*N_VERTICES(dim)+(j))

static const REAL_B center_bary[DIM_LIMIT+1] = {
  INIT_BARY_0D(0.0),
  INIT_BARY_1D(0.5, 0.5),
  INIT_BARY_2D(1.0/3.0, 1.0/3.0, 1.0/3.0),
  INIT_BARY_3D(0.25,0.25,0.25,0.25)
};

/***************************************************************************/
/* gmv_open_ascii(filenam, mesh, sim_time): open a file for ASCII GMV      */
/* output.                                                                 */
/***************************************************************************/

static FILE * gmv_open_ascii(const char *filenam, MESH *mesh, REAL sim_time)
{
  /* FUNCNAME("gmv_open_ascii"); */
  FILE       *file = NULL;

  file = fopen(filenam, "w");

  if (file == NULL) {
    return NULL;
  }

  /*  Write header.  */ 
  fprintf(file, "gmvinput ascii\n");
  
  if(mesh->name) {
    fprintf(file, "comments\n");
    fprintf(file, "Mesh '%s'\n", mesh->name);
    fprintf(file, "endcomm\n");
  }

  if (isfinite(sim_time)) {
    fprintf(file, "probtime %.6E\n", sim_time);
  }
  fprintf(file, "codename ALBERTA \n");
  fprintf(file, "codever 2.0      \n");
  
  return file;
}

/***************************************************************************/
/* gmv_open_bin(filenam,isize,rsize,time): open a file for binary output   */
/***************************************************************************/

static FILE * gmv_open_bin(const char *filenam, int isize, int rsize,
			   REAL sim_time)
{
  FUNCNAME("gmv_open_bin");
  FILE *fp = NULL;

  /*  Check if this machine can address 64bit integers (isize = 8).  */
  if (isize == 8)
    TEST_EXIT (sizeof(long) >= 8,
      "Cannot write 8 byte integers on this machine.\n");

  fp = fopen(filenam, "w");

  if (fp == NULL) {
    return NULL;
  }

  /*  Write header.  */ 
  AI_fwrite("gmvinput", sizeof(char), 8, fp);
  if (isize == 4 && rsize == 8) {
    AI_fwrite("iecxi4r8", sizeof(char), 8, fp);
  }
  else if (isize == 8 && rsize == 4) {
    AI_fwrite("iecxi8r4", sizeof(char), 8, fp);
  }
  else if (isize == 8 && rsize == 8) {
    AI_fwrite("iecxi8r8", sizeof(char), 8, fp);
  }
  else  /* just do the normal "iecxi4r4" thing */ {
    AI_fwrite("iecxi4r4", sizeof(char), 8, fp);
  }

  AI_fwrite("probtime", sizeof(char), 8, fp);
  AI_fwrite(&sim_time, sizeof(REAL), 1, fp);
  AI_fwrite("codenameALBERTA codever 2.0     ", sizeof(char), 32, fp);  
  
  return fp;
}


/***************************************************************************/
/* convert_string(in): Replace all white-space characters in "string"      */
/*                     with underscores.                                   */
/***************************************************************************/

static void convert_string(char *pos)
{
  for(; *pos; pos++)
    if(!isgraph((int)*pos)) *pos = '_';
}


/***************************************************************************/
/* write_coord_array(file, n_vert, coords, write_ascii): dump the given    */
/* coordinate array to the GMV file.                                       */
/***************************************************************************/

static void write_coord_array(FILE *file, int n_vert, REAL_D *coords, 
			      bool write_ascii)
{
  REAL *tempxyz;
  int   i, j;

  if(write_ascii) {
    fprintf(file, "nodev %d\n", n_vert);

    for(i = 0; i < n_vert; i++) {
      for(j = 0; j < 3; j++) {
	if(j < DIM_OF_WORLD)
	  fprintf(file, "%.10E ", coords[i][j]);
	else
	  fprintf(file, "0.0 ");
      }
      fprintf(file, "\n");
    }
  }
  else {
    AI_fwrite("nodev   ",sizeof(char),8, file);

    AI_fwrite(&n_vert, sizeof(int), 1, file);

#if DIM_OF_WORLD == 3
    tempxyz = (REAL *) coords;
#else
    tempxyz = MEM_CALLOC(n_vert * 3, REAL);

    for (i = 0; i < n_vert; i++)
      for(j = 0; j < DIM_OF_WORLD; j++) 
	tempxyz[3*i + j] = coords[i][j]; 
#endif
    
    AI_fwrite(tempxyz, sizeof(REAL), 3*n_vert, file);

#if DIM_OF_WORLD < 3
    MEM_FREE(tempxyz, n_vert * 3, REAL);
#endif
  }

  return;
}  


/***************************************************************************/
/* write_elem_array(file, dim, n_elem, elem, write_ascii): dump the given  */
/* element array to the GMV file.                                          */
/***************************************************************************/

static void write_elem_array(FILE *file, int dim, int n_elem, int *elem, 
			     bool write_ascii)
{
  const char *cell_types[4] = {"general ", "line    ", "tri     ", "tet     "};
  int i, j, dim_1 = dim + 1, one = 1, tmp;

  if(write_ascii) {
    fprintf(file, "cells %d\n", n_elem);
  
    for(i = 0; i < n_elem; i++) {
      fprintf(file, "%s%d\n", cell_types[dim], dim+1);
      
      for(j = 0; j < N_VERTICES(dim); j++) {
	if(dim == 0)
	  fprintf(file, "1 ");
	
	fprintf(file, "%d ", 1 + elem[VERT_IND(dim,i,j)]);
      }
      fprintf(file, "\n");
    }
  }
  else {
    AI_fwrite("cells   ", sizeof(char), 8, file);
    AI_fwrite(&n_elem, sizeof(int), 1, file);

    for(i = 0; i < n_elem; i++) {
      AI_fwrite(cell_types[dim], sizeof(char), 8, file);
      AI_fwrite(&dim_1, sizeof(int), 1, file);
      
      for(j = 0; j < N_VERTICES(dim); j++) {
	if(dim == 0)
	  AI_fwrite(&one, sizeof(int), 1, file);

	tmp = 1 + elem[VERT_IND(dim, i, j)];
	AI_fwrite(&tmp, sizeof(int), 1, file);
      }
    }
  }

  return;
}


/***************************************************************************/
/* write_mat_array(file, n_elem, mat_array, write_ascii): dump the given   */
/* material array to the GMV file.                                         */
/***************************************************************************/

static void write_mat_array(FILE *file, int n_elem, int *mat, bool write_ascii)
{
  const char *mat_types[2] = {"affine  ", "parametr"};
  int i, tmp;

  if(write_ascii) {
    fprintf(file, "material 2 0\n");
    fprintf(file, "%s\n", mat_types[0]);
    fprintf(file, "%s\n", mat_types[1]);
    
    for(i = 0; i < n_elem; i++)
      fprintf(file, "%d\n", mat[i]);
  }
  else {
    AI_fwrite("material", sizeof(char), 8, file);
    tmp = 2;
    AI_fwrite(&tmp, sizeof(int), 1, file);
    tmp = 0;
    AI_fwrite(&tmp, sizeof(int), 1, file);
    AI_fwrite(mat_types[0], sizeof(char), 32, file);
    AI_fwrite(mat_types[1], sizeof(char), 32, file);

    AI_fwrite(mat, sizeof(int), n_elem, file);
  }

  return;
}


/***************************************************************************/
/* add_refined_data(file, mesh, use_refined_mesh, write_ascii,             */
/*                  write_mesh_date,  n_drv,                               */
/*                  drv_ptr, n_drv_d, drv_d_ptr, velocity):                */
/*                                                                         */
/* This routine enables us to output more information for higher order     */
/* Lagrange finite elements. Since GMV only displays linear data we first  */
/* perform a virtual refinement of all elements, see the array             */
/* "n_sub_elements" below. The refined mesh is then output to GMV as usual.*/
/***************************************************************************/

static const int n_sub_elements[/*dimension*/ 4][/*degree*/ 5] = {
  {1,1,1,1,1},
  {1,1,2,3,4},
  {1,1,4,9,16},
  {1,1,4,10,20}
};

/* sub_elements[dimension][degree][max_n_elements == 20][vertex] */
static const int sub_elements[4][5][20][4] =
  {/*dim=0*/ {/*p=0*/ {{0}},
	      /*p=1*/ {{0}},
	      /*p=2*/ {{0}},
	      /*p=3*/ {{0}},
	      /*p=4*/ {{0}}},
   /*dim=1*/ {/*p=0*/ {{0,1}},
	      /*p=1*/ {{0,1}},
	      /*p=2*/ {{0,2}, {2,1}},
	      /*p=3*/ {{0,2}, {2,3}, {3,1}},
	      /*p=4*/ {{0,2}, {2,3}, {3,4}, {4,1}}},
   /*dim=2*/ {/*p=0*/ {{0,1,2}},
	      /*p=1*/ {{0,1,2}},
	      /*p=2*/ {{0,5,4},{5,3,4},
		       {5,1,3},{4,3,2}},
	      /*p=3*/ {{0,7,6},{7,9,6},{7,8,9},
		       {6,9,5},{8,3,9},{9,4,5},
		       {8,1,3},{9,3,4},{5,4,2}},
	      /*p=4*/ {{ 0, 9, 8},{ 9,12, 8},{ 9,10,12},{ 8,12, 7},
		       {10,13,12},{12,14, 7},{10,11,13},{12,13,14},
		       { 7,14, 6},{11, 3,13},{13, 4,14},{14, 5, 6},
		       {11, 1, 3},{13, 3, 4},{14, 4, 5},{ 6, 5, 2}}},
   /*dim=3*/ {/*p=0*/ {{ 0, 1, 2, 3}},
	      /*p=1*/ {{ 0, 1, 2, 3}},
	      /*p=2*/ {{ 0, 4, 5, 6},{ 4, 1, 7, 8},
		       { 5, 7, 2, 9},
		       { 6, 8, 9, 3}},
	      /*p=3*/ {{ 0, 4, 6, 8},{ 4, 5,19,18},{ 5, 1,10,12},
		       { 6,19, 7,17},{19,10,11,16},
		       { 7,11, 2,14},
		       { 8,18,17, 9},{18,12,16,13},
		       {17,16,14,15},
		       { 9,13,15, 3}},
	      /*p=4*/ {{ 0, 4, 7,10},{ 4, 5,31,28},{ 5, 6,32,29},{ 6, 1,13,16},
		       { 7,31, 8,25},{31,32,33,34},{32,13,14,22},
		       { 8,33, 9,26},{33,14,15,23},
		       { 9,15, 2,19},
		       {10,28,25,11},{28,29,34,30},{29,16,22,17},
		       {25,34,26,27},{34,22,23,24},
		       {26,23,19,20},
		       {11,30,27,12},{30,17,24,18},
		       {27,24,20,21},
		       {12,18,21, 3}}}};

static const REAL *identity_loc(REAL_D world, const EL_INFO *el_info,
				const QUAD *quad, int iq, void *dummy)
{
  static REAL_D space;

  if (world == NULL) {
    world = space;
  }

  if ((el_info->fill_flag & FILL_COORDS) == 0) {
    const PARAMETRIC *parametric = el_info->mesh->parametric;
    REAL_D world;

    parametric->coord_to_world(el_info, NULL, 1, quad->lambda + iq,
			       (REAL_D *)world);
  } else {
    coord_to_world(el_info, quad->lambda[iq], world);
  }

  return world;
}

static int add_refined_data(FILE *file, MESH *mesh, 
			    bool use_refined_mesh,
			    bool write_ascii,
			    bool write_mesh_data,
			    const int n_drv, DOF_REAL_VEC **drv_ptr,
			    const int n_drv_d, DOF_REAL_VEC_D **drv_d_ptr,
			    DOF_REAL_VEC_D *velocity)
{
  FUNCNAME("add_refined_data");
  int             count, max_degree = -1, ne, nv, n_bas_fcts, i, j, k, n_sub;
  int             dim = mesh->dim, index, node_m = 0, n0;
  DOF_REAL_VEC    *drv = NULL;
  DOF_REAL_VEC_D  *drv_d = NULL;
  const FE_SPACE  *max_fe_space = NULL;
  const BAS_FCTS  *max_bas_fcts;
  const BAS_FCTS  *lag, *bas_fcts;
  DOF_INT_VEC     *dof_vert_ind;
  DOF             *local_dofs = NULL;
  DOF             **local_dof_ptr;
  int             *vert_ind = NULL, *elements;
  REAL_D          *vertices, buffer;
  int             *mat_array = NULL;
  REAL            *new_vecs[250] = { NULL, };
  REAL            *new_vecs_d[250][DIM_OF_WORLD] = { { NULL, }, };
  REAL            *new_vel[3] = { 0, };
  REAL            *local_new_vec;
  REAL_D          *local_new_vec_d;
  const BAS_FCTS  *new_bas_fcts;
  const DOF_ADMIN *new_admin;
  char            name[32] = { '\0', };
  bool            kill_fe_space = false;
  const REAL_B    *lag_nodes;

/***************************************************************************/
/* Step 1: Look for the largest Lagrange degree of vectors on mesh.        */
/* Remember the corresponding FE_SPACE, since we need a mapping from local */
/* DOFs to global indices.                                                 */
/***************************************************************************/

  for(count = 0; count < n_drv; count++) {

    drv = drv_ptr[count];
    
    if(!drv) {
      ERROR_EXIT("Could not find DOF_REAL_VEC %d!\n", count);
    }
    bas_fcts = drv->fe_space->bas_fcts;
    lag = get_lagrange(bas_fcts->dim, bas_fcts->degree);
    if (lag != bas_fcts) {
      if(use_refined_mesh) {
	WARNING("Refinement only implemented for Lagrange Finite Elements!\n");
	WARNING("Using standard output mechanism.\n");
      }
      max_fe_space = drv->fe_space;
      max_degree = 0;
      break;
    }
    if(drv->fe_space->bas_fcts->degree > max_degree) {
      max_fe_space = drv->fe_space;
      max_degree  = max_fe_space->bas_fcts->degree;
    }
  }
  for(count = 0; count < n_drv_d; count++) {
    drv_d = drv_d_ptr[count];
    
    if(!drv_d) {
      ERROR_EXIT("Could not find DOF_REAL_D_VEC %d!\n", count);
    }
    bas_fcts = drv_d->fe_space->bas_fcts;
    lag = get_lagrange(bas_fcts->dim, bas_fcts->degree);
    if (lag != bas_fcts) {
      if(use_refined_mesh) {
	WARNING("Refinement only implemented for Lagrange Finite Elements!\n");
	WARNING("Using standard output mechanism.\n");
      }
      max_fe_space = drv_d->fe_space;
      max_degree = 0;
      break;
    }
    if(drv_d->fe_space->bas_fcts->degree > max_degree) {
      max_fe_space = drv_d->fe_space;
      max_degree  = max_fe_space->bas_fcts->degree;
    }
  }
  if(velocity) {
    bas_fcts = velocity->fe_space->bas_fcts;
    lag = get_lagrange(bas_fcts->dim, bas_fcts->degree);
    if (lag != bas_fcts) {
      if(use_refined_mesh) {
	WARNING("Only implemented for Lagrange Finite Elements!\n");
	WARNING("Using standard output mechanism.\n");
      }
      max_fe_space = velocity->fe_space;
      max_degree = 0;
    }

    if(velocity->fe_space->bas_fcts->degree > max_degree) {
      max_fe_space = velocity->fe_space;
      max_degree  = max_fe_space->bas_fcts->degree;
    }
  }

  if(max_degree > 1) {
    if(use_refined_mesh)
      MSG("Maximal degree found:%d\n", max_degree);
    else 
      max_degree = 1;
  }

  /* No vectors at all? Or no vectors with VERTEX DOFs? Then define        */
  /* max_fe_space as a new FE_SPACE having VERTEX DOFs as well as standard */
  /* linear Lagrange basis functions.                                      */

  if(!max_fe_space || max_degree < 1) {
    const BAS_FCTS *lagrange = get_lagrange(mesh->dim, 1);

    max_fe_space = get_fe_space(mesh, "vertex FE_SPACE",
				lagrange, -1, ADM_FLAGS_DFLT);
    kill_fe_space = true;
    max_degree = 1;
  }


/***************************************************************************/
/* Step 2: Count the number of needed vertices and elements. Fill element  */
/* and vertex arrays.                                                      */
/***************************************************************************/
  n_sub           = n_sub_elements[dim][max_degree];
  n_bas_fcts      = max_fe_space->bas_fcts->n_bas_fcts;
  local_dofs      = MEM_ALLOC(n_bas_fcts, DOF);
  max_bas_fcts    = max_fe_space->bas_fcts;
  dof_vert_ind    = get_dof_int_vec("vertex indices", max_fe_space);

  GET_DOF_VEC(vert_ind, dof_vert_ind);
  FOR_ALL_DOFS(max_fe_space->admin, vert_ind[dof] = -1);

  elements        = MEM_ALLOC(mesh->n_elements * n_sub * N_VERTICES(dim), int);

  ne = nv = 0;
  TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL) {

    GET_DOF_INDICES(max_bas_fcts, el_info->el, max_fe_space->admin, local_dofs);

    for(i = 0; i < n_bas_fcts; i++)
      if (vert_ind[local_dofs[i]] == -1) {
/* Assign a global index to each vertex.                                    */
        vert_ind[local_dofs[i]] = nv;    
	
        nv++;
      }

/* Assign element indices.                                                  */
    for(i = 0; i < n_sub; i++) {
      for(j = 0; j < N_VERTICES(dim); j++)
	elements[VERT_IND(dim,ne,j)] =
	  vert_ind[local_dofs[sub_elements[dim][max_degree][i][j]]];
     
      ne++;
    }
  } TRAVERSE_NEXT();

/* Do a quick check on the element number.                                 */
  TEST_EXIT(ne == mesh->n_elements * n_sub,
    "Wrong no. of elements counted!\n");  

/***************************************************************************/
/* Step 3: Allocate and fill the vertex array, as well as the DOF_REAL_VEC */
/* and DOF_REAL_D_VEC arrays.                                              */
/***************************************************************************/
  vertices        = MEM_ALLOC(nv, REAL_D);
  local_new_vec   = MEM_ALLOC(n_bas_fcts, REAL);
  local_new_vec_d = MEM_ALLOC(n_bas_fcts, REAL_D);

  for(i = 0; i < n_drv; i++) {
    if(!drv_ptr[i]) {
      ERROR("Could not find DOF_REAL_VEC %d!\n", i);
      break;
    }
    if(drv_ptr[i]->fe_space->bas_fcts->degree == 0)
      new_vecs[i] = MEM_ALLOC(ne, REAL);
    else
      new_vecs[i] = MEM_ALLOC(nv, REAL);
  }
  for(i = 0; i < n_drv_d; i++) {
    if(!drv_d_ptr[i]) {
      ERROR("Could not find DOF_REAL_D_VEC %d!\n", i);
      break;
    }
    if(drv_d_ptr[i]->fe_space->bas_fcts->degree == 0)
      index = ne;
    else
      index = nv;

    for(j = 0; j < DIM_OF_WORLD; j++)
      new_vecs_d[i][j] = MEM_ALLOC(index, REAL);
  }
  if(velocity) {
    if(velocity->fe_space->bas_fcts->degree == 0)
      index = ne;
    else
      index = nv;
    for(i = 0; i < 3; i++)
      new_vel[i] = MEM_CALLOC(index, REAL);
  }

  lag_nodes = LAGRANGE_NODES(max_fe_space->bas_fcts);

  index = 0;
  TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|FILL_COORDS) {
    DEF_EL_VEC_CONST(REAL_D, local_coords, N_VERTICES_MAX, N_VERTICES_MAX);
    index++;

    GET_DOF_INDICES(max_bas_fcts, el_info->el, max_fe_space->admin, local_dofs);

    if (mesh->parametric) {
      mesh->parametric->init_element(el_info, mesh->parametric);
    }
    
    INTERPOL_D(max_bas_fcts,
	       local_coords, el_info, -1, 0, NULL, identity_loc, NULL);

    for(i = 0; i < n_bas_fcts; i++) 
      COPY_DOW(local_coords->vec[i], vertices[vert_ind[local_dofs[i]]]);

    for(i = 0; i < n_drv; i++) {
      if(drv_ptr[i]) {
	new_bas_fcts = drv_ptr[i]->fe_space->bas_fcts;
	new_admin = drv_ptr[i]->fe_space->admin;

	if(new_bas_fcts->degree == 0) {
	  n0 = new_admin->n0_dof[CENTER];
	  node_m = mesh->node[CENTER];

	  local_dof_ptr = el_info->el->dof;

	  for(j = 0; j < n_sub; j++) 
	    new_vecs[i][index*n_sub + j] = 
	      drv_ptr[i]->vec[local_dof_ptr[node_m][n0]];
	}
	else {
	  const EL_REAL_VEC *local_new_vec =
	    fill_el_real_vec(NULL, el_info->el, drv_ptr[i]);
	  for(j = 0; j < n_bas_fcts; j++)
	    new_vecs[i][vert_ind[local_dofs[j]]] =
	      eval_uh(lag_nodes[j], local_new_vec, new_bas_fcts);
	}
      }
    }

    for(i = 0; i < n_drv_d; i++) {
      if(drv_d_ptr[i]) {
	const EL_REAL_VEC_D *local_new_vec;

	new_bas_fcts = drv_d_ptr[i]->fe_space->bas_fcts;
	new_admin = drv_d_ptr[i]->fe_space->admin;
	local_new_vec = fill_el_real_vec_d(NULL, el_info->el, drv_d_ptr[i]);

	if(new_bas_fcts->degree == 0) {
	  eval_uh_dow(buffer, center_bary[dim], local_new_vec, new_bas_fcts);
	  for(j = 0; j < n_bas_fcts; j++) {
	    for(k = 0; k < DIM_OF_WORLD; k++) {
	      new_vecs_d[i][k][vert_ind[local_dofs[j]]] = buffer[k];
	    }
	  }
	} else {
	  for(j = 0; j < n_bas_fcts; j++) {
	    eval_uh_dow(buffer, lag_nodes[j], local_new_vec, new_bas_fcts);
	    for(k = 0; k < DIM_OF_WORLD; k++) {
	      new_vecs_d[i][k][vert_ind[local_dofs[j]]] = buffer[k];
	    }
	  }
	}
      }
    }
    
    if(velocity) {
      const EL_REAL_VEC_D *local_new_vec;

      new_bas_fcts = velocity->fe_space->bas_fcts;
      new_admin = velocity->fe_space->admin;
      local_new_vec = fill_el_real_vec_d(NULL, el_info->el, velocity);
      
      if(new_bas_fcts->degree == 0) {
	eval_uh_dow(buffer, center_bary[dim], local_new_vec, new_bas_fcts);
	for(i = 0; i < n_bas_fcts; i++) {
	  for(j = 0; j < DIM_OF_WORLD; j++) {
	    new_vel[j][vert_ind[local_dofs[i]]] = buffer[j];
	  }
	}
      } else {
	local_new_vec = fill_el_real_vec_d(NULL, el_info->el, velocity);
	for(i = 0; i < n_bas_fcts; i++) {
	  eval_uh_dow(buffer, lag_nodes[i], local_new_vec, new_bas_fcts);
	  for(j = 0; j < DIM_OF_WORLD; j++) {
	    new_vel[j][vert_ind[local_dofs[i]]] = buffer[j];
	  }
	}
      }
    }

  } TRAVERSE_NEXT();

/***************************************************************************/
/* Step 5: Write a material array in the parametric case.                  */
/***************************************************************************/

  if(mesh->parametric) {
    mat_array = MEM_CALLOC(ne, int);
    ne = 0;

    TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|FILL_COORDS|FILL_PROJECTION) {

      if(mesh->parametric->init_element(el_info, mesh->parametric))
	for(i = 0; i < n_sub; i++)
	  mat_array[ne + i] = 2;
      else
	for(i = 0; i < n_sub; i++)
	  mat_array[ne + i] = 1;

      ne += n_sub;
    } TRAVERSE_NEXT();

    ne = mesh->n_elements * n_sub;
  }


/***************************************************************************/
/* Step 6: Dump all vectors to the file.                                   */
/***************************************************************************/

  if(write_mesh_data) {
    write_coord_array(file, nv, vertices, write_ascii);
    write_elem_array(file, dim, ne, elements, write_ascii);
    if(mesh->parametric) 
      write_mat_array(file, ne, mat_array, write_ascii);
  }

  if(n_drv && drv_ptr) {
    if(write_ascii)
      fprintf(file, "variable\n");
    else
      AI_fwrite("variable",sizeof(char),8,file);
  }

  for(i = 0; i < n_drv; i++)
    if(new_vecs[i]) {
      snprintf(name, 32, "%s", drv_ptr[i]->name);
      convert_string(name);

      if(drv_ptr[i]->fe_space->bas_fcts->degree == 0) {
	j = 0;
	index = ne*n_sub;
      }
      else {
	j = 1;
	index = nv;
      }

      if(write_ascii) {
	fprintf(file, "%s %d\n", name, j);

	for(j = 0; j < index; j++) 
	  fprintf(file, "%.10E\t", new_vecs[i][j]);
      }
      else {
	AI_fwrite(name, sizeof(char), 32, file);
	AI_fwrite(&j, sizeof(int), 1, file);
	AI_fwrite(new_vecs[i], sizeof(REAL), index, file);
      }
    }

  if(n_drv && drv_ptr) {
    if(write_ascii)
      fprintf(file, "endvars\n");
    else
      AI_fwrite("endvars ",sizeof(char),8,file);
  }

  if(n_drv_d && drv_d_ptr) {
    if(write_ascii)
      fprintf(file, "vectors \n");
    else
      AI_fwrite("vectors ",sizeof(char),8,file);
  }

  for(i = 0; i < n_drv_d; i++)
    if(new_vecs_d[i][0]) {
      snprintf(name, 32, "%s", drv_d_ptr[i]->name);
      convert_string(name);

      if(drv_d_ptr[i]->fe_space->bas_fcts->degree == 0) {
	j = 0;
	index = ne*n_sub;
      }
      else {
	j = 1;
	index = nv;
      }

      if(write_ascii) {
	fprintf(file, "%s %d %d %d\n", name, j, DIM_OF_WORLD, 0);

	for(j = 0; j < DIM_OF_WORLD; j++)
	  for(k = 0; k < index; k++) 
	    fprintf(file, "%.10E\t", new_vecs_d[i][j][k]);
      }
      else {
	AI_fwrite(name, sizeof(char), 32, file);
	AI_fwrite(&j, sizeof(int), 1, file);
	j = DIM_OF_WORLD;
	AI_fwrite(&j, sizeof(int), 1, file);
	j = 0;
	AI_fwrite(&j, sizeof(int), 1, file);
	for(j = 0; j < DIM_OF_WORLD; j++)
	  AI_fwrite(new_vecs_d[i][j], sizeof(REAL), index, file);
      }
    }

  if(n_drv_d && drv_d_ptr) {
    if(write_ascii)
      fprintf(file, "endvect\n");
    else
      AI_fwrite("endvect ",sizeof(char),8,file);
  }
  
  if(velocity) {
    if(velocity->fe_space->bas_fcts->degree == 0) {
      j = 0;
      index = ne;
    }
    else {
      j = 1;
      index = nv;
    }

    if(write_ascii) { 
      fprintf(file, "velocity %d\n", j);

      for(i = 0; i < 3; i++)
	for(j = 0; j < index; j++)
	  fprintf(file, "%.10E\t", new_vel[i][j]);
    }
    else {
      AI_fwrite("velocity", sizeof(char), 8, file);
      AI_fwrite(&j, sizeof(int), 1, file);

      for(i = 0; i < 3; i++)
	AI_fwrite(new_vel[i], sizeof(REAL), index, file);
    }
  }

/* Free all allocated memory.   .                                          */
  MEM_FREE(elements, ne * N_VERTICES(dim), int);
  MEM_FREE(vertices, nv, REAL_D);
  MEM_FREE(local_dofs, n_bas_fcts, DOF);
  MEM_FREE(local_new_vec, n_bas_fcts, REAL);
  MEM_FREE(local_new_vec_d, n_bas_fcts, REAL_D);
  if(mesh->parametric) 
    MEM_FREE(mat_array, ne, int);

  for(i = 0; i < n_drv; i++)
    if(new_vecs[i]) {
      if(drv_ptr[i]->fe_space->bas_fcts->degree == 0)
	MEM_FREE(new_vecs[i], ne, REAL);
      else
	MEM_FREE(new_vecs[i], nv, REAL);
    }
  for(i = 0; i < n_drv_d; i++)
    if(new_vecs_d[i][0]) {
      for(j = 0; j < DIM_OF_WORLD; j++)
	if(drv_d_ptr[i]->fe_space->bas_fcts->degree == 0)
	  MEM_FREE(new_vecs_d[i][j], ne, REAL);
	else
	  MEM_FREE(new_vecs_d[i][j], nv, REAL);
    }
  for(i = 0; i < 3; i++)
    if(new_vel[i]) {
      if(velocity->fe_space->bas_fcts->degree == 0)
	MEM_FREE(new_vel[i], ne, REAL);
      else
	MEM_FREE(new_vel[i], nv, REAL);
    }

  free_dof_int_vec(dof_vert_ind);

  if(kill_fe_space)
    free_fe_space(max_fe_space);
  
  return true;
}


/***************************************************************************/
/* User interface                                                          */
/***************************************************************************/

/***************************************************************************/
/* write_mesh_gmv():                                                       */
/* Write mesh and vectors in GMV ASCII or binary format.                   */
/* We may output up to 250 DOF_REAL_VECs and 250 DOF_REAL_D_VECs into the  */
/* GMV file. These are passed as lists. The "velocity" argument is treated */
/* in a special way; GMV will automatically generate a field storing the   */
/* velocity magnitudes. Only the mesh and file names are mandatory. See    */
/* the description of "add_refined_data()" above for an explanation of     */
/* "use_refined_grid". The entry "sim_time" represents the numerical time  */
/* for instationary problems. GMV can display this value together with     */
/* the FEM data.                                                           */
/*                                                                         */
/* Return value is 0 for OK and 1 for ERROR.                               */
/***************************************************************************/

bool write_mesh_gmv(MESH *mesh, const char *file_name,
		    bool write_ascii,
		    bool use_refined_grid,
		    const int n_drv,
		    DOF_REAL_VEC **drv_ptr,
		    const int n_drv_d,
		    DOF_REAL_VEC_D **drv_d_ptr,
		    DOF_REAL_VEC_D *velocity, 
		    REAL sim_time)
{
  FUNCNAME("write_mesh_gmv");
  FILE           *file = NULL;

  if(!mesh) {
    ERROR("no mesh - no file created!\n");
    return(1);
  }

  if(n_drv < 0 || n_drv > 250) {
    ERROR("n_drv must be an int between 0 and 250!\n");
    return(1);
  }

  if(n_drv_d < 0 || n_drv_d > 250) {
    ERROR("n_drv_d must be an int between 0 and 250!\n");
    return(1);
  }

  if(write_ascii)
    file = gmv_open_ascii(file_name, mesh, sim_time);
  else
    file = gmv_open_bin(file_name, sizeof(int), sizeof(REAL), sim_time);

  if(!file) {
    ERROR("cannot open file %s\n",file_name);
    return(1);
  }
  
  dof_compress(mesh);

  add_refined_data(file, mesh, use_refined_grid,
		   write_ascii, true, n_drv, drv_ptr,
		   n_drv_d, drv_d_ptr, velocity);

  if(write_ascii)
    fprintf(file, "endgmv");
  else {
    AI_fwrite("endgmv  ",sizeof(char),8,file);
  }

  fclose(file);

  return(0);
}


/***************************************************************************/
/* write_dof_vec_gmv():                                                    */
/* This function is similar to the one above. However, we do not output    */
/* the mesh node and element data - instead we direct GMV to access this   */
/* data from the "mesh_file". This file must have been created by a        */
/* previous call to write_mesh_gmv(). This way we avoid wasting time and   */
/* disk space when writing full GMV files including redundant mesh         */
/* information during each step of an instationary simulation, see GMV     */
/* documentation.                                                          */
/*                                                                         */
/* PLEASE NOTE: When using the "use_refined_grid" feature, the prior       */
/* write_mesh_gmv() call must also be given DOF_REAL_[D_]VECS to           */
/* determine the needed virtual refinement of the output mesh.             */
/*                                                                         */
/* Return value is 0 for OK and 1 for ERROR.                               */
/***************************************************************************/

bool write_dof_vec_gmv(MESH *mesh,
		       const char *mesh_file, 
		       const char *file_name,
		       bool write_ascii,
		       bool use_refined_grid,
		       const int n_drv,
		       DOF_REAL_VEC **drv_ptr,
		       const int n_drv_d,
		       DOF_REAL_VEC_D **drv_d_ptr,
		       DOF_REAL_VEC_D *velocity,
		       REAL sim_time)
{
  FUNCNAME("write_mesh_gmv");
  FILE           *file = NULL;

  if(n_drv < 0 || n_drv > 250) {
    ERROR("n_drv must be an int between 0 and 250!\n");
    return(1);
  }

  if(n_drv_d < 0 || n_drv_d > 250) {
    ERROR("n_drv_d must be an int between 0 and 250!\n");
    return(1);
  }


  if(write_ascii)
    file = gmv_open_ascii(file_name, mesh, sim_time);
  else
    file = gmv_open_bin(file_name, sizeof(int), sizeof(REAL), sim_time);

  if(!file) {
    ERROR("cannot open file %s\n",file_name);
    return(1);
  }
  
  dof_compress(mesh);

/* At this point we use the "fromfile" keyword to tell GMV about the other   */
/* file storing the mesh nodes and elements.                                 */

  if(write_ascii) {
    fprintf(file, "nodev fromfile \"%s\"\n", mesh_file);
    fprintf(file, "cells fromfile \"%s\"\n", mesh_file);

    /* Material is only written for parametric meshes. */
    if(mesh->parametric)
      fprintf(file, "material fromfile \"%s\"\n", mesh_file);
  }
  else {
    char filename[1024];

    TEST_EXIT(strlen(mesh_file) < 1024, "Sorry, the filename is too long, please use less than 1024 characters.\n");
    
    snprintf(filename, 1024, "\"%s\"", mesh_file);

    AI_fwrite("nodev   fromfile",sizeof(char),16,file);
    AI_fwrite(filename,sizeof(char),strlen(filename),file);
    AI_fwrite("cells   fromfile",sizeof(char),16,file);
    AI_fwrite(filename,sizeof(char),strlen(filename),file);
    /* Material is only written for parametric meshes. */
    if(mesh->parametric) {
      AI_fwrite("materialfromfile",sizeof(char),16,file);
      AI_fwrite(filename,sizeof(char),strlen(filename),file);
    }
  }

  
  add_refined_data(file, mesh, use_refined_grid,
		   write_ascii, false, n_drv, drv_ptr,
		   n_drv_d, drv_d_ptr, velocity);

  if(write_ascii)
    fprintf(file, "endgmv");
  else {
    AI_fwrite("endgmv  ",sizeof(char),8,file);
  }

  fclose(file);

  return(0);
}
