/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     write_mesh.c                                                   */
/*                                                                          */
/* description:  functions for writing meshes and DOF vectors in binary     */
/*               machine independent and native formats                     */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Alfred Schmidt                                               */
/*             Zentrum fuer Technomathematik                                */
/*             Fachbereich 3 Mathematik/Informatik                          */
/*             Univesitaet Bremen                                           */
/*             Bibliothekstr. 2                                             */
/*             D-28359 Bremen, Germany                                      */
/*                                                                          */
/*             Kunibert G. Siebert                                          */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*  http://www.mathematik.uni-freiburg.de/IAM/ALBERTA                       */
/*                                                                          */
/*  (c) by A. Schmidt and K.G. Siebert (1996-2003)                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

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

static XDR  *xdrp;
static FILE *file;

/*
   WARNING:
   XDR routines to read/write ALBERTA types must be changed if the
   ALBERTA types change!

   current state:  REAL   = double
                   U_CHAR = unsigned char
                   S_CHAR = signed char
                   DOF    = int

   Another WARNING! (D.K.)
   XDR routines are not well documented in the "xdr" man page.
   Do not change anything unless you know what you are doing!
*/

/*--------------------------------------------------------------------------*/

typedef DOF_REAL_VEC DOF_VEC;

static bool
write_dof_vec_master(const DOF_VEC *dv, const char *dofvectype,
		     const char term[]);

#define DOF_REAL_VEC_NAME   "DOF_REAL_VEC    "
#define DOF_REAL_D_VEC_NAME "DOF_REAL_D_VEC  "
#define DOF_INT_VEC_NAME    "DOF_INT_VEC     "
#define DOF_SCHAR_VEC_NAME  "DOF_SCHAR_VEC   "
#define DOF_UCHAR_VEC_NAME  "DOF_UCHAR_VEC   "

size_t AI_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
{
  size_t nwritten;
  
  nwritten = fwrite(ptr, size, nmemb, stream);

  return nwritten;
}

static void write_int(int val)
{
  bool result;

  if(xdrp)
    result = xdr_int(xdrp, &val);
  else
    result = fwrite(&val, sizeof(int), 1, file) == 1;
  /* Now, what to do with RESULT? ;) */
  (void)result;
}

#if 0
/* NEVER use this. If you need 64 bits, then use xdr_int64_t() */
static void write_long(long int val)
{
  bool result;

  if(xdrp)
    result = xdr_long(xdrp, &val);
  else
    result = fwrite(&val, sizeof(long int), 1, file) == 1;
  /* Now, what to do with RESULT? ;) */
}
#endif

static void write_REAL(REAL val)
{
  bool result;

  if(xdrp)
    result = AI_xdr_REAL(xdrp, &val);
  else
    result = fwrite(&val, sizeof(REAL), 1, file) == 1;
  /* Now, what to do with RESULT? ;) */
  (void)result;
}

static void write_string(const char *string, int write_length)
{
  bool result;
  int strileng = 0;

  if(string)
    strileng = strlen(string);

  if(write_length)
    write_int(strileng);

  if(strileng) {
    if(xdrp)
      result = xdr_string(xdrp, (char **)&string, strileng+1);
    else
      result = fwrite(string, sizeof(char), strileng+1, file) == (strileng+1);
  }
  /* Now, what to do with RESULT? ;) */
  (void)result;
}

static void write_vector(void *start, int n, size_t size, xdrproc_t xdrproc)
{
  bool result;

  if(xdrp)
    result = xdr_vector(xdrp, (char *)start, n, size, xdrproc);
  else
    result = fwrite(start, size, n, file) == n;
  /* Now, what to do with RESULT? ;) */
  (void)result;
}

static void write_U_CHAR(U_CHAR val)
{
  bool result;

  if(xdrp)
    result = AI_xdr_U_CHAR(xdrp, &val);
  else
    result = fwrite(&val, sizeof(U_CHAR), 1, file) == 1;
  /* Now, what to do with RESULT? ;) */
  (void)result;
}

static void write_S_CHAR(S_CHAR val)
{
  bool result;

  if(xdrp)
    result = AI_xdr_S_CHAR(xdrp, &val);
  else
    result = fwrite(&val, sizeof(S_CHAR), 1, file) == 1;
  /* Now, what to do with RESULT? ;) */
  (void)result;
}

/*--------------------------------------------------------------------------*/

