/*******************************************************************************
 * ALBERTA:  an Adaptive multi Level finite element toolbox using
 *           Bisectioning refinement and Error control by Residual
 *           Techniques for scientific Applications
 *
 * file:     alberta2paraview.c
 *
 * description: Converter from ALBERTA data to Paraview input.
 *
 *******************************************************************************
 *
 *  authors:   Rebecca Stotz
 *             Abteilung fuer Angewandte Mathematik
 *             Albert-Ludwigs-Universitaet Freiburg
 *             Hermann-Herder-Str. 10
 *             D-79104 Freiburg im Breisgau, Germany
 *
 *             Claus-Justus Heine
 *             NMH @ IANS
 *             Universitaet Stuttgart
 *             Pfaffenwaldring 54
 *             70569 Stuttgart
 *             Claus-Justus.Heine@IANS.Uni-Stuttgart.DE
 *
 *  http://www.alberta-fem.de
 *
 *  (c) by R. Stotz (2008), C.-J. Heine (2008-2011)
 *
 *****************************************************************************/

/*---------'cb64[]' and 'encodeblock()' are due to 'base64.c':---------------*/
/*----------------------------------------------------------------------------
LICENCE:        Copyright (c) 2001 Bob Trower, Trantor Standard Systems Inc.

                Permission is hereby granted, free of charge, to any person
                obtaining a copy of this software and associated
                documentation files (the "Software"), to deal in the
                Software without restriction, including without limitation
                the rights to use, copy, modify, merge, publish, distribute,
                sublicense, and/or sell copies of the Software, and to
                permit persons to whom the Software is furnished to do so,
                subject to the following conditions:

                The above copyright notice and this permission notice shall
                be included in all copies or substantial portions of the
                Software.

                THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
                KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
                WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
                PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
                OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
                OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
                OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
                SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
----------------------------------------------------------------------------*/


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

#include <alberta/alberta.h>

#include <unistd.h>
#include <getopt.h>

#ifndef EXIT_SUCCESS
# define EXIT_SUCCESS 0
#endif
#ifndef EXIT_FAILURE
# define EXIT_FAILURE 1
#endif


static 
void write_mesh_paraview(MESH *mesh, const char *outname,
			 bool  ascii, bool unperforated, int lag_deg,
			 int  n_drv, DOF_REAL_VEC *drv[],
			 int n_drdv, DOF_REAL_VEC_D *drdv[],
			 REAL time);


static
void write_data_array(int size_data, FILE *file, REAL *data, int size_vec, 
		      bool ascii, const char *name);

typedef MESH *(*rm_fptr)(const char *filename, REAL *timep,
			 NODE_PROJECTION *(*)(MESH *, MACRO_EL *, int),
			 MESH *master);
typedef DOF_REAL_VEC   *(*rdrv_fptr)(const char *, MESH *, FE_SPACE *);
typedef DOF_REAL_VEC_D *(*rdrdv_fptr)(const char *, MESH *, FE_SPACE *);

typedef struct rdrv {
  const char *name;
  rdrv_fptr  rd_fptr;
} rdrv_struct;

typedef struct rdrdv {
  const char *name;
  rdrdv_fptr rd_fptr;
} rdrdv_struct;


static
void write_movi_paraview(const char *mesh_name, rm_fptr rm,
			 const char *pvd_outname, 
			 bool  ascii, bool unperforated, int lag_deg,
			 int  n_drv, rdrv_struct rdrv_list[],
			 int n_drdv, rdrdv_struct rdrdv_list[],
			 const char *path,
			 int i_start, int i_end, int i_delta);

struct encode_data
{
  unsigned char in[3];
  int in_cnt;
  unsigned char out[4];
};

static inline void encode_and_write(FILE *file,
				    struct encode_data *buffer,
				    char data);

static inline void encode_and_flush(FILE *file, struct encode_data *buffer);


/*
** Translation Table as described in RFC1113
*/
static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

void encodeblock(unsigned char in[3], unsigned char out[4], int len)
{
    out[0] = cb64[ in[0] >> 2 ];
    out[1] = cb64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4) ];
    out[2] = (unsigned char) (len > 1 ? cb64[ ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6) ] : '=');
    out[3] = (unsigned char) (len > 2 ? cb64[ in[2] & 0x3f ] : '=');
}


static int print_help(const char *funcName, FILE *f, int status)
{
  fprintf(f,
"Usage: %s [-t FIRST LAST] [-i STEP] [-p PATH] [-o OUTPUT]\n"
"          -m  MESH [-s DRV] [-v DRDV] [[-m MESH1] [-s DRV1] [-v DRDV1] ...]\n"
"\n"
"Example for converting stationary data:\n"
"  %s -r lagrange_degree --mesh mymesh -s temperature --vector velocity\n"
"    where \"mymesh\", \"temperature\" and \"velocity\" are file-names.\n"
"\n"
"Example for converting a sequence of files resulting from a transient\n"
"problem:\n"
"  %s -t 0 10 -i 5 -p PATH --mesh mymesh -s u_h --vector v_h\n"
"\n"
"  reads grid mymesh000000 with scalar function u_h000000 and\n"
"  vector function v_h000000, then mesh000005 with u_h000005 and\n"
"  v_h000005, and finally mesh000010 with u_h000010 and v_h000010\n"
"\n"
"If a long option shows an argument as mandatory, then it is mandatory\n"
"for the equivalent short option also.  Similarly for optional arguments.\n"
"\n"
"The order of the options _is_ significant in the following cases:\n"
"`-p PATH' alters the search path for all following data-files.\n"
"`-m MESH' specifies a new mesh for all following DRVs and DRDVs (see below)\n"
"`-b|-x'   alters the expected data-format for all following files\n"
"          (see below)\n"
"\n",
          funcName, funcName, funcName);
  fprintf(f,
"Options:\n"
"  -t, --transient FIRST LAST\n"
"            Convert a sequence of mesh- and data-files. The file-names\n"
"            must end with 6-digit decimal number. FIRST and LAST specify the\n"
"            first and last member of this sequence.\n"
"  -i, --interval SKIP\n"
"            In conjunction with `-t' use only every SKIP-th frame in the\n"
"            given sequence of files.\n"
"  -m, --mesh MESH\n"
"            The file-name of an ALBERTA-mesh gnereated by the ALBERTA\n"
"            library routines `write_mesh()' or `write_mesh_xdr()',\n"
"            see the `-x' option below.\n"
"            This option is mandatory and may not be omitted.\n"
"  -a, --ascii\n"
"            Write the paraview file in ASCII format.\n"
"  -r, --refined LAGRANGE-DEGREE\n"
"            Expect Lagrange-degree (between 0 and 4) to refine the given\n"
"            MESH\n"
"            To select 'no refinement' simply do not specify `--refined',\n"
"            'Lagrange-degree = 0' is the default.\n"
"  -u, --unperforated\n"
"            For a 3d mesh refine without holes (produces a lot more\n"
"            elements). To select mesh-refine with holes simply do not\n"
"            specify `--unperforated' (refinement with holes is the default).\n"
"  -b, --binary\n"
"            Write the paraview file in binary format.\n"
"            To select ASCII OUTPUT format simply do not specify `--binary',\n"
"            because ASCII OUTPUT format is the default.\n"
"  -x, --xdr\n"
"            This is the default and just mentioned here for completeness.\n"
"            Expect MESH, DRV and DRDV to contain data in network\n"
"            byte-order, generated by `write_SOMETHING_xdr()' routines\n"
"            of the ALBERTA library. Per convention this means big-endian\n"
"            byte-order. '-l' and '-x' may be specified multiple times.\n\n"
"  -l, --legacy\n"
"            Expect MESH, DRV and DRDV to contain data in ALBERTA's\n"
"            legacy file-format, generated by `write_SOMETHING()' routines\n"
"            of the ALBERTA library. This may not work, because the format\n"
"            of those data-files is byte-order dependent and thus not portable\n"
"            across different computer architectures. '-l' and '-x' may be\n"
"            specified multiple times.\n"
"  -s, --scalar DRV\n"
"            Load the data-file DRV which must contain a DOF_REAL_VEC\n"
"            dumped to disk by `write_dof_real_vec[_xdr]()'.\n"
"            This option may be specified multiple times. The DOF_REAL_VECs\n"
"            must belong to the most recently specified mesh.\n"
"            See `-m' above.\n"
"  -v, --vector DRDV\n"
"            Load the data-file DRDV which must contain a DOF_REAL_VEC_D\n"
"            dumped to disk by `write_dof_real_d_vec[_xdr]()'.\n"
"            This option may be specified multiple times. The vector\n"
"            must belong to the most recently specified mesh.\n"
"            See `-m' above.\n"
"  -o, --output FILENAME\n"
"            Specify an output file-name. If this option is omitted, then the\n"
"            output file-name is\"alberta\".\n"
"  -d, --pvd_output FILENAME\n"
"            Specify an pvd_output file-name,in conjuncion with'-t'.\n "
"            \"alberta_paraview_movi\"is the default\n"
"  -p, --path PATH\n"
"            Specify a path prefix for all following data-files. This option\n"
"            may be specified multiple times. PATH is supposed to be the\n"
"            directory containing all data-files specified by the following\n"
"            `-m', `-s' and `-v' options.\n"
"  -h, --help\n"
"            Print this help.\n");

  return status;
}