static bool write_mesh_master(MESH *mesh, REAL time)
{
  FUNCNAME("write_mesh_master");
  MACRO_EL  *mel, *meln;
  DOF_ADMIN *admin, *face_admin, *vert_admin, *edge_admin;
  DOF       *face_dofs = NULL, *vert_dofs = NULL, *edge_dofs = NULL;
  DOF       **face_ptrs = NULL, **vert_ptrs = NULL, **edge_ptrs = NULL;
  int       n_face_ptrs, n_vert_ptrs, n_edge_ptrs;
  int       i, n, iadmin, dim;
  int       neigh_i[N_NEIGH_MAX];
  int       wt_i[N_WALLS_MAX];

  if (!mesh) {
    ERROR("no mesh - no file created\n");
    return true;
  }
  dim = mesh->dim;

  dof_compress(mesh);

  write_string(ALBERTA_VERSION, false); /* file marker */

  write_int(dim);

  write_int(DIM_OF_WORLD);

  write_REAL(time);

  write_string(mesh->name, true);

  write_int(mesh->n_vertices);

  if(dim > 1) {
    write_int(mesh->n_edges);
  }

  write_int(mesh->n_elements);
  write_int(mesh->n_hier_elements);

  if(dim == 3) {
    write_int(mesh->n_faces);
    write_int(mesh->max_edge_neigh);
  }

  write_S_CHAR((S_CHAR)mesh->is_periodic);
  if (mesh->is_periodic) {
    write_int(mesh->per_n_vertices);

    if(dim > 1) {
      write_int(mesh->per_n_edges);
      if(dim > 2) {
	write_int(mesh->per_n_faces);
      }
    }
    write_int(mesh->n_wall_trafos);
    for (i = 0; i < mesh->n_wall_trafos; i++) {
      write_vector((REAL *)mesh->wall_trafos[i],
		   SQR(DIM_OF_WORLD) + DIM_OF_WORLD, sizeof(REAL),
		   (xdrproc_t)AI_xdr_REAL);
    }
  }

  write_vector(mesh->bbox,
	       2*DIM_OF_WORLD, sizeof(REAL), (xdrproc_t)AI_xdr_REAL);

  write_int(mesh->n_dof_el);
  write_vector(mesh->n_dof, N_NODE_TYPES, sizeof(int), (xdrproc_t)xdr_int);
  write_int(mesh->n_node_el);
  write_vector(mesh->node, N_NODE_TYPES, sizeof(int), (xdrproc_t)xdr_int);



  write_int(mesh->n_dof_admin);
  for (iadmin = 0; iadmin < mesh->n_dof_admin; iadmin++) {
    admin = mesh->dof_admin[iadmin];

    DEBUG_TEST_EXIT(admin, "dof admin no. %d not found!\n", iadmin);

    write_vector(admin->n_dof, N_NODE_TYPES, sizeof(int), (xdrproc_t)xdr_int);
    write_int(admin->used_count);

    /* after dof_compress(), no more information is required */

    write_string(admin->name, true);

    write_U_CHAR(admin->flags & 0xff); /* preserve coarse dofs and periodic */
  }


  face_admin = NULL;
  if (mesh->n_dof[FACE] > 0) {
    for (iadmin = 0; iadmin < mesh->n_dof_admin; iadmin++) {
      if ((admin = mesh->dof_admin[iadmin]) != NULL) {
	if (admin->n_dof[FACE] > 0 &&
	    (face_admin == NULL ||
	     ((admin->flags & ADM_PRESERVE_COARSE_DOFS) != 0 &&
	      (face_admin->flags & ADM_PRESERVE_COARSE_DOFS) == 0)
	     ||
	     (((admin->flags & ADM_PRESERVE_COARSE_DOFS)
	       == 
	       (face_admin->flags & ADM_PRESERVE_COARSE_DOFS) &&
	       admin->n_dof[FACE] < face_admin->n_dof[FACE])))) {
	  face_admin = admin;
	}
      }
    }
    DEBUG_TEST_EXIT(face_admin, "no admin with face dofs?\n");
  }

  edge_admin = NULL;
  if (mesh->n_dof[EDGE] > 0) {
    if (face_admin && (face_admin->n_dof[EDGE] > 0)
	&& (face_admin->flags & ADM_PRESERVE_COARSE_DOFS)) {
      edge_admin = face_admin;
    } else {
      for (iadmin = 0; iadmin < mesh->n_dof_admin; iadmin++) {
	if ((admin = mesh->dof_admin[iadmin]) != NULL) {
	  if (admin->n_dof[EDGE] > 0 &&
	      (edge_admin == NULL ||
	       ((admin->flags & ADM_PRESERVE_COARSE_DOFS) != 0 &&
		(edge_admin->flags & ADM_PRESERVE_COARSE_DOFS) == 0)
	       ||
	       ((admin->flags & ADM_PRESERVE_COARSE_DOFS)
		== 
		(edge_admin->flags & ADM_PRESERVE_COARSE_DOFS) &&
		admin->n_dof[EDGE] < edge_admin->n_dof[EDGE]))) {
	    edge_admin = admin;
	  }
	}
      }
      DEBUG_TEST_EXIT(edge_admin, "no admin with edge dofs?\n");
    }
  }

  vert_admin = NULL;
  if (mesh->n_dof[VERTEX] > 0) {
    if (face_admin && (face_admin->n_dof[VERTEX] > 0)) {
      vert_admin = face_admin;
    } else if (edge_admin && (edge_admin->n_dof[VERTEX] > 0)) {
      vert_admin = edge_admin;
    } else {
      for (iadmin = 0; iadmin < mesh->n_dof_admin; iadmin++) {
	if ((admin = mesh->dof_admin[iadmin])) {
	  if (admin->n_dof[VERTEX] > 0  &&
	      (vert_admin == NULL ||
	       admin->n_dof[VERTEX] < vert_admin->n_dof[VERTEX])) {
	    vert_admin = admin;
	  }
	}
      }
      DEBUG_TEST_EXIT(vert_admin, "no admin with vertex dofs?\n");
    }
  }

  /* On a periodic mesh we have duplicated DOF-pointers across
   * periodic boundaries. So we first loop over the mesh and count the
   * number of DOF pointers for each type of node (FACE/EDGE/VERTEX)
   * and then loop again to do the actual dumping.
   */
  n_vert_ptrs = n_edge_ptrs = n_face_ptrs = 0;
  TRAVERSE_FIRST(mesh, -1, CALL_EVERY_EL_PREORDER|FILL_NOTHING) {
    EL *el = el_info->el;
    int n0, node;
    DOF dof;

    if (vert_admin) { /* shouldn't there be one, always? */
      node = mesh->node[VERTEX];
      n0   = vert_admin->n0_dof[VERTEX];
      for (i = 0; i < N_VERTICES(dim); i++) {
	if ((dof = el->dof[node+i][n0]) >= DOF_UNUSED) {
	  el->dof[node+i][n0] = DOF_UNUSED - 1 -  dof;
	  n_vert_ptrs++;
	}
      }
    }
    
    if (edge_admin) {
      node = mesh->node[EDGE];
      n0   = edge_admin->n0_dof[EDGE];
      for (i = 0; i < N_EDGES(dim); i++) {
	if ((dof = el->dof[node+i][n0]) >= DOF_UNUSED) {
	  el->dof[node+i][n0] = DOF_UNUSED - 1 -  dof;
	  n_edge_ptrs++;
	}
      }
    }

    if (face_admin) {
      node = mesh->node[FACE];
      n0   = face_admin->n0_dof[FACE];
      for (i = 0; i < N_FACES(dim); i++) {
	if ((dof = el->dof[node+i][n0]) >= DOF_UNUSED) {
	  el->dof[node+i][n0] = DOF_UNUSED - 1 -  dof;
	  n_face_ptrs++;
	}
      }
    }

  } TRAVERSE_NEXT();

  /* Loop again over the mesh and enumerate the DOF-pointers, the
   * original DOF values will we saved and restored later when dumping
   * the elements to disk.
   */
  if (vert_admin) {
    vert_ptrs = MEM_ALLOC(n_vert_ptrs, DOF *);
    vert_dofs = MEM_ALLOC(n_vert_ptrs, DOF);
  }
  if (edge_admin) {
    edge_ptrs = MEM_ALLOC(n_edge_ptrs, DOF *);
    edge_dofs = MEM_ALLOC(n_edge_ptrs, DOF);
  }
  if (face_admin) {
    face_ptrs = MEM_ALLOC(n_face_ptrs, DOF *);
    face_dofs = MEM_ALLOC(n_face_ptrs, DOF);
  }

  n_vert_ptrs = n_edge_ptrs = n_face_ptrs = 0;
  TRAVERSE_FIRST(mesh, -1, CALL_EVERY_EL_PREORDER|FILL_NOTHING) {
    DOF *dofptr;
    EL *el = el_info->el;
    int n0, node;

    if (vert_admin) { /* shouldn't there be one, always? */
      node = mesh->node[VERTEX];
      n0   = vert_admin->n0_dof[VERTEX];
      for (i = 0; i < N_VERTICES(dim); i++) {
	if ((dofptr = el->dof[node+i])[n0] < DOF_UNUSED) {
	  dofptr[n0]               = DOF_UNUSED - 1 - dofptr[n0];
	  vert_ptrs[n_vert_ptrs++] = dofptr;
	}
      }
    }
    
    if (edge_admin) {
      node = mesh->node[EDGE];
      n0   = edge_admin->n0_dof[EDGE];
      for (i = 0; i < N_EDGES(dim); i++) {
	if ((dofptr = el->dof[node+i])[n0] < DOF_UNUSED) {
	  dofptr[n0]               = DOF_UNUSED - 1 - dofptr[n0];
	  edge_ptrs[n_edge_ptrs++] = dofptr;
	}
      }
    }

    if (face_admin) {
      node = mesh->node[FACE];
      n0   = face_admin->n0_dof[FACE];
      for (i = 0; i < N_FACES(dim); i++) {
	if ((dofptr = el->dof[node+i])[n0] < DOF_UNUSED) {
	  dofptr[n0]               = DOF_UNUSED - 1 - dofptr[n0];
	  face_ptrs[n_face_ptrs++] = dofptr;
	}
      }
    }
    
  } TRAVERSE_NEXT();

  write_int(n_vert_ptrs);
  if (n_vert_ptrs) {
    int n, n0;
    n  = mesh->n_dof[VERTEX];
    n0 = vert_admin->n0_dof[VERTEX];
    for (i = 0; i < n_vert_ptrs; i++) {
      write_vector(vert_ptrs[i], n, sizeof(DOF), (xdrproc_t)AI_xdr_DOF);
      vert_dofs[i]     = vert_ptrs[i][n0]; /* undo this abuse later */
      vert_ptrs[i][n0] = i;
    }
  }

  if(dim > 1) {
    int n, n0;
    write_int(n_edge_ptrs);
    if (n_edge_ptrs) {
      n  = mesh->n_dof[EDGE];
      n0 = edge_admin->n0_dof[EDGE];
      for (i = 0; i < n_edge_ptrs; i++) {
	write_vector(edge_ptrs[i], n, sizeof(DOF), (xdrproc_t)AI_xdr_DOF);
	edge_dofs[i]     = edge_ptrs[i][n0]; /* undo this abuse later */
	edge_ptrs[i][n0] = i;
      }
    }
  }

  if(dim == 3) {
    int n, n0;
    write_int(n_face_ptrs);
    if (n_face_ptrs) {
      n  = mesh->n_dof[FACE];
      n0 = face_admin->n0_dof[FACE];
      for (i = 0; i < n_face_ptrs; i++) {
	write_vector(face_ptrs[i], n, sizeof(DOF), (xdrproc_t)AI_xdr_DOF);
	face_dofs[i]     = face_ptrs[i][n0]; /* undo this abuse later */
	face_ptrs[i][n0] = i;
      }
    }
  }

  /*
   * gather info about macro elements (vertices, ...)
   */                       
  {
    typedef int  intNV[N_VERTICES_MAX];
    intNV    *mcindex = MEM_ALLOC(mesh->n_macro_el, intNV);
    REAL_D   *mccoord;
    int    mccount, m;

    mccount = ((MESH_MEM_INFO *)(mesh->mem_info))->count;
    mccoord  = ((MESH_MEM_INFO *)(mesh->mem_info))->coords;

    for (m = 0, mel = mesh->macro_els;
	 m < mesh->n_macro_el;
	 m++, mel = mesh->macro_els + m) {
      for (i = 0; i < N_VERTICES(dim); i++) {
	mcindex[m][i] =
	  ((char *)(mel->coord[i])
	   -
	   (char *)mccoord)/(sizeof(char)*sizeof(REAL_D));
      }
      if (mel->index != m) {
	mel->index = m;
      }
    }

    write_int(mesh->n_macro_el);
    write_int(mccount);                           /* number of macro coords */

    for (i = 0; i < mccount; i++) {
      write_vector(mccoord[i], DIM_OF_WORLD, sizeof(REAL),
		   (xdrproc_t)AI_xdr_REAL);
    }

    TRAVERSE_FIRST(mesh, -1, CALL_EVERY_EL_PREORDER|FILL_NOTHING) {
      EL *el = el_info->el;
      int node, n0;
      
      if (el_info->level == 0) {
	/* this is a macro element */
	MACRO_EL *mel = (MACRO_EL *)el_info->macro_el;

	m = mel->index;
	
	write_vector(mcindex[m], N_VERTICES(dim), sizeof(int),
		     (xdrproc_t)xdr_int);
	write_vector(mel->wall_bound, N_WALLS(dim), sizeof(BNDRY_TYPE),
		     (xdrproc_t)AI_xdr_S_CHAR);

	for (i = 0; i < N_NEIGH(dim); i++) {
	  if ((meln = mel->neigh[i])) {
	    neigh_i[i] = meln->index;
	  } else {
	    neigh_i[i] = -1;
	  }
	}

	write_vector(neigh_i, N_NEIGH(dim), sizeof(int), (xdrproc_t)xdr_int);
	write_vector(mel->opp_vertex, N_NEIGH(dim), sizeof(S_CHAR),
		     (xdrproc_t)AI_xdr_S_CHAR);
#if DIM_MAX > 2
	if(dim == 3) {
	  write_U_CHAR(mel->el_type);
	}
#endif

	/* Dump the projection status of the element/faces */
	for (i = 0; i < 1+N_WALLS(dim); i++) {
	  write_S_CHAR(mel->projection[i] != NULL);
	}

	/* dump the binding to the master mesh, if this is a trace mesh */
	if (mel->master.macro_el) {
	  write_int(mel->master.macro_el->index);
	  write_S_CHAR(mel->master.opp_vertex);
	} else {
	  write_int(-1);
	  write_S_CHAR(-1);
	}

	/* dump the status of periodic boundaries */

	if (mesh->is_periodic) {
	  for (i = 0; i < N_NEIGH(dim); i++) {
	    write_vector(mel->neigh_vertices[i], N_VERTICES(dim-1),
			 sizeof(S_CHAR), (xdrproc_t)AI_xdr_S_CHAR);
	  }
	  for (i = 0; i < N_WALLS(dim); i++) {
	    AFF_TRAFO *wt;
	    int j;
	    if ((wt = mel->wall_trafo[i]) != NULL) {
	      for (j = 0;
		   j < mesh->n_wall_trafos && mesh->wall_trafos[j] != wt; j++);
	      DEBUG_TEST_EXIT(j < mesh->n_wall_trafos,
			      "Iconsistent or unknown wall transformations.\n");
	      wt_i[i] = j;
	    } else {
	      wt_i[i] = -1;
	    }
	  }
	  write_vector(wt_i, N_WALLS(dim), sizeof(int), (xdrproc_t)xdr_int);
	}
      }
      
      /* dump the element to disk (macro or not) */

      if (el->child[0]) {
	DEBUG_TEST_EXIT(el->child[1], "child 0 but no child 1\n");
	write_U_CHAR(true);
      } else {
	write_U_CHAR(false);
      }

      if(dim > 1) {
	if (el->new_coord) {
	  write_U_CHAR(true);
	  write_vector(el->new_coord, DIM_OF_WORLD, sizeof(REAL),
		       (xdrproc_t)AI_xdr_REAL);
	} else {
	  write_U_CHAR(false);
	}
      }
      
      if (mesh->n_dof[VERTEX] > 0) {
	node = mesh->node[VERTEX];
	n0   = vert_admin->n0_dof[VERTEX];
	for (i = 0; i < N_VERTICES(dim); i++) {
	  write_int(el->dof[node + i][n0]);
	}
      }
	
      if (dim > 1 && mesh->n_dof[EDGE] > 0) {
	node = mesh->node[EDGE];
	n0   = edge_admin->n0_dof[EDGE];

	for (i = 0; i < N_EDGES(dim); i++) {
	  if(el->dof[node + i][n0] > DOF_UNUSED) {
	    write_int(el->dof[node + i][n0]);
	  } else {
	    write_int(DOF_UNUSED);
	  }
	}
      }
      
      if (dim == 3 && mesh->n_dof[FACE] > 0) {
	node = mesh->node[FACE];
	n0   = face_admin->n0_dof[FACE];

	for (i = 0; i < N_FACES_3D; i++) {
	  if(el->dof[node + i][n0] > DOF_UNUSED) {
	    write_int(el->dof[node + i][n0]);
	  } else {
	    write_int(DOF_UNUSED);
	  }
	}
      }

      if ((n = mesh->n_dof[CENTER]) > 0) {
	node = mesh->node[CENTER];

	write_vector(el->dof[node], n, sizeof(DOF), (xdrproc_t)AI_xdr_DOF);
      }

    } TRAVERSE_NEXT();
    
    MEM_FREE(mcindex, mesh->n_macro_el, intNV);
  }

  /* Write the magic cookie. */
  write_int(mesh->cookie);

  if (dim == 3 && face_admin) {
    /* It remains to undo the abuse of el->dof[][n0] */
    int n0 = face_admin->n0_dof[FACE];
    for (i = 0; i < n_face_ptrs; i++) {
      face_ptrs[i][n0] = face_dofs[i];
    }
    MEM_FREE(face_ptrs, n_face_ptrs, DOF *);
    MEM_FREE(face_dofs, n_face_ptrs, DOF);
  }

  if (dim > 1 && edge_admin) {
    /* It remains to undo the abuse of el->dof[][n0] */
    int n0 = edge_admin->n0_dof[EDGE];
    for (i = 0; i < n_edge_ptrs; i++) {
      edge_ptrs[i][n0] = edge_dofs[i];
    }
    MEM_FREE(edge_ptrs, n_edge_ptrs, DOF *);
    MEM_FREE(edge_dofs, n_edge_ptrs, DOF);
  }

  if (vert_admin) {
    /* It remains to undo the abuse of el->dof[][n0] */
    int n0 = vert_admin->n0_dof[VERTEX];
    for (i = 0; i < n_vert_ptrs; i++) {
      vert_ptrs[i][n0] = vert_dofs[i];
    }
    MEM_FREE(vert_ptrs, n_vert_ptrs, DOF *);
    MEM_FREE(vert_dofs, n_vert_ptrs, DOF);
  }

  /* If this is a parametric mesh with the internal standard Lagrange
   * parameterization, then we dump the co-ordinate and touched-edges
   * information to disk here.
   */
  if (_AI_is_lagrange_parametric(mesh)) {
    PARAMETRIC *parametric = mesh->parametric;
    DOF_PTR_VEC *edge_projections;
    
    write_string("LAPA", false);
    write_S_CHAR(parametric->not_all);
    write_S_CHAR(parametric->use_reference_mesh);
    write_int(_AI_lagrange_strategy(mesh));
    write_dof_vec_master((DOF_VEC *)get_lagrange_coords(mesh),
			 DOF_REAL_D_VEC_NAME, "EOF.");
    edge_projections = get_lagrange_edge_projections(mesh);
    if (edge_projections != NULL) {
      DOF_UCHAR_VEC *touched_edges =
        get_dof_uchar_vec("touched edges",
                          edge_projections->fe_space);
      FOR_ALL_DOFS(edge_projections->fe_space->admin, {
          touched_edges->vec[dof] = edge_projections->vec[dof] != NULL;
        });
      write_dof_vec_master((DOF_VEC *)touched_edges,
                           DOF_UCHAR_VEC_NAME, "EOF.");
      free_dof_uchar_vec(touched_edges);
    }
  }

  {
    MESH_MEM_INFO *minfo = (MESH_MEM_INFO *)mesh->mem_info;
    
    /* If we have trace-meshes, then dump those to disk as well. */
    for (i = 0; i < minfo->n_slaves; i++) {
      write_string("TMSH", false);
      write_int(minfo->slaves[i]->trace_id);
      write_mesh_master(minfo->slaves[i], time);
    }
  }

  write_string("EOF.", false);                       /* file end marker */

  return false; /* Success status. */
}