static const char *filename(const char *path, const char *fn)
{
  static char  name[1024];

  if (!fn) {
    return(NULL);
  }

  if (path != NULL && path[0] != '\0') {
    
    const char *cp = path;
    while (*cp) {
      cp++;
    }
    cp--;
    if (*cp == '/') {
      sprintf(name, "%s%s", path, fn);
    } else {
      sprintf(name, "%s/%s", path, fn);
    }
    return (const char *)name;
  } else {
    return fn;
  }
  
}

enum {
  KEY_V,
  KEY_F,
  KEY_T,   
  KEY_B_ORDER,
  KEY_GRID,
  KEY_PI,
  KEY_N_PO,
  KEY_N_C,
  KEY_N_COMP,
  KEY_PO,
  KEY_C,
  KEY_POD,
  KEY_CD,
  KEY_DA,
  KEY_FORM,
  KEY_NAME,
  KEY_COL,
  KEY_DS,
  KEY_COM,
  KEY_DACOM,
  KEY_TIMEST,
  N_KEYS
};

#define N_MIN_KEYS  6
static const char *keys[N_KEYS] = {
  "version",           /*  0 */
  "VTKFile",           /*  1 */
  "type",              /*  2 */
  "byte_order",        /*  3 */
  "UnstructuredGrid",  /*  4 */
  "Piece",             /*  5 */
  "NumberOfPoints",    /*  6 */
  "NumberOfCells",     /*  7 */
  "NumberOfComponents",/*  8 */
  "Points",            /*  9 */
  "Cells",             /* 10 */
  "PointData",         /* 11 */
  "CellData",          /* 12 */
  "DataArray",         /* 13 */
  "format",            /* 14 */
  "Name",              /* 15 */
  "Collection",        /* 16 */
  "DataSet",           /* 17 */
  "Compressor",        /* 18 */
  "vtkZLibDataCompressor",/* 19 */
  "timestep",          /* 20 */
};



static struct option long_options[] = {
  {"help",    no_argument,        0, 'h' },
  {"ascii",   no_argument,        0, 'a' },
  {"binary",  no_argument,        0, 'b' },
  {"xdr",     no_argument,        0, 'x' },
  {"legacy",     no_argument,     0, 'l' },
  {"output",  required_argument,  0, 'o' },
  {"pvd_output", required_argument, 0, 'd'},
  {"path",    required_argument,  0, 'p' },
  {"mesh",    required_argument,  0, 'm' },
  {"refined", required_argument,  0, 'r' },
  {"scalar",  required_argument,  0, 's' },
  {"vector",  required_argument,  0, 'v' },
  {"transient", no_argument,      0, 't' },
  {"interval", required_argument, 0, 'i' },
  {"unperforated", no_argument,   0, 'u' },
  { NULL,     no_argument,        0, '\0' }
};

int main(int argc, char *argv[])
{
  FUNCNAME("main");
  const char     *outname = NULL;
  const char     *pvd_outname = NULL;
  char           vtu_name[1024];
  MESH           *mesh = NULL;
  const char     *mesh_path = NULL;

  DOF_REAL_VEC   **drv = NULL;
  DOF_REAL_VEC_D **drdv = NULL;
  rdrdv_struct *rdrdv_list = NULL;
  rdrv_struct  *rdrv_list = NULL;
  int            n_drv = 0, n_drdv = 0;
  const char     *fn, *path = NULL;

  REAL           time;
  bool           ascii = true;
  int            c, option_index;
  int            i_start = 0, i_end = 0, i_delta = 1;
  int            lag_deg = 0;
  int            transient = false;
  bool           unperforated = false;

  int            i = 0;
  rm_fptr        rm;
  rdrv_fptr      rdrv;
  rdrdv_fptr     rdrdv;

  rm = read_mesh_xdr;
  rdrv = read_dof_real_vec_xdr;
  rdrdv = read_dof_real_vec_d_xdr;

  /* first get start and stop scene index */
  if (argc < 3) {
    return print_help(argv[0], stderr, EXIT_FAILURE);
  }
  while (1) {
    c = getopt_long(argc, argv, "-ti:aubhm:d:o:p:r:s:v:xl", 
		    long_options, &option_index);
    if (c == -1) {
      break;
    }
    switch (c) {
    case 't':
      transient = true;
      break;
    case 1: /* should be i_start, i_stop */
      if (optind == 3) {
	i_start = atoi(optarg);
      } else if (optind == 4) {
	i_end = atoi(optarg);
      } else {
	return print_help(argv[0], stderr, EXIT_FAILURE);
      }
      break;
    case 'i':
      i_delta = atoi(optarg);
      break;
    case 'a':
      ascii = true;
      break;
    case 'b':
      ascii = false;
      break;
    case 'u':
      unperforated = true;
      break;
    case 'h':
      return print_help(argv[0], stdout, EXIT_SUCCESS);
    case 'm':
      if (!transient) {
	mesh_path = optarg;
	fn = filename(path, optarg);
	MSG("reading mesh `%s'\n", fn);
	mesh = rm(fn, &time, NULL, NULL);
	TEST_EXIT(mesh,"could not read mesh `%s'!\n", fn);
	break;
      } else {
	mesh_path = optarg;
	break;
      }
    case 'o':
      outname = optarg;
      break;
    case 'd':
      pvd_outname = optarg;
      break;
    case 'p':
      path = optarg;
      break;
    case 'r':
      lag_deg = atoi(optarg);
      break;
    case 's':
      if (mesh_path == NULL) {
	ERROR("a mesh has to be given _FIRST_!\n");
	return print_help(argv[0], stderr, EXIT_FAILURE);
      }
      if (!transient) {
	fn = filename(path, optarg);
	MSG("reading dof_real_vec `%s'\n", fn);
	drv = MEM_REALLOC(drv, n_drv, n_drv+1, DOF_REAL_VEC *);
	drv[n_drv++] = (*rdrv)(fn, mesh, NULL);
	TEST_EXIT(drv[n_drv-1],"could not read dof_real_vec `%s'!\n", fn);
	break;
      } else {
	MSG("adding dof_real_vec `%s'\n", optarg);
	rdrv_list = MEM_REALLOC(rdrv_list, n_drv, n_drv+1, rdrv_struct);
	rdrv_list[n_drv].name    = optarg;
	rdrv_list[n_drv].rd_fptr = rdrv;
	++n_drv;
	break;
      }
    case 'v':
      if (mesh_path == NULL) {
	ERROR("a mesh has to be given _FIRST_!\n");
	return print_help(argv[0], stderr, EXIT_FAILURE);
      }
      if (!transient) {
	fn = filename(path, optarg);
	MSG("reading dof_real_vec_d `%s'\n", fn);
	drdv = MEM_REALLOC(drdv, n_drdv, n_drdv+1, DOF_REAL_VEC_D *);
	drdv[n_drdv++] = (*rdrdv)(fn, mesh, NULL);
	TEST_EXIT(drdv[n_drdv-1],"could not read dof_real_vec_d `%s'!\n", fn);
	break;
      } else {
	MSG("adding dof_real_vec_d `%s'\n", optarg);
	rdrdv_list = MEM_REALLOC(rdrdv_list, n_drdv, n_drdv+1, rdrdv_struct);
	rdrdv_list[n_drdv].name    = optarg;
	rdrdv_list[n_drdv].rd_fptr = rdrdv;
	++n_drdv;
	break;
      }
    case 'x':
      if (mesh_path == NULL) {
	rm = read_mesh_xdr;
      }
      rdrv = read_dof_real_vec_xdr;
      rdrdv = read_dof_real_vec_d_xdr;
      break;
    case 'l':
      if (mesh_path == NULL) {
	rm = read_mesh;
      }
      rdrv = read_dof_real_vec;
      rdrdv = read_dof_real_vec_d;
      break;
    default:
      return print_help(argv[0], stderr, EXIT_FAILURE);
    }
  }

  if (outname == NULL) {
    outname = mesh_path;
  }
  if (pvd_outname == NULL) {
    pvd_outname = mesh_path;
  }

  if (!transient) {
    if (mesh_path == NULL) {
      ERROR("a mesh has to be given!\n\n");
      return print_help(argv[0], stderr, EXIT_FAILURE);
    }
    sprintf(vtu_name, "%s.vtu", outname);
    printf("Attempting to write file \"%s\"\n", vtu_name);
    write_mesh_paraview(mesh, vtu_name, ascii, unperforated, lag_deg,
			n_drv, drv, n_drdv, drdv,
			time);
  } else {
    if (mesh_path == NULL) {
      ERROR("a mesh has to be given!\n\n");
      return print_help(argv[0], stderr, EXIT_FAILURE);
    }
    
    printf("Attempting to write file \"%s.pvd\", ", pvd_outname);
    printf("and the files:");
    for (i = i_start; i <= i_end; i += i_delta) {
      printf("\n\t\"%s%06d.vtu\"", pvd_outname, i);
    }
    printf("  as well.\n\n");
    write_movi_paraview(mesh_path, rm,
			pvd_outname, ascii, unperforated, lag_deg, 
			n_drv, rdrv_list, n_drdv, rdrdv_list,
			path, i_start, i_end, i_delta);
  }
  
  return EXIT_SUCCESS;
}


/********************************************************************************/
/*                  move-visualisation: transient case                          */
/********************************************************************************/
static 
void write_movi_paraview(const char *mesh_name, rm_fptr rm,
			 const char *pvd_outname, 
			 bool  ascii, bool unperforated, int lag_deg,
			 int  n_drv, rdrv_struct rdrv_list[],
			 int n_drdv, rdrdv_struct rdrdv_list[],
			 const char *path,
			 int i_start, int i_end, int i_delta)
{
  FUNCNAME("write_movi_paraview");
  char           vtu_name[1024], pvd_name[1024];
  const char     *fn;
  FILE           *pvd_file;  
  DOF_REAL_VEC   *drv[n_drv];
  DOF_REAL_VEC_D *drdv[n_drdv];
  MESH           *mesh = NULL;
   
  int            i;
  int            ntime = 0;
  
  REAL           t_start = LARGE, t_end = -LARGE, t_act, t_old = HUGE_VAL;

  MSG("start %d, end %d, delta %d\n", i_start, i_end, i_delta);
  
  TEST_EXIT(i_end >= i_start, "end < start\n");
  TEST_EXIT(i_delta > 0, "delta <= 0\n");

  sprintf(pvd_name, "%s.pvd",  pvd_outname);

  if (!(pvd_file = fopen(pvd_name, "w"))) {
    ERROR("could not open file %s for writing\n", pvd_name);
    return ;
  }
/**************** begin pvd-paraview_file *************************************/
  fprintf(pvd_file, "<?xml %s=\"1.0\"?>\n",keys[KEY_V]);
  
  fprintf(pvd_file, "<%s %s=\"%s\" %s=\"0.1\" %s=\"LittleEndian\">\n",
	  keys[KEY_F], keys[KEY_T], keys[KEY_COL], keys[KEY_V],
	  keys[KEY_B_ORDER]);
  
  fprintf(pvd_file, " <%s>\n", keys[KEY_COL]);
  
  for (ntime = i_start; ntime <= i_end; ntime += i_delta) {

    MSG("ntime %d, end %d, delta %d\n", ntime, i_end, i_delta);
    fn = generate_filename(path, mesh_name, ntime);
    MSG("reading \"%s\"\n", fn);
    mesh = (*rm)(fn, &t_act, NULL, NULL);
    TEST_EXIT(mesh,"couldn't read mesh \"%s\"\n", fn);

    MSG("current time: %f (time step size: %f)\n", t_act, t_act - t_old);
    
    for (i = 0; i < n_drv ; i++) {
      fn = generate_filename(path, rdrv_list[i].name, ntime);
      MSG("reading \"%s\"\n", fn);
      drv[i] = rdrv_list[i].rd_fptr(fn, mesh, NULL);
      TEST_EXIT(drv[i], "couldn't read dof_real_vec \"%s\"\n", fn);
    }
    for (i = 0; i < n_drdv ; i++) {
      fn = generate_filename(path, rdrdv_list[i].name, ntime);
      MSG("reading \"%s\"\n", fn);
      drdv[i] = rdrdv_list[i].rd_fptr(fn, mesh, NULL);
      TEST_EXIT(drdv[i], "couldn't read dof_real_d_vec \"%s\"\n", fn);
    }
    
    sprintf(vtu_name, "%s%06d.vtu",  pvd_outname, ntime);
    write_mesh_paraview(mesh, vtu_name, ascii, unperforated, lag_deg,
			n_drv, drv, n_drdv, drdv, t_act);
    
 
    fprintf(pvd_file, "<%s %s=\"%f\" group=\"\" part=\"0\" file=\"%s\"/>\n",
	    keys[KEY_DS], keys[KEY_TIMEST], t_act, vtu_name);
    
    /* Free all resources (mesh and vectors). This can be done in a
     * rather brutal way: just free the entire mesh, ALBERTA will take
     * care of all dirty details (hopefully).
     */
    free_mesh(mesh);
  
    

    if (ntime == i_start) {
      t_start = t_end = t_act;
    }
    t_start = MIN(t_start, t_act);
    t_end = MAX(t_end, t_act);
    t_old = t_act;
  }
/****************************** epilogue ************************************/
  fprintf(pvd_file, " </%s>\n", keys[KEY_COL]);
  fprintf(pvd_file, "</%s>", keys[KEY_F]);
  fclose(pvd_file);

  MSG("Simulation times:\n");
  MSG("start = %e, end = %e, total = %e\n", t_start, t_end, t_end - t_start);
}
 