bool fwrite_mesh(MESH *mesh, FILE *fp, REAL time)
{
  bool result;
  
  file = fp;
  
  result = write_mesh_master(mesh, time);

  file = NULL;
  
  return result;
}

bool write_mesh(MESH *mesh, const char *filename, REAL time)
{
  FUNCNAME("write_mesh");
  FILE *fp;
  bool result;
  
  if ((fp = fopen(filename,"wb")) == NULL) {
    ERROR("Cannot open file '%s' for writing.\n", filename);
    return true;
  }
  
  result = fwrite_mesh(mesh, fp, time);

  fclose(fp);
  
  return result;
}

bool fwrite_mesh_xdr(MESH *mesh, FILE *fp, REAL time)
{
  FUNCNAME("fwrite_mesh_xdr");
  bool result;

  if (!(xdrp = AI_xdr_fopen(fp, XDR_ENCODE))) {
    ERROR("Cannot convert file handle to XDR handle.\n");
    return true;
  }
  file = fp;

  result = write_mesh_master(mesh, time);

  AI_xdr_close(xdrp);
  
  xdrp = NULL;
  file = NULL;

  return result;
}

bool write_mesh_xdr(MESH *mesh, const char *filename, REAL time)
{
  FUNCNAME("write_mesh_xdr");
  FILE *fp;
  bool result;
  
  if ((fp = fopen(filename,"wb")) == NULL) {
    ERROR("Cannot open file '%s' for writing.\n", filename);
    return true;
  }

  result = fwrite_mesh_xdr(mesh, fp, time);
  
  fclose(fp);

  return result;
}