/* Check whether BAS_FCTS has only center DOFs */
static inline bool cell_bfcts(const BAS_FCTS *bas_fcts)
{
  return (bas_fcts->n_dof[VERTEX] == 0 &&
	  bas_fcts->n_dof[FACE] == 0 &&
	  bas_fcts->n_dof[EDGE] == 0);
}


static const int n_sub_elements_holes[/*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_holes[dimension][degree][max_n_elements == 20][vertex] */
static const int sub_elements_holes[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 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,8,27,64}
};

/* sub_elements[dimension][degree][max_n_elements == 64][vertex] */
static const int sub_elements[4][5][64][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, 5, 6, 8},{ 5, 6, 9, 8},
		       { 5, 9, 8, 7},{ 4, 5, 8, 7},{ 4, 1, 7, 8},
		       { 5, 7, 2, 9},
		       { 6, 8, 9, 3}},
	      /*p=3*/ {{ 0, 4, 6, 8},{4, 6, 8, 18},{6, 8, 17, 18},
		       {16, 17, 18, 19},{17, 18, 19, 6},{18, 19, 4, 6},
		       { 4, 5,19,18},{5, 19, 18, 12}, {19, 18, 16, 12},
		       {16, 12, 10, 19},{12, 10, 5, 19},
		       { 5, 1,10,12},{19,10,11,16},{ 7,11, 2,14},
		       { 6,19, 7,17},{19, 7, 17, 16}, {7, 17, 14, 16},
		       {14, 16, 11, 7},{16, 11, 19, 7},
		       { 8,18,17, 9},{18, 17,  9, 13},{17, 9, 15, 13},
		       {15, 13, 16, 17},{13, 16, 18, 17},
		       {18,12,16,13},{17,16,14,15},
		       { 9,13,15, 3}},
	      /*p=4*/ {/*first layer*/{0,  4,  7, 10 },{4,  7, 10, 28},
		       {7, 10, 25, 28 },
		       {34, 25, 28, 31},{7, 25, 28, 31},{4,  7, 28, 31 },
		       /* */
		       {4,  5, 31, 28 },{5, 31, 28, 29 },{31, 28, 34, 29},
		       {22, 34, 29, 32},{31, 34, 29, 32},{5, 31, 29, 32},
		       /* */
		       {7, 31,  8, 25},{31, 8, 25, 34},{8, 25, 26, 34},
		       { 23, 26, 34, 33 },{  8, 26, 34, 33 },{31,  8, 34, 33 },
		       /* */
		       {5,  6, 32, 29 },{6, 32, 29, 16 },{ 32, 29, 22, 16 },
		       {32, 22, 16, 13 },{6, 32, 16, 13 },
		       /* */
		       {31, 32, 33, 34},{32, 33, 34, 22 },{ 33, 34, 23, 22 },
		       {33, 23, 22, 14 },{32, 33, 22, 14 },
		       /* */
		       {8, 33,  9, 26},{33,9, 26, 23},{9, 26, 19, 23 },
		       {9, 19, 23, 15 },{33,  9, 23, 15},
		       {6, 1, 13, 16},{32, 13, 14, 22 },
		       {33, 14, 15, 23},{ 9, 15,  2, 19},
		       /* second layer */
		       {10, 28, 25, 11},{28, 25, 11, 30},{25, 11, 27, 30},
		       {24, 27, 30, 34},{25, 27, 30, 34},{28, 25, 30, 34},
		       /* */
		       {28, 29, 34, 30},{29, 34, 30, 17},{34, 30, 24, 17},
		       {34, 24, 17, 22},{29, 34, 17, 22},
		       /* */
		       {25, 34, 26, 27},{34, 26, 27, 24},{26, 27, 20, 24},
		       {26, 20, 24, 23},{34, 26, 24, 23},
		       {29,16, 22, 17 },{34, 22, 23, 24},{26, 23, 19, 20 },
		       /* third layer */
		       {11, 30, 27, 12},{30, 27, 12, 18},{27, 12, 21, 18},
		       {27, 21, 18, 24},{30, 27, 18, 24},
		       {30, 17, 24, 18},{27, 24, 20, 21},
		       /* fourth layer */
		       {12, 18, 21, 3}}}};




static 
void write_mesh_paraview(MESH *mesh, const char *outname,
			 bool  ascii, bool unperforated, int lag_deg ,
			 int  n_drv,  DOF_REAL_VEC *drv[],
			 int n_drdv, DOF_REAL_VEC_D *drdv[],
			 REAL time)
{
  FUNCNAME("write_mesh_paraview");
  FILE                 *paraview_file;
  const DOF_ADMIN      *admin;
  const BAS_FCTS       *lagrange;
  const BAS_FCTS       *bas_fcts;
  const FE_SPACE       *fe_space;
  DOF_REAL_D_VEC       *coords;
  DOF_REAL_VEC         *drv_temp[n_drv];
  bool                 drv_cell[n_drv], have_drv_pod, have_drv_cd;
  DOF_REAL_D_VEC       *drdv_temp[n_drdv];
  bool                 drdv_cell[n_drdv], have_drdv_pod, have_drdv_cd;
  DOF_INT_VEC          *dof_vert_ind;
  const EL_REAL_VEC    *uh_loc;
  const EL_REAL_VEC_D  *uh_loc_d;
  REAL_D               result;
  int                  i, j, l, k, m, val_num;
  int                  n_sub, n_bas_fcts, n_vertices, n_elements;
  static const int     (*sub_els)[4];
  char                 name_coords[1024];
  struct encode_data   encblk, encblk_header;
  const REAL_B         *lag_nodes;
  REAL_B               sub_center[64];
  const PARAMETRIC     *parametric = mesh->parametric;
  bool                 is_param = false;

  if (lag_deg > 4) {
    ERROR("Not implemented for lagrange_degree > 4 \n"
	  "\t\t\t- no file created-\n");
    return;
  }

  lag_deg = MAX(1, lag_deg); /* Do not allow degree 0 */
  
  lagrange = get_lagrange(mesh->dim, lag_deg);
  fe_space = get_fe_space(mesh, "mesh2paraview_data", lagrange,
			  DIM_OF_WORLD /* int rdim */, ADM_FLAGS_DFLT);
  coords = get_dof_real_d_vec("coords", fe_space);
  lag_nodes = LAGRANGE_NODES(fe_space->bas_fcts);
  
  
  have_drv_pod = have_drv_cd = false;
  for (l = 0; l < n_drv; l++) {
    if (!(drv_cell[l] = cell_bfcts(drv[l]->fe_space->bas_fcts))) {
      have_drv_pod = true;
      drv_temp[l] = get_dof_real_vec("drv_temp", fe_space);
    } else {
      have_drv_cd = true;
    }
  }
 
  for (l = 0; l < n_drdv; l++) {
    if (!(drdv_cell[l] = cell_bfcts(drdv[l]->fe_space->bas_fcts))) {
      have_drdv_pod = true;
      drdv_temp[l] = get_dof_real_d_vec("drdv_temp", fe_space);
    } else {
      have_drdv_cd = true;
    }
  }

  admin = fe_space->admin;
  bas_fcts = fe_space->bas_fcts;
  n_bas_fcts = bas_fcts->n_bas_fcts;

  dof_vert_ind = get_dof_int_vec("vertex indices", fe_space);
 
 if (unperforated) {
    n_sub = n_sub_elements[mesh->dim][lag_deg];
    sub_els = sub_elements[mesh->dim][lag_deg];
    n_elements = mesh->n_elements * n_sub;
  } else {
    n_sub = n_sub_elements_holes[mesh->dim][lag_deg];
    sub_els = sub_elements_holes[mesh->dim][lag_deg];
    n_elements = mesh->n_elements * n_sub;
  }

  n_vertices = admin->size_used;

  for (i = 0; i < n_sub; i++) {
    for (j = 0; j < N_VERTICES(mesh->dim); j++) {
      AXPY_BAR(mesh->dim, 1.0, lag_nodes[sub_els[i][j]], sub_center[i]);
    }    
    SCAL_BAR(mesh->dim, 1.0/(REAL)N_VERTICES(mesh->dim), sub_center[i]);
  } 

/**************** begin vtu-paraview_file ************************************/
  if (!(paraview_file = fopen(outname, "w"))) {
    ERROR("could not open file %s for writing\n", outname);
    return ;
  }
 
  if (DIM_OF_WORLD < 2 || DIM_OF_WORLD > 3) {
    ERROR("could not convert file %s for DIM_OF_WORLD = %d\n",
	  outname, DIM_OF_WORLD);
    return ;
  }
 
  fprintf(paraview_file, "<?xml %s=\"1.0\"?>\n",keys[KEY_V]);

  fprintf(paraview_file, "<%s %s=\"%s\" %s=\"0.1\" %s=\"LittleEndian\">\n",
	  keys[KEY_F], keys[KEY_T], keys[KEY_GRID], keys[KEY_V],
	  keys[KEY_B_ORDER]);

  fprintf(paraview_file, " <%s>\n", keys[KEY_GRID]);

  fprintf(paraview_file, "  <%s %s=\"%d\" %s=\"%d\">\n", keys[KEY_PI],
	  keys[KEY_N_PO], n_vertices, keys[KEY_N_C],
	  n_elements);
 
/***************************** points ****************************************/
  fprintf(paraview_file, "   <%s>\n", keys[KEY_PO]);

/*---------------------------------------------------------------------------*/
/*  The first pass counts vertices, stores them in a new vector              */
/*  and assigns a global index to each vertex.                               */
/*  Evaluation of finite element functions.                                  */
/*---------------------------------------------------------------------------*/

  TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|FILL_COORDS) {
    DOF local_dofs[n_bas_fcts];
    GET_DOF_INDICES(bas_fcts, el_info->el, admin, local_dofs);
    if (parametric) {
      is_param = parametric->init_element(el_info, parametric);
    }
    if (is_param) {
      for (i = 0; i < n_bas_fcts; i++) {
	parametric->coord_to_world(el_info, NULL, 1, 
				   lag_nodes+i, &coords->vec[local_dofs[i]]);
      }
    } else {
      for (i = 0; i < n_bas_fcts; i++) {
	coord_to_world(el_info, lag_nodes[i], coords->vec[local_dofs[i]]);
      }
    }
    
    for (l = 0; l < n_drv; l++) {
      if (drv_cell[l]) {
	continue;
      }
      uh_loc = fill_el_real_vec(NULL, el_info->el, drv[l]);
      for (i = 0; i < n_bas_fcts; i++) {
	drv_temp[l]->vec[local_dofs[i]] =
	  eval_uh(lag_nodes[i], uh_loc, drv[l]->fe_space->bas_fcts);
      }
    }    
    for (m = 0; m < n_drdv; m++) {
      if (drdv_cell[m]) {
	continue;
      }
      uh_loc_d = fill_el_real_vec_d(NULL, el_info->el, drdv[m]);
      for (i = 0; i < n_bas_fcts; i++) {
	eval_uh_dow(drdv_temp[m]->vec[local_dofs[i]], lag_nodes[i], uh_loc_d, 
		    drdv[m]->fe_space->bas_fcts);
      }
    }
    
  } TRAVERSE_NEXT();
  

  sprintf(name_coords, "coordinates");
  
  write_data_array(n_vertices, paraview_file, &coords->vec[0][0],
		   3 , ascii, (const char *)name_coords);
  
  fprintf(paraview_file, "   </%s>\n", keys[KEY_PO]);
  
/**************************** cells ******************************************/
  fprintf(paraview_file, "   <%s>\n", keys[KEY_C]);
  
  