/*--------------------------------------------------------------------------*/
/* write DOF vectors of various types                                       */
/*--------------------------------------------------------------------------*/
static bool
write_dof_vec_master(const DOF_VEC *dv, const char *dofvectype,
		     const char term[])
{
  FUNCNAME("write_dof_vec_master");
  U_CHAR          flags_and_stride;
  const FE_SPACE  *fe_space;
  const DOF_ADMIN *admin;
  MESH            *mesh;
  int              i, iadmin, last;

  if (!dv || !(fe_space = dv->fe_space)) {
    ERROR("no %s or fe_space - no file created\n", dofvectype);
    return(1);
  }
  if (!(admin = fe_space->admin) || !(mesh = admin->mesh)) {
    ERROR("no dof_admin or dof_admin->mesh - no file created\n");
    return true; /* crazy return convention: 1 means error, 0 means no error */
  }

  dof_compress(mesh);

  iadmin = -1;
  for (i = 0; i < mesh->n_dof_admin; i++) {
    if (mesh->dof_admin[i] == admin) {
      iadmin = i;
      break;
    }
  }
  if (iadmin < 0) {
    ERROR("vec->admin not in mesh->dof_admin[] - no file created\n");
    return true;
  }

  last = admin->used_count;
  DEBUG_TEST_EXIT(last <= dv->size,
		  "dof_vec->size %d < admin->size_used %d\n", dv->size, last);

  write_string(dofvectype, false);

  write_string(dv->name, true);

#if (ADM_FLAGS_MASK & 0x80)
# error ADM_FLAGS_MASK overlaps the sign bit.
#endif

  /* We (mis-)use the sign bit to code the stride of DOF_REAL_VEC_Ds. */
  flags_and_stride = admin->flags & ADM_FLAGS_MASK;
  if (dv->reserved != 1) {
    flags_and_stride |= 0x80;
  }
  write_U_CHAR(flags_and_stride);

  write_vector((void *)admin->n_dof, N_NODE_TYPES, sizeof(int),
	       (xdrproc_t)xdr_int);

  if (fe_space->bas_fcts)
    write_string(fe_space->bas_fcts->name, true);
  else
    write_int(0);


  write_int(last);

  if (last) {
    if (!strncmp(dofvectype, "DOF_REAL_VEC    ", 12))
      write_vector(dv->vec, last, sizeof(REAL), (xdrproc_t)AI_xdr_REAL);
    else if (!strncmp(dofvectype, "DOF_REAL_D_VEC  ", 12))
      write_vector(dv->vec, last*DIM_OF_WORLD, sizeof(REAL),
		   (xdrproc_t)AI_xdr_REAL);
    else if (!strncmp(dofvectype, "DOF_INT_VEC     ", 12))
      write_vector(dv->vec, last, sizeof(int), (xdrproc_t)xdr_int);
    else if (!strncmp(dofvectype, "DOF_SCHAR_VEC   ", 12))
      write_vector(dv->vec, last, sizeof(S_CHAR), (xdrproc_t)AI_xdr_S_CHAR);
    else if (!strncmp(dofvectype, "DOF_UCHAR_VEC   ", 12))
      write_vector(dv->vec, last, sizeof(U_CHAR), (xdrproc_t)AI_xdr_U_CHAR);
    else
      ERROR("Invalid file id '%s'.\n",dofvectype);
  }

  /* Write the magic cookie. */
  write_int(mesh->cookie);

  write_string(term, false);                         /* file end marker */

  return false; /* no error */
}