/*---------------------------------------------------------------------------*/
/*  The second pass assigns the vertices to the corresponding element        */
/*---------------------------------------------------------------------------*/


  if (ascii) {
    fprintf(paraview_file, "    <%s %s=\"Int32\" %s=\"connectivity\" %s=\"ascii\">\n", 
	    keys[KEY_DA], keys[KEY_T], keys[KEY_NAME],keys[KEY_FORM]);
    
    TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL) {
      fprintf(paraview_file, "      ");
      DOF local_dofs[n_bas_fcts];
      GET_DOF_INDICES(bas_fcts, el_info->el, admin, local_dofs);
      
      for (i = 0; i < n_sub; i++) {
	for (j = 0; j < N_VERTICES(mesh->dim); j++) {
	  fprintf(paraview_file, "%d  ", local_dofs[sub_els[i][j]]);
	}
      }
      
      fprintf(paraview_file, "\n");
      
    } TRAVERSE_NEXT();
    
    fprintf(paraview_file, "    </%s>\n", keys[KEY_DA]);
  }
  
  if (!ascii) {
    fprintf(paraview_file, "    <%s %s=\"Int32\" %s=\"connectivity\" %s=\"binary\">\n",  
	    keys[KEY_DA], keys[KEY_T], keys[KEY_NAME],keys[KEY_FORM]);

    memset(&encblk_header, 0, sizeof(encblk_header));
    int numb_contact;
    
    if (mesh->dim == 1) {
      numb_contact = 2;
    }
    if (mesh->dim == 2) {
      numb_contact = 3;
    }
    if (mesh->dim == 3) {
      numb_contact = 4;
    }
 
    int data_lenght = numb_contact * sizeof(DOF) * n_elements;
    char *data_header = (char *)&data_lenght;
    for (k = 0; k < sizeof(DOF); k++) {
      encode_and_write(paraview_file, &encblk_header, *data_header++);
    }
    encode_and_flush(paraview_file, &encblk_header);
    
    memset(&encblk, 0, sizeof(encblk));
    
    TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL) {
      DOF local_dofs[n_bas_fcts];
      GET_DOF_INDICES(bas_fcts, el_info->el, admin, local_dofs);
      for (i = 0; i < n_sub; i++) {
	for(j = 0 ; j < N_VERTICES(mesh->dim); j++) {
	  char *connectivity_data = (char *)&local_dofs[sub_els[i][j]];
	  for (k = 0; k < sizeof(DOF); k++) {
	    encode_and_write(paraview_file, &encblk, *connectivity_data++);
	  }
	}
      }
    } TRAVERSE_NEXT();
    encode_and_flush(paraview_file, &encblk);
    
    fprintf(paraview_file, "\n    </%s>\n", keys[KEY_DA]);
  }


  
  if (ascii) {
    
    fprintf(paraview_file, "    <%s %s=\"Int32\" %s=\"offsets\" %s=\"ascii\">\n",
	    keys[KEY_DA], keys[KEY_T], keys[KEY_NAME],
	    keys[KEY_FORM]);
    
    fprintf(paraview_file, "      ");
    
    for (k = 0; k < n_elements; k++){
      fprintf(paraview_file, "%d ", N_VERTICES(mesh->dim) * (k+1));
    }
    fprintf(paraview_file, "\n");
    
    fprintf(paraview_file, "    </%s>\n", keys[KEY_DA]);
    fprintf(paraview_file, "    <%s %s=\"UInt8\" %s=\"types\" %s=\"ascii\">\n",
	    keys[KEY_DA], keys[KEY_T], keys[KEY_NAME], keys[KEY_FORM]);  
    
    fprintf(paraview_file, "      ");
     
    if (mesh->dim == 1) {
      for (i = 0; i < n_elements; i++) {
	fprintf(paraview_file, "3 "); /* '3' = line */
      }
    }
    
    if (mesh->dim == 2) {
      for (i = 0; i < n_elements; i++) {
	fprintf(paraview_file, "5 "); /* '5' = triangle */
      }
    }
    if (mesh->dim == 3) {
      for (i = 0; i < n_elements; i++) {
	fprintf(paraview_file, "10 "); /* '10' = tetra */
      }
    }
  } else /* if (!ascii) */ {
    
    fprintf(paraview_file, "    <%s %s=\"Int32\" %s=\"offsets\" %s=\"binary\">\n",
	    keys[KEY_DA], keys[KEY_T], keys[KEY_NAME],
	    keys[KEY_FORM]);
    
    memset(&encblk_header, 0, sizeof(encblk_header));
    int32_t data_lenght = n_elements * sizeof(int);
    char *data_header = (char *)&data_lenght;
    for (k = 0; k < sizeof(int32_t); k++) {
      encode_and_write(paraview_file, &encblk_header, *data_header++);
    }
    encode_and_flush(paraview_file, &encblk_header);
    
    memset(&encblk, 0, sizeof(encblk));
    for (k = 0; k < n_elements; k++){
      int32_t offset_data = N_VERTICES(mesh->dim) * (k+1);
      char *offset_data_char = (char *)&offset_data;
      for (j = 0; j < sizeof(int32_t); j++) {
	encode_and_write(paraview_file, &encblk, *offset_data_char++);
      }
    }
    encode_and_flush(paraview_file, &encblk);
    fprintf(paraview_file, "\n");
    
    fprintf(paraview_file, "    </%s>\n", keys[KEY_DA]);
    fprintf(paraview_file, "    <%s %s=\"UInt8\" %s=\"types\" %s=\"binary\">\n",
	    keys[KEY_DA], keys[KEY_T], keys[KEY_NAME], keys[KEY_FORM]);  
    
    
    if (mesh->dim == 1) {
      memset(&encblk_header, 0, sizeof(encblk_header));
      int32_t data_length = n_elements;
      char *data_header = (char *)&data_length;

      for (k = 0; k < sizeof(int32_t); k++) {
	encode_and_write(paraview_file, &encblk_header, *data_header++);
      }
      encode_and_flush(paraview_file, &encblk_header);

      memset(&encblk, 0, sizeof(encblk));
      u_int8_t type = 3;

      for (i = 0; i < n_elements; i++) {
	encode_and_write(paraview_file, &encblk, type);
	  } 
      encode_and_flush(paraview_file, &encblk);
    }
    if (mesh->dim == 2) {
      memset(&encblk_header, 0, sizeof(encblk_header));
      int32_t data_length = n_elements;
      char *data_header = (char *)&data_length;
      
      for (k = 0; k < sizeof(int32_t); k++) {
	encode_and_write(paraview_file, &encblk_header, *data_header++);
      }
      encode_and_flush(paraview_file, &encblk_header);
      
      memset(&encblk, 0, sizeof(encblk));
      u_int8_t type = 5;
      
      for (i = 0; i < n_elements; i++) {
	encode_and_write(paraview_file, &encblk, type);
	  }
      encode_and_flush(paraview_file, &encblk);
    }
    
    if (mesh->dim == 3) {
      memset(&encblk_header, 0, sizeof(encblk_header));
      int32_t data_length = n_elements;
      char *data_header = (char *)&data_length;
      for (k = 0; k < sizeof(int32_t); k++) {
	encode_and_write(paraview_file, &encblk_header, *data_header++);
      }
      encode_and_flush(paraview_file, &encblk_header);
      
      memset(&encblk, 0, sizeof(encblk));
      u_int8_t type = 10;
      
      for (i = 0; i < n_elements; i++) {
	encode_and_write(paraview_file, &encblk, type);
      }
      encode_and_flush(paraview_file, &encblk);  
    }
  }
  fprintf(paraview_file, "\n");
  fprintf(paraview_file, "    </%s>\n", keys[KEY_DA]);
  fprintf(paraview_file, "   </%s>\n", keys[KEY_C]);

/********************************* point data ********************************/

  if (have_drv_pod || have_drdv_pod) {
    fprintf(paraview_file, "   <%s", keys[KEY_POD]);

    if (have_drv_pod) {
      for (val_num = 0; val_num < n_drv; val_num++) {
	if (!drv_cell[val_num]) {
	  if (drv[val_num]->name == NULL || drv[val_num]->name[0] == '\0') {
	    fprintf(paraview_file, " Scalars=\"finite_el_fct_value_%d",
		    val_num);
	  } else {
	    fprintf(paraview_file, " Scalars=\"%s\"", drv[val_num]->name);
	  }
	  break;
	}
      }
    }

    if (have_drdv_pod) {
      for (val_num = 0; val_num < n_drdv; val_num++) {
	if (!drdv_cell[val_num]) {
	  if (drdv[val_num]->name == NULL || drdv[val_num]->name[0] == '\0') {
	    fprintf(paraview_file, " Vectors=\"finite_el_fct_value_d_%d\"",
		    val_num);
	  } else {
	    fprintf(paraview_file, " Vectors=\"%s\"", drdv[val_num]->name);
	  }
	  break;
	}
      }
    }


    fprintf(paraview_file, ">\n");
    for (val_num = 0; val_num < n_drv; val_num++) {
      char namebuf[1024];
      if (drv_cell[val_num]) {
	continue;
      }
      if (drv[val_num]->name == NULL || drv[val_num]->name[0] == '\0') {
	sprintf(namebuf, "finite_el_fct_value_%d", val_num);

	write_data_array(n_vertices, paraview_file, 
			 &drv_temp[val_num]->vec[0], 1, ascii,
			 (const char *)namebuf);
      } else {

	write_data_array(n_vertices, paraview_file, 
			 &drv_temp[val_num]->vec[0], 1, ascii,
			 drv[val_num]->name);
      }
    }
    for (val_num = 0; val_num < n_drdv; val_num++) {
      char namebuffer[1024];
      if (drdv_cell[val_num]) {
	continue;
      }
      if (drdv[val_num]->name == NULL || drdv[val_num]->name[0] == '\0') {
	sprintf(namebuffer, "finite_el_fct_value_d_%d", val_num);
	write_data_array(n_vertices, paraview_file, 
			 &drdv_temp[val_num]->vec[0][0], 3,
			 ascii, (const char *)namebuffer);
      } else {
	write_data_array(n_vertices, paraview_file, 
			 &drdv_temp[val_num]->vec[0][0], 3,
			 ascii, drdv[val_num]->name);
	
      }
    }

    
    fprintf(paraview_file, "   </%s>\n", keys[KEY_POD]);
  }