static inline
bool fwrite_dof_vec_master(bool write_xdr, const DOF_VEC *dv,
			   FILE *fp, const char *dofvectype)
{
  FUNCNAME("fwrite_dof_vec_master");
  const DOF_VEC *chain;
  bool error, is_vec_d;
  
  if (write_xdr) {
    if (!(xdrp = AI_xdr_fopen(fp, XDR_ENCODE))) {
      ERROR("Cannot convert file handle to XDR handle.\n");
      return true;
    }
  }
  file = fp;

  is_vec_d = strcmp("DOF_REAL_VEC_D  ", dofvectype) == 0;

  chain = dv;
  CHAIN_DO(chain, const DOF_VEC) {
    if (is_vec_d) {
      dofvectype = ((DOF_REAL_VEC_D *)chain)->stride == 1
	? "DOF_REAL_VEC    " : "DOF_REAL_D_VEC  ";
    }
    error =
      write_dof_vec_master(chain, dofvectype,
			   CHAIN_NEXT(chain, DOF_VEC) == dv ? "EOF." : "NEXT");
    if (error) {
      break;
    }
  } CHAIN_WHILE(chain, const DOF_VEC);

  if(write_xdr) {
    AI_xdr_close(xdrp);
    xdrp = NULL;
  }
  file = NULL;

  return error;
}

static bool file_write_dof_vec_master(bool write_xdr, const DOF_VEC *dv,
				      const char *fn, const char *dofvectype)
{
  FUNCNAME("file_write_dof_vec_master");
  FILE *fp;
  bool error;

  if ((fp = fopen(fn, "wb")) == NULL) {
    ERROR("Cannot open file '%s' for writing.\n", fn);
    return true;
  }
  
  error = fwrite_dof_vec_master(write_xdr, dv, fp, dofvectype);
  
  fclose(fp);

  return error;
}

/*--------------------------------------------------------------------------*/

bool write_dof_real_vec_xdr(const DOF_REAL_VEC *dv, const char *fn)
{
  return file_write_dof_vec_master(true, (const DOF_VEC *)dv,
				   fn, "DOF_REAL_VEC    ");
}

bool write_dof_real_vec(const DOF_REAL_VEC *dv, const char *fn)
{
  return file_write_dof_vec_master(false, (const DOF_VEC *)dv,
				   fn, "DOF_REAL_VEC    ");
}

bool fwrite_dof_real_vec_xdr(const DOF_REAL_VEC *dv, FILE *fp)
{
  return fwrite_dof_vec_master(true, (const DOF_VEC *)dv,
			       fp, "DOF_REAL_VEC    ");
}

bool fwrite_dof_real_vec(const DOF_REAL_VEC *dv, FILE *fp)
{
  return fwrite_dof_vec_master(false, (const DOF_VEC *)dv,
			       fp, "DOF_REAL_VEC    ");
}

bool write_dof_real_vec_d_xdr(const DOF_REAL_VEC_D *dv, const char *fn)
{
  return file_write_dof_vec_master(
    true, (const DOF_VEC *)dv, fn, "DOF_REAL_VEC_D  ");
}

bool write_dof_real_vec_d(const DOF_REAL_VEC_D *dv, const char *fn)
{
  return file_write_dof_vec_master(
    false, (const DOF_VEC *)dv, fn, "DOF_REAL_VEC_D  ");
}

bool fwrite_dof_real_vec_d_xdr(const DOF_REAL_VEC_D *dv, FILE *fp)
{
  return fwrite_dof_vec_master(
    true, (const DOF_VEC *)dv, fp, "DOF_REAL_VEC_D  ");
}

bool fwrite_dof_real_vec_d(const DOF_REAL_VEC_D *dv, FILE *fp)
{
  return fwrite_dof_vec_master(
    false, (const DOF_VEC *)dv, fp, "DOF_REAL_VEC_D  ");
}




bool write_dof_real_d_vec_xdr(const DOF_REAL_D_VEC *dv, const char *fn)
{
  return file_write_dof_vec_master(true, (const DOF_VEC *)dv,
				   fn, "DOF_REAL_D_VEC  ");
}

bool write_dof_real_d_vec(const DOF_REAL_D_VEC *dv, const char *fn)
{
  return file_write_dof_vec_master(false, (const DOF_VEC *)dv,
				   fn, "DOF_REAL_D_VEC  ");
}

bool fwrite_dof_real_d_vec_xdr(const DOF_REAL_D_VEC *dv, FILE *fp)
{
  return fwrite_dof_vec_master(true, (const DOF_VEC *)dv,
			       fp, "DOF_REAL_D_VEC  ");
}

bool fwrite_dof_real_d_vec(const DOF_REAL_D_VEC *dv, FILE *fp)
{
  return fwrite_dof_vec_master(false, (const DOF_VEC *)dv,
			       fp, "DOF_REAL_D_VEC  ");
}

bool write_dof_int_vec_xdr(const DOF_INT_VEC *dv, const char *fn)
{
  return file_write_dof_vec_master(true, (const DOF_VEC *)dv,
				   fn, "DOF_INT_VEC     ");
}

bool write_dof_int_vec(const DOF_INT_VEC *dv, const char *fn)
{
  return file_write_dof_vec_master(false, (const DOF_VEC *)dv,
				   fn, "DOF_INT_VEC     ");
}

bool fwrite_dof_int_vec_xdr(const DOF_INT_VEC *dv, FILE *fp)
{
  return fwrite_dof_vec_master(true, (const DOF_VEC *)dv,
			       fp, "DOF_INT_VEC     ");
}

bool fwrite_dof_int_vec(const DOF_INT_VEC *dv, FILE *fp)
{
  return fwrite_dof_vec_master(false, (const DOF_VEC *)dv,
			       fp, "DOF_INT_VEC     ");
}

bool write_dof_schar_vec_xdr(const DOF_SCHAR_VEC *dv, const char *fn)
{
  return file_write_dof_vec_master(true, (const DOF_VEC *)dv,
				   fn, "DOF_SCHAR_VEC   ");
}

bool write_dof_schar_vec(const DOF_SCHAR_VEC *dv, const char *fn)
{
  return file_write_dof_vec_master(false, (const DOF_VEC *)dv,
				   fn, "DOF_SCHAR_VEC   ");
}

bool fwrite_dof_schar_vec_xdr(const DOF_SCHAR_VEC *dv, FILE *fp)
{
  return fwrite_dof_vec_master(true, (const DOF_VEC *)dv,
			       fp, "DOF_SCHAR_VEC   ");
}