/***************************** cell data *************************************/

  if (have_drv_cd || have_drdv_cd) {
    fprintf(paraview_file, "   <%s", keys[KEY_CD]);

    if (have_drv_cd) {
      for (val_num = 0; val_num < n_drv; val_num++) {
	if (drv_cell[val_num]) {
	  if (drv[val_num]->name == NULL || drv[val_num]->name[0] == '\0') {
	    fprintf(paraview_file, " Scalars=\"finite_el_fct_value_%d",
		    val_num);
	  } else {
	    fprintf(paraview_file, " Scalars=\"%s\"", drv[val_num]->name);
	  }
	  break;
	}
      }
    }

    if (have_drdv_cd) {
      for (val_num = 0; val_num < n_drdv; val_num++) {
	if (drdv_cell[val_num]) {
	  if (drdv[val_num]->name == NULL || drdv[val_num]->name[0] == '\0') {
	    fprintf(paraview_file, " Vectors=\"finite_el_fct_value_d_%d\"",
		    val_num);
	  } else {
	    fprintf(paraview_file, " Vectors=\"%s\"", drdv[val_num]->name);
	  }
	  break;
	}
      }
    }

    fprintf(paraview_file, ">\n");
    if (ascii) {
      if (have_drv_cd) {
        /* dump scalar cell-data */
	for (val_num = 0; val_num < n_drv; val_num++) {
	  if (!drv_cell[val_num]) {
	    continue;
	  }
	  if (drv[val_num]->name == NULL || drv[val_num]->name[0] == '\0') {
	    fprintf(paraview_file,
		    "    <%s %s=\"finite_el_fct_value_%d\" "
		    "%s=\"ascii\" %s=\"Float64\">\n",
		    keys[KEY_DA], keys[KEY_NAME], val_num,
		    keys[KEY_FORM], keys[KEY_T]);
	  } else {
	    fprintf(paraview_file,
		    "    <%s %s=\"%s\" %s=\"ascii\" %s=\"Float64\">\n",
		    keys[KEY_DA], keys[KEY_NAME], drv[val_num]->name,
		    keys[KEY_FORM], keys[KEY_T]);
	  }
	  TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL) {
	    uh_loc = fill_el_real_vec(NULL, el_info->el, drv[val_num]);
	    for (i = 0; i < n_sub; i++) {
	      fprintf(paraview_file, "   %.15e\n",
		      eval_uh(sub_center[i], uh_loc,
			      drv[val_num]->fe_space->bas_fcts));
	    }  
	  } TRAVERSE_NEXT();
	  fprintf(paraview_file, "    </%s>\n", keys[KEY_DA]);
	}
      }
      
      if (have_drdv_cd) {
	/* dump vector cell-data */
	for (val_num = 0; val_num < n_drdv; val_num++) {
	  if (!drdv_cell[val_num]) {
	    continue;
	  }
	  if (drdv[val_num]->name == NULL || drdv[val_num]->name[0] == '\0') {
	    fprintf(paraview_file,
		    "    <%s %s=\"finite_el_fct_value_d_%d\" "
		    "%s=\"ascii\" %s=\"Float64\" %s=\"3\">\n",
		    keys[KEY_DA], keys[KEY_NAME], val_num, keys[KEY_FORM],
		    keys[KEY_T], keys[KEY_N_COMP]);
	  } else {
	    fprintf(paraview_file,
		    "    <%s %s=\"%s\" %s=\"ascii\" %s=\"Float64\" %s=\"3\">\n",
		    keys[KEY_DA], keys[KEY_NAME], drdv[val_num]->name,
		    keys[KEY_FORM], keys[KEY_T], keys[KEY_N_COMP]);
	  }
	  TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL) {
	    uh_loc_d = fill_el_real_vec_d(NULL, el_info->el, drdv[val_num]);
	    for (i = 0; i < n_sub; i++) {
	      eval_uh_dow(result, sub_center[i],
			  uh_loc_d, drdv[val_num]->fe_space->bas_fcts);
              fprintf(paraview_file, "      ");
              for (j = 0; j < DIM_OF_WORLD; j++) {
                fprintf(paraview_file, "%.15e%s", result[j], j < 2 ? " " : "\n");
              }
              for (; j < 3; j++) {
                fprintf(paraview_file, " 0%s", j < 2 ? " " : "\n");
              }
	    }
	  } TRAVERSE_NEXT();
	  fprintf(paraview_file, "    </%s>\n", keys[KEY_DA]);
	}
      }
    } else { /* !ascii */
      if (have_drv_cd) {
      /* dump scalar cell-data */
	for (val_num = 0; val_num < n_drv; val_num++) {
	  if (!drv_cell[val_num]) {
	    continue;
	  }
	  if (drv[val_num]->name == NULL || drv[val_num]->name[0] == '\0') {
	    fprintf(paraview_file,
		    "    <%s %s=\"finite_el_fct_value_%d\" "
		    "%s=\"binary\" %s=\"Float64\">\n",
		    keys[KEY_DA], keys[KEY_NAME], val_num,
		    keys[KEY_FORM], keys[KEY_T]);
	  } else {
	    fprintf(paraview_file,
		    "    <%s %s=\"%s\" %s=\"binary\" %s=\"Float64\">\n",
		    keys[KEY_DA], keys[KEY_NAME], drv[val_num]->name,
		    keys[KEY_FORM], keys[KEY_T]);
	  }
	  
	  memset(&encblk_header, 0, sizeof(encblk_header));
	  int data_lenght = mesh->n_elements * sizeof(REAL);
	  char *data_header = (char *)&data_lenght;
	  for (k = 0; k < sizeof(int); k++) {
	    encode_and_write(paraview_file, &encblk_header, *data_header++);
	  }
	  encode_and_flush(paraview_file, &encblk_header);

	  memset(&encblk, 0, sizeof(encblk));
	  TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL) {
	    uh_loc = fill_el_real_vec(NULL, el_info->el, drv[val_num]);
	  REAL cell_data = eval_uh(sub_center[i], uh_loc,
				   drv[val_num]->fe_space->bas_fcts);
	  char *scalar_cell_data = (char *)&cell_data;
	  for (k=0; k < sizeof(REAL); k++)
	  {
	    encode_and_write(paraview_file, &encblk, *scalar_cell_data++);
	  }
	  } TRAVERSE_NEXT();
	  encode_and_flush(paraview_file, &encblk);
	  fprintf(paraview_file, "    </%s>\n", keys[KEY_DA]);
	}
      }

      if (have_drdv_cd) {
	/* dump vector cell-data */
	for (val_num = 0; val_num < n_drdv; val_num++) {
	  if (!drdv_cell[val_num]) {
	    continue;
	  }
	  if (drdv[val_num]->name == NULL || drdv[val_num]->name[0] == '\0') {
	    fprintf(paraview_file,
		    "    <%s %s=\"finite_el_fct_value_d_%d\" "
		    "%s=\"binary\" %s=\"Float64\" %s=\"3\">\n",
		    keys[KEY_DA], keys[KEY_NAME], val_num, keys[KEY_FORM],
		    keys[KEY_T], keys[KEY_N_COMP]);
	  } else {
	    fprintf(paraview_file,
		    "    <%s %s=\"%s\" %s=\"binary\" %s=\"Float64\" %s=\"3\">\n",
		    keys[KEY_DA], keys[KEY_NAME], drdv[val_num]->name,
		    keys[KEY_FORM], keys[KEY_T], keys[KEY_N_COMP]);
	  }
	  TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL) {
	    uh_loc_d = fill_el_real_vec_d(NULL, el_info->el, drdv[val_num]);
	    eval_uh_dow(result,	sub_center[i],
			uh_loc_d, drdv[val_num]->fe_space->bas_fcts);
	    fprintf(paraview_file, "      ");
	    for (j = 0; j < DIM_OF_WORLD; j++) {
	      fprintf(paraview_file, "%.15e%s", result[j], j < 2 ? " " : "\n");
	    }
	    for (; j < 3; j++) {
	      fprintf(paraview_file, " 0%s", j < 2 ? " " : "\n");
	    }
	  } TRAVERSE_NEXT();
	  fprintf(paraview_file, "    </%s>\n", keys[KEY_DA]);
	}
      }
    } 

    fprintf(paraview_file, "   </%s>\n", keys[KEY_CD]);
  }

  /****************************** epilogue ************************************/
  fprintf(paraview_file, "  </%s>\n", keys[KEY_PI]);
  fprintf(paraview_file, " </%s>\n", keys[KEY_GRID]);
  fprintf(paraview_file, "</%s>", keys[KEY_F]);

  fclose(paraview_file);

  /*************************** free all resources *****************************/
  
  free_dof_real_d_vec(coords);
 
  for (l = 0; l < n_drv; l++) {
    if (!drv_cell[l]) {
      free_dof_real_vec(drv_temp[l]);
    }
  }

  for (l = 0; l < n_drdv; l++) {
    if (!drdv_cell[l]) {
      free_dof_real_d_vec(drdv_temp[l]);
    }
  }
  free_dof_int_vec(dof_vert_ind);

  free_fe_space(fe_space);

  return;
}