bool fwrite_dof_schar_vec(const DOF_SCHAR_VEC *dv, FILE *fp)
{
  return fwrite_dof_vec_master(false, (const DOF_VEC *)dv,
			       fp, "DOF_SCHAR_VEC   ");
}

bool write_dof_uchar_vec_xdr(const DOF_UCHAR_VEC *dv, const char *fn)
{
  return file_write_dof_vec_master(true, (const DOF_VEC *)dv,
				   fn, "DOF_UCHAR_VEC   ");
}

bool write_dof_uchar_vec(const DOF_UCHAR_VEC *dv, const char *fn)
{
  return file_write_dof_vec_master(false, (const DOF_VEC *)dv,
				   fn, "DOF_UCHAR_VEC   ");
}

bool fwrite_dof_uchar_vec_xdr(const DOF_UCHAR_VEC *dv, FILE *fp)
{
  return fwrite_dof_vec_master(true, (const DOF_VEC *)dv,
			       fp, "DOF_UCHAR_VEC   ");
}

bool fwrite_dof_uchar_vec(const DOF_UCHAR_VEC *dv, FILE *fp)
{
  return fwrite_dof_vec_master(false, (const DOF_VEC *)dv,
			       fp, "DOF_UCHAR_VEC   ");
}

/*--------------------------------------------------------------------------*/
/*  write_dof_matrix_pbm: print matrix structure as portable bitmap         */
/*--------------------------------------------------------------------------*/

bool fwrite_dof_matrix_pbm(const DOF_MATRIX *matrix, FILE *file)
{
  FUNCNAME("write_dof_matrix_pbm");
  int  i, j, jcol, size, matsize;
  MATRIX_ROW *row;
  char *pbm_row;

  TEST_EXIT(matrix->type == MATENT_REAL,
	    "Only implemented for scalar matrices so far.\n");

  if (matrix->row_fe_space) {
    matsize = matrix->row_fe_space->admin->size_used;
  } else {
    matsize = matrix->size;
  }

  size = matsize + 1;
  pbm_row = MEM_CALLOC(size, char);

  fprintf(file, "P1\n");
  fprintf(file, "# ALBERTA output of DOF_MATRIX %s\n", matrix->name);
  fprintf(file, "%d %d\n", matsize, matsize);

  for (i=0; i < matsize; i++) {
    memset(pbm_row, '0', matsize);
    for (row = matrix->matrix_row[i]; row; row = row->next) {

      for (j=0; j<ROW_LENGTH; j++) {
	jcol = row->col[j];

	if (ENTRY_USED(jcol) && row->entry.real[j])
	  pbm_row[jcol] = '1';

      }
    }
    fprintf(file, "%s\n", pbm_row);
  }

  MEM_FREE(pbm_row, size, char);

  return 0;
}

bool write_dof_matrix_pbm(const DOF_MATRIX *matrix,
			  const char *filename)
{
  FUNCNAME("write_dof_matrix_pbm");
  FILE *fp;
  bool result;

  if ((fp = fopen(filename,"w")) == NULL) {
    ERROR("cannot open file %s\n",filename);
    return true;
  }

  result = fwrite_dof_matrix_pbm(matrix, fp);
  
  fclose(fp);
  
  return result;
}

/*--------------------------------------------------------------------------*/

#if 0
/* FORM
   LENGTH
   AFEM
   AMSH
   LENGTH
   data
   AVEC
   LENGTH
   data
   AVEC
   LENGTH
   data

   where AVEC is one of DRV DRDV DUCV DSCV DINV
 */

/* Write a new IFF-file. On return fp will be positioned at the FORM
 * record. If fp is already in IFF format, then just move to the FORM
 * record, otherwise generate a new FORM. On entry fp must be
 * positioned either at a FORM record which contains an ADTA id.
 */
bool fwrite_iff(FILE *fp)
{
  unsigned int size = htonl(4);
  char tag[4];
  
  if (fread(tag, 1, 4, fp) != 4 && !feof(fp)) {
    return true;
  }
  if (is_iff_tag(tag, "FORM")) {
    if (fseek(fp, 4, SEEK_CUR) != 0) {
      return true;
    }
    if (fread(tag, 1, 4, fp) != 4) {
      return true;
    }
    if (!is_iff_tag(tag, IFF_TAG_ALBERTA)) {
      return true;
    }
    if (fseek(fp, -12, SEEK_CUR) != 0) {
      return true;
    }
    return false;
  } else if (is_iff_tag(tag, IFF_TAG_ALBERTA)) {
    if (fseek(fp, -12, SEEK_CUR) != 0) {
      return true;
    }
    return fwrite_iff(fp);
  }

  /* ok, start a new FORM */
  if (fwrite("FORM", 1, 4, fp) != 4) {
    return true;
  }
  if (fwrite(&size, 4, 1, fp) != 1) {
    return true;
  }
  if (fwrite(IFF_TAG_ALBERTA, 1, 4, fp) != 4) {
    return true;
  }
  if (fseek(fp, -12, SEEK_CUR) != 0) {
    return true;
  }

  return false;
}

/* Open the named filename which must be an ALBERTA IFF file. Position
 * the file pointer on the FORM record. FILENAME will be created when
 * it does not exist. If APPEND, do not destroy the old data.
 */
FILE *fopen_iff(const char *filename, bool append)
{
  FILE *fp;
  
  if ((fp = fopen(filename, append ? "ab+" : "wb+")) == NULL) {
    ERROR("Cannot open file '%s' for writing.\n", filename);
    return NULL;
  }
  if (fseek(fp, 0, SEEK_SET) != 0) {
    fclose(fp);
    return NULL;
  }
  if (fwrite_iff(fp) == true) {
    fclose(fp);
    return NULL;
  }
  return fp;
}

/* Correct the size of the enclosing record, which must be a "FORM".
 * On entry the file must be positioned at the FORM record.
 */
bool fterm_iff(FILE *fp)
{
  char tag[4];
  unsigned int size;

  if (fread(tag, 1, 4, fp) != 4) {
    return true;
  }
  if (!is_iff_tag(tag, "FORM")) {
    return true;
  }
  if (fread(&size, 4, 1, fp) != 1) {
    return true;
  }
  if (fread(tag, 1, 4, fp) != 4) {
    return true;
  }
  if (!is_iff_tag(tag, IFF_TAG_ALBERTA)) {
    return true;
  }
  
  if (memcmp(tag, "FORM", 4) != 0) {
    return true;
  }
  size = htonl(size + 8); /* ADTA header + size */
  if (fwrite(&size, 4, 1, fp) != 1) {
    return true;
  }

  return false;
}

bool fclose_iff(FILE *fp)
{
  bool result = fterm_iff(fp);
  fclose(fp);
  return result;
}

/* Write an AMSH record to an IFF file. On entry, the file must point
 * to the ADTA record. On return, the file will still be positioned at
 * the ADTA record. Obviously, all this can only work with seekable
 * files.
 */
bool fwrite_mesh_iff(MESH *mesh, REAL time, FILE *fp)
{
  long int pos, mshpos, mshend;
  char tag[4];
  unsigned int size, mshsize;
  
  if ((pos = ftell(fp)) == -1) {
    return true;
  }

  if (fread(tag, 1, 4, fp) != 4) {
    return true;
  }
  if (!is_iff_tag(tag, "FORM")) {
    return true;
  }
  if (fread(&size, 4, 1, fp) != 1) {
    return true;
  }
  size = ntohl(size);
  if (fread(tag, 1, 4, fp) != 4) {
    return true;
  }
  if (!is_iff_tag(tag, IFF_TAG_ALBERTA)) {
    return true;
  }
  if (fseek(fp, size-4, SEEK_CUR) != 0) {
    return true;
  }
  if ((mshpos = ftell(fp)) == -1) {
    return true;
  }
  if (fwrite(IFF_TAG_MESH, 1, 4, fp) != 4) {
    return true;
  }
  if (fwrite("SIZE", 1, 4, fp) != 4) { /* correct that later */
    return true;
  }
  fwrite_mesh_xdr(mesh, fp, time);
  if ((mshend = ftell(fp)) == -1) {
    return true;
  }
  mshsize = mshend - mshpos - 8;
  
  size += mshsize + 8;
  mshsize = htonl(mshsize);

  if (fseek(fp, mshpos+4, SEEK_SET) != 0) {
    return true;
  }
  if (fwrite(&mshsize, 4, 1, fp) != 1) { /* correct size */
    return true;
  }
  
  if (fseek(fp, pos+4, SEEK_SET) != 0) {
    return true;
  }
  if (fwrite(&size, 4, 1, fp) != 1) { /* correct size */
    return true;
  }
  if (fseek(fp, pos, SEEK_SET) != 0) {
    return true;
  }
  
  return false;
}


/* Write an AVEC record to an IFF file. On entry, the file must point
 * to the ADTA record. On return, the file will still be positioned at
 * the ADTA record. Obviously, all this can only work with seekable
 * files.
 */
bool fwrite_dof_vec_master_iff(const DOF_VEC *vec,
			       const char *ifftag, const char *type, FILE *fp)
{
  long int pos, vecpos, vecend;
  char tag[4];
  unsigned int size, vecsize;
  
  if ((pos = ftell(fp)) == -1) {
    return true;
  }

  if ((pos = ftell(fp)) == -1) {
    return true;
  }

  if (fread(tag, 1, 4, fp) != 4) {
    return true;
  }
  if (!is_iff_tag(tag, "FORM")) {
    return true;
  }
  if (fread(&size, 4, 1, fp) != 1) {
    return true;
  }
  size = ntohl(size);
  if (fread(tag, 1, 4, fp) != 4) {
    return true;
  }
  if (!is_iff_tag(tag, IFF_TAG_ALBERTA)) {
    return true;
  }
  if (fseek(fp, size-4, SEEK_CUR) != 0) {
    return true;
  }

  if ((vecpos = ftell(fp)) == -1) {
    return true;
  }
  if (fwrite(ifftag, 1, 4, fp) != 4) {
    return true;
  }
  if (fwrite("SIZE", 1, 4, fp) != 4) { /* correct that later */
    return true;
  }
  if (fwrite_dof_vec_master(true, vec, fp, type)) {
    return true;
  }
  if ((vecend = ftell(fp)) == -1) {
    return true;
  }
  vecsize = vecend - vecpos - 8;
  
  size += vecsize + 8;
  vecsize = htonl(vecsize);

  if (fseek(fp, vecpos+4, SEEK_SET) != 0) {
    return true;
  }
  if (fwrite(&vecsize, 4, 1, fp) != 1) { /* correct size */
    return true;
  }
  
  if (fseek(fp, pos+4, SEEK_SET) != 0) {
    return true;
  }
  if (fwrite(&size, 4, 1, fp) != 1) { /* correct size */
    return true;
  }
  if (fseek(fp, pos, SEEK_SET) != 0) {
    return true;
  }
  
  return false;
}

bool fwrite_dof_real_vec_iff(const DOF_REAL_VEC *v, FILE *fp) 
{
  return fwrite_dof_vec_master_iff((const DOF_VEC *)v,
				   IFF_TAG_REAL_VEC, "DOF_REAL_VEC    ", fp);
}
bool fwrite_dof_real_d_vec_iff(const DOF_REAL_D_VEC *v, FILE *fp) 
{
  return fwrite_dof_vec_master_iff((const DOF_VEC *)v,
				   IFF_TAG_REAL_D_VEC, "DOF_REAL_D_VEC  ", fp);
}
bool fwrite_dof_int_vec_iff(const DOF_INT_VEC *v, FILE *fp) 
{
  return fwrite_dof_vec_master_iff((const DOF_VEC *)v,
				   IFF_TAG_INT_VEC, "DOF_INT_VEC     ", fp);
}
bool fwrite_dof_schar_vec_iff(const DOF_SCHAR_VEC *v, FILE *fp) 
{
  return fwrite_dof_vec_master_iff((const DOF_VEC *)v,
				   IFF_TAG_SCHAR_VEC, "DOF_SCHAR_VEC    ", fp);
}
bool fwrite_dof_uchar_vec_iff(const DOF_UCHAR_VEC *v, FILE *fp) 
{
  return fwrite_dof_vec_master_iff((const DOF_VEC *)v,
				   IFF_TAG_UCHAR_VEC, "DOF_UCHAR_VEC    ", fp);
}
#endif