static inline void encode_and_write(FILE *file,
				    struct encode_data *buffer,
				    char data)
{
  int j;

  if (buffer->in_cnt == 3) {
    encodeblock(buffer->in, buffer->out, buffer->in_cnt);
    for (j = 0; j < 4; j++) {
      putc(buffer->out[j], file);
    }
    buffer->in_cnt = 0;
  }
  buffer->in[buffer->in_cnt++] = data;
}

static inline void encode_and_flush(FILE *file, struct encode_data *buffer)
{
  int i;
  
  if (buffer->in_cnt > 0) {
    for (i = buffer->in_cnt; i < 3; i++) {
      buffer->in[i] = 0;
    }
    encodeblock(buffer->in, buffer->out, buffer->in_cnt);
    for (i = 0; i < 4; i++) {
      putc(buffer->out[i], file);
    }
    buffer->in_cnt = 0;
  }
}


static
void write_data_array(int size_data, FILE *file, REAL *data, int size_vec, 
		      bool ascii, const char* name)
{
  int i, j, k;
  struct encode_data encblk, encblk_header;
#if DIM_OF_WORLD == 2
  int stride;
#endif

  if (ascii) {
    
    fprintf(file, "    <%s %s=\"Float64\" %s=\"%s\" %s=\"%d\" %s=\"ascii\">\n", 
	    keys[KEY_DA], keys[KEY_T],keys[KEY_NAME], name,
	    keys[KEY_N_COMP], size_vec, keys[KEY_FORM]);
    if (size_vec == 1) {
      for (i = 0; i < size_data; i++) {
	fprintf(file, "   %.15e\n", data[i]);
      }
    } else {
#if DIM_OF_WORLD == 2
      size_vec = 2;
      for (i = 0; i < size_data; i++) {
	fprintf(file, "      ");
	for (j = 0; j < size_vec/*DIM_OF_WORLD*/; j++) {
	  fprintf(file, "%.15e ", data[size_vec/*DIM_OF_WORLD*/*i+j]);
	}
	fprintf(file, " 0\n");
      }
#endif
    
#if DIM_OF_WORLD == 3
      for(i = 0; i < size_data; i++) {
	fprintf(file, "      ");
	for (j = 0; j < size_vec; j++) {
	  fprintf(file, "%.15e%s", data[size_vec/*DIM_OF_WORLD*/ *i+j],
		  j < DIM_OF_WORLD-1 ? " " : "\n");
	}
      }
#endif    
    }
  }


/* 'data_header' is a header prepended to the data (in binary-vtu-format); */
/* it is a 32 bit integer containing the data length (in bytes) */

  if(!ascii) {
    if (size_vec == 1) {
      fprintf(file, "    <%s %s=\"Float64\" %s=\"%s\" %s=\"%d\" %s=\"binary\">\n", 
	      keys[KEY_DA], keys[KEY_T],keys[KEY_NAME], name, 
	      keys[KEY_N_COMP], size_vec/*DOW(?)*/, keys[KEY_FORM]);
      
      memset(&encblk, 0, sizeof(encblk));
      memset(&encblk_header, 0, sizeof(encblk_header));
      int data_lenght = size_vec * size_data * sizeof(REAL);
      char *data_header = (char *)&data_lenght;
      for (k = 0; k < sizeof(int); k++) {
	encode_and_write(file, &encblk_header, *data_header++);
      }
      encode_and_flush(file, &encblk_header);
      
      for (i = 0; i < size_data; i++) {
	char *data_char = (char *)&data[i*size_vec];
	for (k = 0; k < sizeof(REAL); k++) {
	  encode_and_write(file, &encblk, *data_char++);
	}
	
      }
     encode_and_flush(file, &encblk);
    } else {
#if DIM_OF_WORLD == 2
      fprintf(file, "    <%s %s=\"Float64\" %s=\"%s\" %s=\"%d\" %s=\"binary\">\n",
	      keys[KEY_DA], keys[KEY_T], keys[KEY_NAME], name, 
	      keys[KEY_N_COMP], DIM_OF_WORLD+1, keys[KEY_FORM]);
      stride = (size_vec > 1 ? DIM_OF_WORLD : 1);
      memset(&encblk, 0, sizeof(encblk));
      memset(&encblk_header, 0, sizeof(encblk_header));
      int data_lenght = size_vec * size_data * sizeof(REAL);
      char *data_header = (char *)&data_lenght;
      for (k = 0; k < sizeof(int); k++) {
	encode_and_write(file, &encblk_header, *data_header++);
      }
      encode_and_flush(file, &encblk_header);
      
      for (i = 0; i < size_data; i++) {
	char *data_char = (char *)&data[i*stride];
	for (j = 0; j < stride; j++) {
	  for (k = 0; k < sizeof(REAL); k++) {
	    encode_and_write(file, &encblk, *data_char++);
	  }
	}
	if (size_vec > 1) {
	  for (k = 0; k < sizeof(REAL); k++) {
	    encode_and_write(file, &encblk, 0);
	  }
	}
      encode_and_flush(file, &encblk);
      }

#endif
      
#if DIM_OF_WORLD == 3
      fprintf(file, "    <%s %s=\"Float64\" %s=\"%s\" %s=\"%d\" %s=\"binary\">\n", 
	      keys[KEY_DA], keys[KEY_T], keys[KEY_NAME], name,
	      keys[KEY_N_COMP], DIM_OF_WORLD , keys[KEY_FORM]);
      
      memset(&encblk, 0, sizeof(encblk));
      memset(&encblk_header, 0, sizeof(encblk_header));
      int data_lenght = size_vec * size_data * sizeof(REAL);
      char *data_header = (char *)&data_lenght;
      for (k = 0; k < sizeof(int); k++) {
	encode_and_write(file, &encblk_header, *data_header++);
      }
      encode_and_flush(file, &encblk_header);
      
      for (i = 0; i < size_data; i++) {
	char *data_char = (char *)&data[i*size_vec];
	for (j = 0; j < size_vec; j++) {
	  for (k = 0; k < sizeof(REAL); k++) {
	    encode_and_write(file, &encblk, *data_char++);
	  }
	}
	encode_and_flush(file, &encblk);
      }
     
#endif  
    }
    fprintf(file, "\n");
  }
  
  fprintf(file, "    </%s>\n", keys[KEY_DA]); 
  
  return;
  
}

