/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     read_mesh.c                                                    */
/*                                                                          */
/* description:  reading data of mesh and vectors in machine independent    */
/*               and native binary 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)                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
/* to_do :  number of dofs depending on node                                */
/* nach read_macro, damit dort pointer auf mel, v vorhanden sind!!!         */
/*    (fuers naechste Schreiben)                                            */
/*  error handling is terrible                                              */
/*--------------------------------------------------------------------------*/

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

#include <string.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;

typedef enum dv_type_enum {
  __DOF_REAL_VEC = 0,
  __DOF_REAL_D_VEC,
  __DOF_REAL_VEC_D,
  __DOF_INT_VEC,
  __DOF_SCHAR_VEC,
  __DOF_UCHAR_VEC,
} DV_TYPE_ENUM;

static DOF_VEC *read_dof_vec_master(DV_TYPE_ENUM dv_type,
				    DOF_VEC *dv,
				    MESH *mesh, FE_SPACE *fe_space,
				    bool expect_next);

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

  /* Now, what to do with RESULT? ;) */
  return nread;
}

bool_t AI_xdr_int(XDR *xdr, void *ip)
{
  FUNCNAME("AI_xdr_int");

  TEST_EXIT(sizeof(int) == 4,
	    "sizeof(int) != 4, please start hacking!");
#if HAVE_XDR_INT32_T
  return (xdr_int32_t(xdr,(int *)ip));
#elif HAVE_XDR_INT
  return (xdr_int(xdr,(int *)ip));
#endif
}

bool_t AI_xdr_REAL(XDR *xdr, void *rp)
{ 
  FUNCNAME("AI_xdr_REAL");

  TEST_EXIT(sizeof(double) == sizeof(REAL),
	    "sizeof(double) != sizeof(REAL), please start hacking!");
  return (xdr_double(xdr,(double *)rp));
}

bool_t AI_xdr_U_CHAR(XDR *xdr, void *ucp)
{ 
  return (xdr_u_char(xdr,(u_char *)ucp));
}

bool_t AI_xdr_S_CHAR(XDR *xdr, void *cp)
{ 
  return (xdr_char(xdr,(char *)cp));
}

bool_t AI_xdr_DOF(XDR *xdr, void *dp)
{ 
  FUNCNAME("AI_xdr_DOF");

  TEST_EXIT(sizeof(DOF) == sizeof(int),
	    "sizeof(DOF) != sizeof(int), please start hacking!");
  return (AI_xdr_int(xdr,(int *)dp));
}

#if 1
/* Needed to read old mesh-files. */
static int read_xdr_file(void *file, void *buffer, unsigned int size)
{
  return (int)fread(buffer, 1, (size_t)size, (FILE *)file);
} 

static int write_xdr_file(void *file, void *buffer, unsigned int size)
{
  return fwrite(buffer, (size_t)size, 1, (FILE *)file) == 1 ? (int)size : 0;
}
#endif

XDR *AI_xdr_fopen(FILE *fp, enum xdr_op mode)
{
  FUNCNAME("AI_xdr_open_file");
  XDR *xdr;

  if (!(xdr = MEM_ALLOC(1,XDR))) { 
    ERROR("can't allocate memory for xdr pointer.\n");
    return NULL;
  }

  xdrstdio_create(xdr, file = fp, mode);
#if 0
  /* only needed for old meshes, this function is now integrated into
   * the version check
   */
  xdrrec_create(xdr, 65536, 65536, (caddr_t) file, 
		(int (*)())read_xdr_file, (int (*)())write_xdr_file);
    
  xdr->x_op = mode;
  xdr->x_public = (caddr_t)file;
    
  if (mode == XDR_DECODE) {
    xdrrec_skiprecord(xdr);
  }
#endif

  return xdr;
}

XDR *AI_xdr_open_file(const char *fn, enum xdr_op mode)
{
  if ((file = fopen(fn, (mode == XDR_DECODE) ? "r": "w"))) {
    return AI_xdr_fopen(file, mode);
  }
  return NULL;
}

bool AI_xdr_close(XDR *xdr)
{
  FUNCNAME("AI_xdr_close");
  if (!xdr) {
    ERROR("NULL xdr pointer.\n");
    return false;
  }

//TODO: still needed? see quick hack concerning the retrying of
//reading the alberta version
#if 0
  if (xdr->x_op == XDR_ENCODE) {
    xdrrec_endofrecord(xdr, 1);
  }
#endif

  xdr_destroy(xdr);
 
  MEM_FREE(xdr,1,XDR);

  return true;
}

bool AI_xdr_close_file(XDR *xdr)
{
  FUNCNAME("AI_xdr_close_file");

  if (!AI_xdr_close(xdr)) {
    return false;
  }

  if (fclose(file)) {
    ERROR("error closing file.\n");
  }
  return true;
}

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

/*   void _AI_read_REAL() */
void _AI_read_REAL(REAL *val)
{
  bool result;

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

void _AI_read_int(int *val)
{
  bool result;

  if (xdrp)
    result = AI_xdr_int(xdrp, val);
  else
    result = fread(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 read_long(long *val)
{
  bool result;

  if (xdrp)
    result = xdr_long(xdrp, val);
  else
    result = fread(val, sizeof(long int), 1, file) == 1;

  /* Now, what to do with RESULT? ;) */
  (void)result;
}
#endif

void _AI_read_string(char *string, int strileng)
{
  bool result;

  if (xdrp) {
    result = xdr_string(xdrp, &string, strileng+1);
  } else {
    result = fread(string, sizeof(char), strileng+1, file) == (strileng+1);
  }

  /* Now, what to do with RESULT? ;) */
  (void)result;
}

void _AI_read_var_string(char **string)
{ 
  bool result;
  int strileng;

  _AI_read_int(&strileng);
  if (strileng) {
    *string = (char *)MEM_ALLOC(strileng+1, char);
  }
  if (xdrp) {
    result = xdr_string(xdrp, string, strileng+1);
  } else {
    result = fread(*string, sizeof(char), strileng+1, file) == (strileng+1);
  }

  /* Now, what to do with RESULT? ;) */
  (void)result;
}

void _AI_read_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 = fread(start, size, n, file) == n;

  /* Now, what to do with RESULT? ;) */
  (void)result;
}

void _AI_read_U_CHAR(U_CHAR *val)
{
  bool result;

  if (xdrp)
    result = AI_xdr_U_CHAR(xdrp, val);
  else
    result = fread(val, sizeof(U_CHAR), 1, file) == 1;

  /* Now, what to do with RESULT? ;) */
  (void)result;
}

void _AI_read_S_CHAR(S_CHAR *val)
{
  bool result;

  if (xdrp)
    result = AI_xdr_S_CHAR(xdrp, val);
  else
    result = fread(val, sizeof(S_CHAR), 1, file) == 1;

  /* Now, what to do with RESULT? ;) */
  (void)result;
}

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

static int        n_vert_dofs;
static DOF        **vert_dofs;

static int        n_edge_dofs;
static DOF        **edge_dofs;

static int        n_face_dofs;
static DOF        **face_dofs;

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

/****************************************************************************/
/* read_dofs(mesh, dof_ptr, type): Read a DOF vector of type "type" from    */
/* the file. Allocate space for nonzero DOF_entries (which are overwritten  */
/* with the indices from the file).                                         */
/****************************************************************************/

static void read_dofs(MESH *mesh, DOF **dof_ptr, int type)
{
  int        n_dof = mesh->n_dof[type];
  DOF        temp_dofs[n_dof];
  DOF_ADMIN *admin;
  int        i, j, n, n0;

  _AI_read_vector(temp_dofs, n_dof, sizeof(DOF), (xdrproc_t)AI_xdr_DOF); 
  *dof_ptr = AI_get_dof_memory(mesh, type);

  for (i = 0; i < mesh->n_dof_admin; i++) {
    admin = mesh->dof_admin[i];
    
    n  = admin->n_dof[type];
    n0 = admin->n0_dof[type];
    
    TEST_EXIT(n+n0 <= n_dof,
	      "dof_admin \"%s\": n=%d, n0=%d too large: ndof=%d\n",
	      admin->name, n, n0, n_dof);
    
    for (j = 0; j < n; j++) {
      (*dof_ptr)[n0+j] = temp_dofs[n0+j];
    }
  }
}

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

static EL *read_el_recursive(MESH *mesh, EL *parent, int level)
{
  FUNCNAME("read_el_recursive");
  int dim = mesh->dim;
  int    i, j, n, node0;
  EL     *el;
  U_CHAR uc, nc;

  el = get_element(mesh);
  mesh->n_hier_elements++;

#if ALBERTA_DEBUG
  el->index = mesh->n_hier_elements;
#endif
 
  _AI_read_U_CHAR(&uc); 

  if (dim > 1) {
    _AI_read_U_CHAR(&nc);  
    if (nc) {
	el->new_coord = get_real_d(mesh);
	
	_AI_read_vector(el->new_coord, DIM_OF_WORLD, sizeof(REAL), 
			(xdrproc_t)AI_xdr_REAL);
    }
    else 
      el->new_coord = NULL;
  }

  if (mesh->n_dof[VERTEX] > 0) {
    node0 = mesh->node[VERTEX];
    for (i = 0; i < N_VERTICES(dim); i++) {
      _AI_read_int(&j);

      TEST_EXIT(j < n_vert_dofs,
	"vert_dofs index too large: %d >= %d\n", j, n_vert_dofs);
      el->dof[node0 + i] = vert_dofs[j];
    }
  }

  if (dim > 1 && mesh->n_dof[EDGE] > 0) {
    node0 = mesh->node[EDGE];
    for (i = 0; i < N_EDGES(dim); i++) {
      _AI_read_int(&j);
      
      TEST_EXIT(j < n_edge_dofs,
	"edge_dofs index too large: %d >= %d\n", j, n_edge_dofs);
      if (j >= 0)
	el->dof[node0 + i] = edge_dofs[j];
    }
  }
  
  if (dim == 3 && (n = mesh->n_dof[FACE]) > 0) {
    node0 = mesh->node[FACE];
    for (i = 0; i < N_FACES_3D; i++) {
      _AI_read_int(&j);
      
      TEST_EXIT(j < n_face_dofs,
	"face_dofs index too large: %d >= %d\n", j, n_face_dofs);
      if (j >= 0)
	el->dof[node0 + i] = face_dofs[j];
    }
  }
  
  if ((n = mesh->n_dof[CENTER]) > 0) {
    node0 = mesh->node[CENTER];
    
    read_dofs(mesh, el->dof + node0, CENTER);
  }

  if (uc) {
    el->child[0] = read_el_recursive(mesh, el, level+1);
    el->child[1] = read_el_recursive(mesh, el, level+1);
  }
  else
    mesh->n_elements++;

  return el;
}

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

static void read_dof_admins(MESH *mesh)
{
  FUNCNAME("read_dof_admins");
  int       i, n_dof_admin, iadmin, used_count;
  int       n_dof_el, n_dof[N_NODE_TYPES], n_node_el, node[N_NODE_TYPES];
  int       a_n_dof[N_NODE_TYPES];
  U_CHAR    flags;
  char      *name;
  DOF_ADMIN *admin;

 
  _AI_read_int(&n_dof_el);
  _AI_read_vector(n_dof, N_NODE_TYPES, sizeof(int), (xdrproc_t)AI_xdr_int);
  _AI_read_int(&n_node_el);
  _AI_read_vector(node, N_NODE_TYPES, sizeof(int), (xdrproc_t)AI_xdr_int);
  /* use data later for check */
  
  _AI_read_int(&n_dof_admin); 
  for (iadmin = 0; iadmin < n_dof_admin; iadmin++) {
    _AI_read_vector(a_n_dof, N_NODE_TYPES, sizeof(int), (xdrproc_t)AI_xdr_int); 
    _AI_read_int(&used_count);

    _AI_read_var_string(&name);

    _AI_read_U_CHAR(&flags);

    admin = AI_get_dof_admin(mesh, name, a_n_dof);
    admin->flags = (FLAGS)flags;

    MEM_FREE(name, strlen(name)+1, char);

    if (used_count > 0) {
      enlarge_dof_lists(admin, used_count);
    }
    admin->used_count = used_count;
  } /* end for (iadmin) */

  for (i = 0; i < N_NODE_TYPES; i++) {
    if (mesh->n_dof[i]) {
      AI_get_dof_list(mesh, i);
    }
  }

  AI_get_dof_ptr_list(mesh);

  TEST(mesh->n_dof_el == n_dof_el,"wrong n_dof_el: %d %d\n", 
				   mesh->n_dof_el, n_dof_el);
  for (i = 0; i < N_NODE_TYPES; i++)
    TEST(mesh->n_dof[i] == n_dof[i],"wrong n_dof[%d]: %d %d\n",
				     i, mesh->n_dof[i], n_dof[i]);
  TEST(mesh->n_node_el == n_node_el,"wrong n_node_el: %d %d\n",
				     mesh->n_node_el, n_node_el);
  for (i = 0; i < N_NODE_TYPES; i++)
    TEST(mesh->node[i] == node[i],"wrong node[%d]: %d %d\n",
                                   i, mesh->node[i], node[i]); 
  return;
}

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

static MESH *
read_mesh_master(REAL *timeptr,
		 NODE_PROJECTION *(*init_node_proj)(MESH *, MACRO_EL *, int),
		 MESH *master){
  FUNCNAME("read_mesh_master");
  MACRO_EL       *mel;
  int            i, j, n;
  REAL_D         *v;
  int            neigh_i[N_NEIGH_MAX];
  char           *name, s[sizeof(ALBERTA_VERSION)];
  int            dim, iDIM_OF_WORLD, ne, nv;
  REAL           time;
  REAL_D         diam, bbox[2];
  int            n_vert, n_elements, n_hier_elements;
  int            n_edges;
  int            *vert_i, *mel_vertices = NULL;
  static int     funccount=0;
  int            n_faces, max_edge_neigh;
  S_CHAR         is_periodic;
  static NODE_PROJECTION id_proj = { NULL };
  MESH           *mesh = NULL;
/*
#define ALBERTA_VERSION  "ALBERTA: Version 1.2 "
#define ALBERTA_VERSION  "ALBERTA: Version 1.00"
#define ALBERTA_VERSION  "ALBERTA: Version 2.0 "
#define ALBERTA_VERSION  "ALBERTA: Version 2.1 "
*/
  int major, minor;
  char *vers;

  _AI_read_string(s, sizeof(ALBERTA_VERSION)-1);

  if (memcmp(s, ALBERTA_MAGIC, sizeof(ALBERTA_MAGIC)-1) != 0) {
    /* try and read again, hoping the xdr pointer is still at the
     * right position. ugly, but works.
     */
    WARNING("Invalid file id: \"%s\", expected \""ALBERTA_MAGIC"X.YZ\"\n", s);
    MSG("Retrying in ALBERTA-1.2 compatibility mode ...\n");
    AI_xdr_close(xdrp);

    /*   xdrrec_endofrecord(xdrp, 1); */
    
    rewind( file /*stream*/);

    if (!(xdrp = MEM_ALLOC(1,XDR))) { 
      ERROR("can't allocate memory for xdr pointer.\n");
      return NULL;
    }

    xdrstdio_create(xdrp, file, XDR_DECODE);
    xdrrec_create(xdrp, 65536, 65536, (void *)file, 
		  (int (*)())read_xdr_file, (int (*)())write_xdr_file);
      
    xdrp->x_op = XDR_DECODE;
    xdrp->x_public = (caddr_t)file;
    
    if (XDR_DECODE)
      xdrrec_skiprecord(xdrp);
    /*MSG("retry...\n");*/
    _AI_read_string(s, sizeof(ALBERTA_VERSION)-1);
    if (memcmp(s, ALBERTA_MAGIC, sizeof(ALBERTA_MAGIC)-1) != 0) {
      ERROR("failed... AGAIN!\nabort...\n");
      goto error_exit;
    } else {
      /*MSG("this time it works.\n");*/
    }
  }
  vers = s + sizeof(ALBERTA_MAGIC)-1;
  major = (int)(*vers++ - '0');
  if (major < 1 || major > 2) {
    ERROR("Unsupported ALBERTA major version: %d\n", major);
    goto error_exit;
  }
  if (*vers++ != '.') {
    ERROR("Invalid file id: \"%s\", expected \""ALBERTA_MAGIC"X.YZ\"\n", s);
    ERROR("*** vers: '%c'\n", vers[-1]);
    goto error_exit;
  }
  minor = (int)(*vers++ - '0');
  if (minor < 0 || minor > 9) {
    ERROR("Invalid file id: \"%s\", expected \""ALBERTA_MAGIC"X.YZ\"\n", s);
    ERROR("*** vers: '%c'\n", vers[-1]);
    goto error_exit;
  }
  if (*vers != ' ') {
    if (major == 2 && minor == 0 && (int)*vers == 0) {
#if ALBERTA_DEBUG
      /* V2.0 versions are one character too short */
      WARNING("ALBERTA V2.0 bug detected, working around it.\n");
#endif
    } else {
      if (*vers < '0' || *vers > '9') {
	ERROR("Invalid file id: \"%s\", expected \""ALBERTA_MAGIC"X.YZ\"\n", s);
	ERROR("*** vers: '%c' (%d)\n", *vers, (int)*vers);
	goto error_exit;
      }
      minor *= 10;
      minor += (int)(*vers - '0');
    }
  }

#if 1
  if (major < 2) {
#define SUP12 1
#if SUP12
    if (major == 1 && minor == 2) {
      return _AI_read_mesh_1_2(timeptr);
    }
#endif
#if !SUP12
    ERROR("ALBERTA V1 compatibility mode not yet implemented, sorry\n");
    goto error_exit;
#endif
  }
#endif
  if (major >= 2) {
    if (major > 2 || minor > 3) {
      ERROR("Post-V2.3 ALBERTA file version detected (V%d.%d), aborting\n",
	    major, minor);
      goto error_exit;
    }
  }
  
  _AI_read_int(&dim);
  if (dim > DIM_MAX) { 
    ERROR("dim == %d is greater than DIM_MAX == %d!\n", dim, DIM_MAX);
    goto error_exit; 
  } 

  _AI_read_int(&iDIM_OF_WORLD); 
  if (iDIM_OF_WORLD != DIM_OF_WORLD) { 
    ERROR("wrong DIM_OF_WORLD %d. abort.\n", iDIM_OF_WORLD); 
    goto error_exit; 
  } 

  _AI_read_REAL(&time);
  if (timeptr) *timeptr = time;

  _AI_read_int(&i);                         /* length without terminating \0 */
  if (i) {
    name = MEM_ALLOC(i+1, char);
    _AI_read_string(name, i);  
  }
  else {
    funccount++;
    i=100;
    name = MEM_ALLOC(i+1, char);
    sprintf(name, "READ_MESH%d", funccount);
  }
  
  _AI_read_int(&n_vert);
  if (dim > 1) {
    _AI_read_int(&n_edges);
  }

  _AI_read_int(&n_elements);
  _AI_read_int(&n_hier_elements);

  if (dim == 3) {
    _AI_read_int(&n_faces);
    _AI_read_int(&max_edge_neigh);
  }

  mesh = GET_MESH(dim, name, NULL, NULL, NULL);

  _AI_read_S_CHAR(&is_periodic);
  mesh->is_periodic = is_periodic != 0;
  if (mesh->is_periodic) {
    _AI_read_int(&mesh->per_n_vertices);
    if (dim > 1) {
      _AI_read_int(&mesh->per_n_edges);
      if (dim > 2) {
	_AI_read_int(&mesh->per_n_faces);
      }
    }
    _AI_read_int(&mesh->n_wall_trafos);
    if (mesh->n_wall_trafos) {
      int wt;

      mesh->wall_trafos = MEM_ALLOC(mesh->n_wall_trafos, AFF_TRAFO *);
      ((AFF_TRAFO **)mesh->wall_trafos)[0] =
	MEM_ALLOC(mesh->n_wall_trafos, AFF_TRAFO);
      for (wt = 0; wt < mesh->n_wall_trafos; wt++) {
	((AFF_TRAFO **)mesh->wall_trafos)[wt] = mesh->wall_trafos[0]+wt;
	_AI_read_vector(mesh->wall_trafos[wt],
		    SQR(DIM_OF_WORLD) + DIM_OF_WORLD, sizeof(REAL),
		    (xdrproc_t)AI_xdr_REAL);
      }
    }
  }

  _AI_read_vector(mesh->bbox, 2*DIM_OF_WORLD, sizeof(REAL), (xdrproc_t)AI_xdr_REAL);
  AXPBY_DOW(1.0, mesh->bbox[1], -1.0, mesh->bbox[0], mesh->diam);
  
  read_dof_admins(mesh);
  
  MEM_FREE(name, i+1, char);

  _AI_read_int(&n_vert_dofs);

  if (n_vert_dofs > 0) {
    vert_dofs = MEM_ALLOC(n_vert_dofs, DOF *); 
    n = mesh->n_dof[VERTEX]; 
    for (i = 0; i < n_vert_dofs; i++) {
      vert_dofs[i] = _AI_get_dof(mesh, VERTEX, false);
      
      _AI_read_vector(vert_dofs[i], n, sizeof(DOF), (xdrproc_t)AI_xdr_DOF); 
    }
  } 

  if (dim > 1) {
    _AI_read_int(&n_edge_dofs);
    
    if (n_edge_dofs > 0) {
      edge_dofs = MEM_ALLOC(n_edge_dofs, DOF *); 
      
      for (i = 0; i < n_edge_dofs; i++) {
	read_dofs(mesh, edge_dofs + i, EDGE);
      }
    } 
  }

  if (dim == 3) { 
    _AI_read_int(&n_face_dofs);
    
    if (n_face_dofs > 0) {
      face_dofs = MEM_ALLOC(n_face_dofs, DOF *); 

      for (i = 0; i < n_face_dofs; i++) {
	read_dofs(mesh, face_dofs + i, FACE);
      }
    } 
  }

  /* After we have read all DOFs we make sure that we have allocated
   * the correct number of DOFs for each admin.
   */
  for (i = 0; i < mesh->n_dof_admin; i++) {
    _AI_allocate_n_dofs(mesh->dof_admin[i], mesh->dof_admin[i]->used_count);
  }

  _AI_read_int(&ne);
  _AI_read_int(&nv); 

  ((MESH_MEM_INFO *)mesh->mem_info)->count = nv;
  v = ((MESH_MEM_INFO *)mesh->mem_info)->coords = MEM_ALLOC(nv, REAL_D);

  for (i = 0; i < nv; i++) {
     _AI_read_vector(v[i], DIM_OF_WORLD, sizeof(REAL), (xdrproc_t)AI_xdr_REAL);
  }

  for (j = 0; j < DIM_OF_WORLD; j++) { 
    bbox[0][j] =  1.E30; 
    bbox[1][j] = -1.E30; 
  } 

  for (i = 0; i < nv; i++) {
    for (j = 0; j < DIM_OF_WORLD; j++) {
      bbox[0][j] = MIN(bbox[0][j], v[i][j]); 
      bbox[1][j] = MAX(bbox[1][j], v[i][j]); 
    }
  }

  AXPBY_DOW(1.0, bbox[1], -1.0, bbox[0], diam);

  mel  = MEM_CALLOC(ne, MACRO_EL); 

  mesh->n_macro_el = ne; 
  mesh->macro_els = mel;

  vert_i = mel_vertices = MEM_ALLOC(ne*N_VERTICES(dim), int);

  for (n = 0; n < ne; n++) {
    mel[n].index = n; 

    _AI_read_vector(vert_i, N_VERTICES(dim), sizeof(int), (xdrproc_t)AI_xdr_int);
    
    for (i = 0; i < N_VERTICES(dim); i++) { 
      if (*vert_i >= 0 && *vert_i < nv) {
	mel[n].coord[i] = v + *vert_i++;
      } else {
#if 1
	ERROR("Macro element vertex numbering is garbled");
	goto error_exit;
#else
	/* Error case???? */
	mel[n].coord[i] = NULL;
#endif
      }
    }

    if (major == 2 && minor == 0) {
      S_CHAR dummy[N_VERTICES(dim)];
      _AI_read_vector(dummy, N_VERTICES(dim), sizeof(S_CHAR),
		  (xdrproc_t)AI_xdr_S_CHAR);
    }
    _AI_read_vector(mel[n].wall_bound, N_WALLS(dim), sizeof(BNDRY_TYPE),
		(xdrproc_t)AI_xdr_S_CHAR);
#if DIM_MAX > 2
    if (dim == 3 && major == 2 && minor == 0) {
      S_CHAR dummy[N_EDGES_3D];
      _AI_read_vector(dummy, N_EDGES_3D, sizeof(S_CHAR), (xdrproc_t)AI_xdr_S_CHAR);
    }
#endif

    _AI_read_vector(neigh_i, N_NEIGH(dim), sizeof(int), (xdrproc_t)AI_xdr_int);
   
    if (init_node_proj) {
      mel[n].projection[0] = init_node_proj(mesh, mel + n, 0);
    }
    for (i = 0; i < N_NEIGH(dim); i++)  { 
      if (init_node_proj && (dim > 1)) {
	mel[n].projection[i+1] = init_node_proj(mesh, mel + n, i+1);
      }
      
      if ((neigh_i[i] >= 0) && (neigh_i[i] < ne)) {
	mel[n].neigh[i] = mel + (neigh_i[i]); 
      } else {
	mel[n].neigh[i] = NULL; 
      }
    }

    _AI_read_vector(mel[n].opp_vertex, N_NEIGH(dim), sizeof(S_CHAR), 
		(xdrproc_t)AI_xdr_S_CHAR);

    /* V2.0 compatibility code. */
    if (major == 2 && minor == 0) {
      if (true) {
	for (i = 0; i < N_NEIGH(dim); i++)  { 
	  _AI_read_vector(mel[n].neigh_vertices[i],
		      N_VERTICES(dim-1), sizeof(S_CHAR), 
		      (xdrproc_t)AI_xdr_S_CHAR);
	}
      }
      if (mesh->is_periodic) {
	int wt_i[N_WALLS_MAX];
	_AI_read_vector(wt_i, N_WALLS(dim), sizeof(int), (xdrproc_t)xdr_int);
	for (i = 0; i < N_WALLS(dim); i++) {
	  if (wt_i[i] != -1) {
	    mel[n].wall_trafo[i] = mesh->wall_trafos[wt_i[i]];
	  } else {
	    mel[n].wall_trafo[i] = NULL;
	  }
	}
      }
    } 

#if DIM_MAX >= 3
    if (dim == 3) {
      _AI_read_U_CHAR(&(mel[n].el_type));            
      mel[n].orientation = AI_get_orientation_3d(&mel[n]);
    }
#endif

    if (major == 2 && minor >= 2) {
      /* file-version V2.2 */
      S_CHAR pr_status;
      int mst_idx;

      for (i = 0; i < 1+N_WALLS(dim); i++) {
	_AI_read_S_CHAR(&pr_status);
	if (init_node_proj == NULL) {
	  /* application specified projection binding has precedence. */
	  if (pr_status) {
	    mel[n].projection[i] = &id_proj;
	  }
	}
      }
      _AI_read_int(&mst_idx);
      _AI_read_S_CHAR(&mel[n].master.opp_vertex);
      if (master) {
	mel[n].master.macro_el = &master->macro_els[mst_idx];
      }
    }

    /* >= V2.1 format */
    if (major == 2 && minor >= 1) {
      if (mesh->is_periodic) {
	int wt_i[N_WALLS_MAX];

	for (i = 0; i < N_NEIGH(dim); i++)  { 
	  _AI_read_vector(mel[n].neigh_vertices[i],
		      N_VERTICES(dim-1), sizeof(S_CHAR), 
		      (xdrproc_t)AI_xdr_S_CHAR);
	}
	_AI_read_vector(wt_i, N_WALLS(dim), sizeof(int), (xdrproc_t)xdr_int);
	for (i = 0; i < N_WALLS(dim); i++) {
	  if (wt_i[i] != -1) {
	    mel[n].wall_trafo[i] = mesh->wall_trafos[wt_i[i]];
	  } else {
	    mel[n].wall_trafo[i] = NULL;
	  }
	}
      } else {
	/* Don't forget to set periodic info to invalid values (i.e. -1) */
	memset(mel[n].neigh_vertices, -1, sizeof(mel[n].neigh_vertices));
      }
    }

    mel[n].el = read_el_recursive(mesh, NULL, 0);
  }

  /* Generate the boundary classification for all lower-dimensional
   * sub-simplices (vertices, in 3d: also edges).
   */
  _AI_fill_bound_info(mesh, mel_vertices, nv, ne, false);

/****************************************************************************/
/* The present mechanism only reads DOFs from the file which were used by   */
/* at least one DOF_ADMIN. The missing element DOF pointers are filled by   */
/* this routine (in memory.c).                                              */
/****************************************************************************/
  if (dim > 0) {
    AI_fill_missing_dofs(mesh);
  }

  if (n_elements != mesh->n_elements) { 
    ERROR("n_elements != mesh->n_elements.\n"); 
    goto error_exit; 
  } 

  if (n_hier_elements != mesh->n_hier_elements) 
  { 
    ERROR("n_hier_elements != mesh->n_hier_elements.\n"); 
    goto error_exit; 
  } 
  

  if (mesh->n_dof[VERTEX])
  {
    if (n_vert != n_vert_dofs) 
    { 
      ERROR("n_vertices == %d != %d == n_vert_dofs.\n", n_vert, n_vert_dofs); 
      mesh->n_vertices = n_vert_dofs; 
      goto error_exit; 
    }
  }
  mesh->n_vertices = n_vert;

  if (dim > 1)
    mesh->n_edges = n_edges;

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

  for (i=0; i < DIM_OF_WORLD; i++)  {
    if (ABS(mesh->diam[i]-diam[i]) > (mesh->diam[i]/10000.0)) {
      WARNING("diam[%i] != mesh->diam[%i]. Parametric mesh?\n", i, i);
      /* goto error_exit; */ /* nope, mesh could be parametric */
    }
  }
 
  /* Read the magic cookie. */
  _AI_read_int((int *)&mesh->cookie);

  for (;;) {
    _AI_read_string(s, 4);  

    /* file-version >= V2.2 */
    if (major == 2 && minor >= 2 && memcmp(s, "LAPA", 4) == 0) {
      S_CHAR not_all, use_reference;
      DOF_REAL_D_VEC *coords;
      DOF_UCHAR_VEC  *touched_edges = NULL;
      DOF_PTR_VEC    *edge_pr;
      /* PARAM_STRATEGY */ int strategy;
      FLAGS flags;

      _AI_read_S_CHAR(&not_all); /* not_all */
      _AI_read_S_CHAR(&use_reference); /* use_reference_mesh */
      _AI_read_int(&strategy);
      coords = (DOF_REAL_D_VEC *)
	read_dof_vec_master(__DOF_REAL_D_VEC, NULL, mesh, NULL, false);
      if (not_all && coords->fe_space->bas_fcts->degree > 1) {
	touched_edges = (DOF_UCHAR_VEC *)
	  read_dof_vec_master(__DOF_UCHAR_VEC, NULL, mesh, NULL, false);
      }

      flags = strategy;
      if (mesh->is_periodic &&
	  (coords->fe_space->admin->flags & ADM_PERIODIC) != 0) {
	flags |= PARAM_PERIODIC_COORDS;
      }

      /* BUG: the "selective" feature will not really work. But so what. */
      use_lagrange_parametric(mesh,
			      coords->fe_space->bas_fcts->degree,
			      NULL /* i.e. not selective */,
			      flags);
      
      dof_copy_d(coords, get_lagrange_coords(mesh));
      free_dof_real_d_vec(coords);

      edge_pr =  get_lagrange_edge_projections(mesh);
      if (edge_pr != NULL) {
        static const NODE_PROJECTION dummy_projection = { NULL, };
	TEST_EXIT(touched_edges != NULL,
		  "No touched-edges vector?\n");
	FOR_ALL_DOFS(touched_edges->fe_space->admin, {
	    edge_pr->vec[dof] =
              touched_edges->vec[dof] ? (void *)&dummy_projection : NULL;
	  });
	free_dof_uchar_vec(touched_edges);

        /* Fill in the real projection pointers using a mesh loop. Is
         *   there a better (more efficient) way?
         */
        TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL|FILL_MACRO_WALLS) {
          /* 1d: edge_projection refers to the "bulk" projection, if
           * only vertices (i.e. walls) are projected, then the
           * element stays affine.
           *
           * 2d: edge_projection refers to the bulk and wall
           * projections, where the edges are the walls.
           *
           * 3d: edge_projection is slightly more complicated.
           */
          switch (mesh->dim) {
          case 1: {
            int node_c     = mesh->node[CENTER];
            int n0_edge_pr = edge_pr->fe_space->admin->n0_dof[CENTER];
            DOF edge_dof   = el_info->el->dof[node_c][n0_edge_pr];

            if (edge_pr->vec[edge_dof] != NULL) {
              edge_pr->vec[edge_dof] = (void *)wall_proj(el_info, -1);
            }
            break;
          }
          case 2: {
            int node_e = mesh->node[EDGE];
            int n0_edge_pr = edge_pr->fe_space->admin->n0_dof[EDGE];

	    int e;
            for (e = 0; e < N_EDGES_2D; n++) {
              DOF edge_dof = el_info->el->dof[node_e+e][n0_edge_pr];

              if (edge_pr->vec[edge_dof] != NULL) {
                const NODE_PROJECTION *act_proj = wall_proj(el_info, e);
                if (act_proj == NULL) {
                  act_proj = wall_proj(el_info, -1);
                }
                if (act_proj) {
                  edge_pr->vec[edge_dof] = (void *)act_proj;
                }
              }
            }
            break;
          }
          case 3: {
            int node_e     = mesh->node[EDGE];
            int n0_edge_pr = edge_pr->fe_space->admin->n0_dof[EDGE];

            int e;
            for (e = 0; e < N_EDGES_3D; ++e) {
              DOF edge_pr_dof = el_info->el->dof[node_e+e][n0_edge_pr];

              if (edge_pr->vec[edge_pr_dof] != NULL) {
                /* Look for a projection function that applies to edge e. */
                const NODE_PROJECTION *act_proj;

                act_proj = wall_proj(el_info, face_of_edge_3d[e][0]);
                if (!act_proj) {
                  act_proj = wall_proj(el_info, face_of_edge_3d[e][1]);
                }
                if (!act_proj) {
                  act_proj = wall_proj(el_info, -1);
                }

                if (act_proj != NULL) {
                  /* replace by actual projection, but keep the
                   * projected status if that was stored on disk.
                   */
                  edge_pr->vec[edge_pr_dof] = (void *)act_proj;
                }
              }
            }
            break;
          }
          } /* end switch (mesh->dim) */
        } TRAVERSE_NEXT();
      }

      continue;
    }

    /* file-version >= V2.3 */
    if (major == 2 && minor >= 2 && memcmp(s, "TMSH", 4) == 0) {
      int trace_id;
      MESH *slave;
      _AI_read_int((int *)&trace_id);
      /* This is a trace mesh, read it in */
      slave = read_mesh_master(NULL, init_node_proj, mesh);
      slave->trace_id = trace_id;
      ((MESH_MEM_INFO *)mesh->mem_info)->next_trace_id = trace_id+1;

      continue;
    }

    if (memcmp(s, "EOF.", 4) == 0) {
      break;
    }

    ERROR("no FILE END MARK.\n"); 
    goto error_exit; 
  }
  
  if (master) {
    bind_submesh(master, mesh, NULL, NULL);
  }

  return mesh;
  
error_exit:
  free_mesh(mesh);
  if (mel_vertices) {
    MEM_FREE(mel_vertices, nv, int);
  }
  
  return NULL;
}

MESH *
fread_mesh_xdr(FILE *fp, REAL *timeptr,
	       NODE_PROJECTION *(*init_node_proj)(MESH *, MACRO_EL *, int),
	       MESH *master)
{
  FUNCNAME("fread_mesh_xdr");
  MESH *mesh;
  
  /* NOTE: xdrp is global */
  if ((xdrp = AI_xdr_fopen(fp, XDR_DECODE)) == NULL) {
    ERROR("Cannot convert file pointer to XDR handle\n");
    return NULL;
  }
  file = fp;

  mesh = read_mesh_master(timeptr, init_node_proj, master);

  AI_xdr_close(xdrp);

  xdrp = NULL; /* do not close fp! */
  file = NULL;

  return mesh;
}

MESH *read_mesh_xdr(const char *fn, REAL *timeptr,
		    NODE_PROJECTION *(*init_node_proj)(MESH *, MACRO_EL *, int),
		    MESH *master)
{
  FUNCNAME("read_mesh_xdr");
  MESH *mesh;
  FILE *fp;

  if ((fp = fopen(fn, "rb")) == NULL) {
    ERROR("Cannot open file '%s'\n", fn);
    return NULL;
  }

  mesh = fread_mesh_xdr(fp, timeptr, init_node_proj, master);

  if (mesh) {
    MSG("File %s read.\n", fn);
  }

  fclose(fp);

  return mesh;
}

MESH *fread_mesh(FILE *fp, REAL *timeptr,
		 NODE_PROJECTION *(*init_node_proj)(MESH *, MACRO_EL *, int),
		 MESH *master)
{
  MESH *mesh;
  file = fp;
  xdrp = NULL;
  
  mesh = read_mesh_master(timeptr, init_node_proj, master);

  file = NULL; /* but do not close the file */

  return mesh;
}

MESH *read_mesh(const char *fn, REAL *timeptr,
		NODE_PROJECTION *(*init_node_proj)(MESH *, MACRO_EL *, int),
		MESH *master)
{
  FUNCNAME("fread_mesh");
  MESH *mesh;
  FILE *fp;
  
  if ((fp = fopen(fn, "rb")) == NULL) {
    ERROR("Cannot open file '%s'\n", fn);
    return NULL;
  }

  mesh = fread_mesh(fp, timeptr, init_node_proj, master);

  fclose(fp);

  if (mesh) {
    MSG("File %s read.\n", fn);
  }

  return mesh;
}

/*--------------------------------------------------------------------------*/
/* read DOF vectors of various types                                        */
/*--------------------------------------------------------------------------*/

typedef void (*ADD_TO_ADM_FCT)(DOF_VEC *dv, const DOF_ADMIN *admin);
typedef DOF_VEC *(*GET_DV_FCT)(const char *name, const FE_SPACE *fesp);

static struct dv_read_struct {
  const char     *name;
  ADD_TO_ADM_FCT add_to_admin;
  GET_DV_FCT     get_dof_vec;
  xdrproc_t      item_proc;
  size_t         item_size;
  int            stride;
} dv_read_table[] = {
  /* [__DOF_REAL_VEC] = */ {
    "DOF_REAL_VEC    ",
    (ADD_TO_ADM_FCT)add_dof_real_vec_to_admin,
    (GET_DV_FCT)get_dof_real_vec,
    (xdrproc_t)AI_xdr_REAL, sizeof(REAL), 1,
  },
  /* [__DOF_REAL_D_VEC] = */ {
    "DOF_REAL_D_VEC  ",
    (ADD_TO_ADM_FCT)add_dof_real_d_vec_to_admin,
    (GET_DV_FCT)get_dof_real_d_vec,
    (xdrproc_t)AI_xdr_REAL, sizeof(REAL), DIM_OF_WORLD,
  },
  /* [__DOF_REAL_VEC_D] = */ {
    /* dummy, will redirect to __DOF_REAL_VEC or __DOF_REAL_VEC_D */
    "DOF_REAL_VEC_D  ",
  },
  /* [__DOF_INT_VEC] = */ {
    "DOF_INT_VEC     ",
    (ADD_TO_ADM_FCT)add_dof_int_vec_to_admin,
    (GET_DV_FCT)get_dof_int_vec,
    (xdrproc_t)AI_xdr_int, sizeof(int), 1,
  },
  /* [__DOF_SCHAR_VEC] = */ {
    "DOF_SCHAR_VEC    ",
    (ADD_TO_ADM_FCT)add_dof_schar_vec_to_admin,
    (GET_DV_FCT)get_dof_schar_vec,
    (xdrproc_t)AI_xdr_S_CHAR, sizeof(S_CHAR), 1,
  },
  /* [__DOF_UCHAR_VEC] = */ {
    "DOF_UCHAR_VEC    ",
    (ADD_TO_ADM_FCT)add_dof_uchar_vec_to_admin,
    (GET_DV_FCT)get_dof_uchar_vec,
    (xdrproc_t)AI_xdr_U_CHAR, sizeof(U_CHAR), 1,
  },
};

static DOF_VEC *read_dof_vec_master(DV_TYPE_ENUM dv_type,
				    DOF_VEC *dv,
				    MESH *mesh, FE_SPACE *fe_space,
				    bool expect_next)
{
  FUNCNAME("read_dof_vec_master");
  struct dv_read_struct *rd_table;
  int             i, last, stride, rdim;
  int             n_dof[N_NODE_TYPES];
  unsigned int    cookie;
  const DOF_ADMIN *admin = NULL;
  const BAS_FCTS  *bas_fcts;
  char            *name, *dv_name;
  char            ss[17]; /* 17 = length of dofvectype with terminating \0 */
  char            *s = ss;
  U_CHAR          flags_and_stride = 0, flags;
  bool            do_free_fe_space = false;
  int             old_version = 0;

  TEST_EXIT(mesh, "no mesh given\n");

  if (dv_type == __DOF_REAL_VEC_D || dv_type == __DOF_REAL_D_VEC) {
    rdim = DIM_OF_WORLD;
  } else {
    rdim = 1;
  }

  TEST_EXIT(fe_space == NULL || fe_space->rdim == rdim,
	    "The range dimension %d of the given fe-space does not match "
	    "the range dimension %d of the stored DOF-vector.\n",
	    fe_space->rdim, rdim);

  _AI_read_string(s, 16);
  
  
  
  if (dv_type == __DOF_REAL_VEC_D) {
    if (memcmp(s, dv_read_table[__DOF_REAL_D_VEC].name, 12) != 0 &&
	memcmp(s, dv_read_table[__DOF_REAL_VEC].name, 12) != 0) {
      ERROR("invalid file id; %s\n", s);
      return NULL;
    }
  } else {
    if (memcmp(s, dv_read_table[dv_type].name, 12) != 0) {
    	
    	/*  if (memcmp(s, ALBERTA_MAGIC, sizeof(ALBERTA_MAGIC)-1) != 0) { */
    	/* try and read again, hoping the xdr pointer is still at the right
    	 * position. ugly, but works.
    	 */
    	    WARNING("Invalid file id: \"%s\"\n", s);
            MSG("Retrying in ALBERTA-1.2 compatibility mode ...\n");
    	    AI_xdr_close(xdrp);

    	/*    xdrrec_endofrecord(xdrp, 1); */
    	    
    	    rewind( file /*stream*/);

    	    if (!(xdrp = MEM_ALLOC(1,XDR))) { 
    	      ERROR("can't allocate memory for xdr pointer.\n");
    	      return NULL;
    	    }

    	    xdrstdio_create(xdrp, file, XDR_DECODE);
    	    xdrrec_create(xdrp, 65536, 65536, (void *)file, 
    			  (int (*)())read_xdr_file, (int (*)())write_xdr_file);
    	      
    	    xdrp->x_op = XDR_DECODE;
    	    xdrp->x_public = (caddr_t)file;
    	    
    	    if (XDR_DECODE)
    	      xdrrec_skiprecord(xdrp);
    	    /*MSG("retry...\n");*/
    	    _AI_read_string(s, 16);
    	    if (memcmp(s, dv_read_table[dv_type].name, 12) != 0) {
    	      ERROR("failed... AGAIN!\nabort...\n");
    	      return NULL;
    	    } else {
    	      /*MSG("this time it works.\n");*/
    	      old_version = 1;
    	    }
    }
  }

  _AI_read_int(&last);  /* length of vector name */ 
  dv_name = MEM_ALLOC(last+1, char);
  _AI_read_string(dv_name, last);  

  if (!old_version) {
    _AI_read_U_CHAR(&flags_and_stride);
  } else {
    /* Goodness, preserve_coarse_dofs formerly was tied to the
     * mesh. How to handle this? Do it this way: as the flag was tied
     * to the mesh, the compat-read-mesh-code has already set the flag
     * in all DOF_ADMINs stored in the mesh file. So simply have a
     * look at the first admin (there always is one with DOFs at
     * vertices), if that one has the flags set, then all ADMINs must
     * have the flag set and we pretend that also this DOF_VECTOR
     * needs it.
     */
    TEST_EXIT(mesh->n_dof_admin != 0, "Not a single DOF_ADMIN in the mesh???\n");
    if (mesh->dof_admin[0]->flags & ADM_PRESERVE_COARSE_DOFS) {
      flags_and_stride = ADM_PRESERVE_COARSE_DOFS;
    }
  }
  stride = (flags_and_stride & 0x80) ? DIM_OF_WORLD : 1;
  flags  = flags_and_stride & ADM_FLAGS_MASK;

  if (memcmp(s, dv_read_table[__DOF_REAL_D_VEC].name, 12) == 0 &&
      stride == 1) {
    WARNING("Stride-mismatch, assuming pre-2.2 DOF_REAL_D_VEC\n");
    stride = DIM_OF_WORLD;
  }

  if (dv_type == __DOF_REAL_VEC_D) {
    /* Switch to either DOF_REAL_VEC or DOF_REAL_D_VEC, based on the
     * "stride" vlaue.
     */
    dv_type = stride != 1 ? __DOF_REAL_D_VEC : __DOF_REAL_VEC;
  }

  rd_table = &dv_read_table[dv_type];

  
  if (old_version) {
	  _AI_read_vector(n_dof, mesh->dim + 1, sizeof(int), (xdrproc_t)AI_xdr_int);
	  _AI_match_node_types(n_dof);
	  for (i = mesh->dim + 1; i < N_NODE_TYPES; i++)
		  n_dof[i] = 0;
  } else {
	  _AI_read_vector(n_dof, N_NODE_TYPES, sizeof(int), (xdrproc_t)AI_xdr_int);
  }
  
  _AI_read_int(&last); /* length of BAS_FCTS name */ 
  
  if (last) {
    name = MEM_ALLOC(last+1, char);
    _AI_read_string(name, last);    

    if (fe_space && (bas_fcts = fe_space->bas_fcts)) {
      if (strcmp(bas_fcts->name, name) != 0) {
	ERROR("invalid name \"%s\" is not given fe_space->bas_fcts->name %s\n",
	      name, bas_fcts->name);
      }
    } else  { /* no given fe_space or no bas_fcts in given fe_space */
      bas_fcts = get_bas_fcts(mesh->dim, name);
      TEST_EXIT(bas_fcts != NULL, "cannot get bas_fcts <%s>\n", name);

      if (fe_space) { /* use given fe_space */
	TEST_EXIT(bas_fcts->rdim <= fe_space->rdim,
		  "Request for vector valued basis functions <%s> with "
		  "scalar fe-space.\n", bas_fcts->name);
	fe_space->bas_fcts = bas_fcts;
      } else {        /* create new fe_space */
	fe_space = (FE_SPACE *)get_fe_space(mesh, name, bas_fcts, rdim, flags);
	TEST_EXIT(fe_space != NULL,
		  "cannot get fe_space for bas_fcts <%s>\n", name);
	do_free_fe_space = true;
      }
    }
    for (i = 0; i < N_NODE_TYPES; i++) {
      TEST_EXIT(n_dof[i] == bas_fcts->n_dof[i],
		"wrong n_dof in bas_fcts <%s>\n", name);
    }
  } else  {  /* no bas_fcts.name in file */
    if (fe_space) { /* use given fe_space */
      TEST_EXIT(admin = fe_space->admin, "no fe_space->admin");
      for (i = 0; i < N_NODE_TYPES; i++) {
	TEST_EXIT(n_dof[i] == admin->n_dof[i],
		  "wrong n_dof in admin <%s>\n", NAME(admin));
      }
    } else { /* create new fe_space */
      fe_space = (FE_SPACE *)get_dof_space(mesh, NULL, n_dof, flags);
      TEST_EXIT(fe_space != NULL, "cannot get fe_space for given n_dof\n");
      TEST_EXIT(admin = fe_space->admin, "no admin in new fe_space\n");
      for (i = 0; i < N_NODE_TYPES; i++) {
	TEST_EXIT(n_dof[i] == admin->n_dof[i],
	  "wrong n_dof in admin <%s>\n", NAME(admin));
      }
      do_free_fe_space = true;
    }
  }
  TEST_EXIT(fe_space,"still no fe_space\n");
  TEST_EXIT(admin = fe_space->admin, "still no admin\n");

  dof_compress(mesh);
 
  if (dv == NULL) {
    dv = dv_read_table[dv_type].get_dof_vec(dv_name, fe_space);
  }
  if (dv->name) {
    free((char *)dv->name);
  }
  dv->name = dv_name;

  _AI_read_int(&last);
  if (last != admin->size_used) {
    ERROR("size of dof vector `%s' == %d "
	  "does not fit to size_used == %d in admin `%s'\n",
	  dv->name, last, admin->size_used, admin->name);
    ERROR_EXIT("cannot read incompatible data\n");
  }

  if (last) {
    _AI_read_vector(
      dv->vec, last*rd_table->stride, rd_table->item_size, rd_table->item_proc);
  } else {
    ERROR("empty dof vector\n");
    dv->size = 0;
    dv->vec = NULL;
  }

  /* Read the magic cookie. */
  if (!old_version) {
	  _AI_read_int((int *)&cookie);

	  if (cookie != mesh->cookie) {
		  WARNING("Mesh and DOF vector do not seem to match!\n");
	  }
  }
  _AI_read_string(s, 4);
  if (memcmp(s, "EOF.", 4) != 0 &&
      (!expect_next || CHAIN_SINGLE(fe_space) || memcmp(s, "NEXT", 4) != 0)) {
    ERROR("no FILE END MARK.\n"); 
  }

  if (do_free_fe_space) {
    free_fe_space(fe_space);
  }

  return dv;
}

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

static inline
DOF_VEC *fread_dof_vec_master(bool do_xdr,
			      FILE *fp, MESH *mesh, FE_SPACE *fe_space,
			      DV_TYPE_ENUM dv_type)
{
  FUNCNAME("fread_dof_vec_master");
  DOF_VEC *dv_chain;
  DOF_VEC *dv;
  
  if (do_xdr) {
    if (!(xdrp = AI_xdr_fopen(fp, XDR_DECODE))) {
      ERROR("Cannot convert file handle to XDR handle\n");
      return NULL;
    }
  }
  file = fp;

  dv = read_dof_vec_master(dv_type, NULL, mesh, fe_space, true /* next? */);

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

  fe_space = (FE_SPACE *)dv->fe_space;

  /* more to read? */
  CHAIN_FOREACH(fe_space, dv->fe_space, FE_SPACE) {
    dv_chain = CHAIN_NEXT(dv, DOF_VEC);
    read_dof_vec_master(dv_type, dv_chain, mesh, fe_space,
			CHAIN_NEXT(fe_space, FE_SPACE) == dv->fe_space
			? false : true);
  }

  if (xdrp) {
    AI_xdr_close(xdrp);
  }

  xdrp = NULL;
  file = NULL;

  return dv;
}

static inline
DOF_VEC *file_read_dof_vec_master(bool do_xdr,
				  const char *fn,
				  MESH *mesh, FE_SPACE *fe_space,
				  DV_TYPE_ENUM dv_type)
{
  FUNCNAME("file_read_dof_vec_master");
  FILE *fp;
  DOF_VEC *dv;
  
  if (!(fp = fopen(fn, "rb"))) {
    ERROR("Cannot open file '%s'\n",fn);
    return NULL;
  }

  dv = fread_dof_vec_master(do_xdr, fp, mesh, fe_space, dv_type);

  fclose(fp);

  if (dv) {
    MSG("File '%s' read.\n",fn);
  }

  return dv;
}

/******************************************************************************/

#define DEFUN_READ_DOF_VEC(TYPE, type)					\
  DOF_##TYPE *								\
  read_dof_##type##_xdr(const char *fn, MESH *mesh, FE_SPACE *fe_space) \
  {									\
    return (DOF_##TYPE *)						\
      file_read_dof_vec_master(true, fn, mesh, fe_space, __DOF_##TYPE);	\
  }									\
  DOF_##TYPE *								\
  read_dof_##type(const char *fn, MESH *mesh, FE_SPACE *fe_space)	\
  {									\
    return (DOF_##TYPE *)						\
      file_read_dof_vec_master(false, fn, mesh, fe_space, __DOF_##TYPE); \
  }									\
  DOF_##TYPE *								\
  fread_dof_##type##_xdr(FILE *fp, MESH *mesh, FE_SPACE *fe_space)	\
  {									\
    return (DOF_##TYPE *)						\
      fread_dof_vec_master(true, fp, mesh, fe_space, __DOF_##TYPE);	\
  }									\
  DOF_##TYPE *								\
  fread_dof_##type(FILE *fp, MESH *mesh, FE_SPACE *fe_space)		\
  {									\
    return (DOF_##TYPE *)						\
      fread_dof_vec_master(false, fp, mesh, fe_space, __DOF_##TYPE);	\
  }									\
  struct _AI_semicolon_dummy

DEFUN_READ_DOF_VEC(REAL_VEC, real_vec);
DEFUN_READ_DOF_VEC(REAL_D_VEC, real_d_vec);
DEFUN_READ_DOF_VEC(REAL_VEC_D, real_vec_d);
DEFUN_READ_DOF_VEC(INT_VEC, int_vec);
DEFUN_READ_DOF_VEC(SCHAR_VEC, schar_vec);
DEFUN_READ_DOF_VEC(UCHAR_VEC, uchar_vec);

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

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

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

/* Sequentially suck in an IFF file, store the data type in tag, and
 * return the size inf SIZE. When positioned at FORM, skip to ADTA,
 * when positioned at ADTA, skip to the first data record and read it
 * its tag into TAG and its size into SIZE. When positioned at a
 * data-record, read its tag into TAG and its size into SIZE.
 *
 * If "true" is returned and *size == -1 then we have an IO error. If
 * "true" is returned and *size >= 0 then it is no ALBERTA tag, but
 * still this may be a valid IFF-tag. If "false" is returned we have
 * hit one of the known tags and positioned at an ALBERTA data-record.
 */
bool fread_iff(FILE *fp, char tag[4], unsigned int *size)
{
  *size = -1;
  
  if (fread(tag, 1, 4, fp) != 4) {
    return true;
  }
  if (is_iff_tag(tag, "FORM")) {
    if (fread(size, 4, 1, fp) != 1) {
      *size = -1;
      return true;
    }
    *size = ntohl(*size);
    if (fread(tag, 1, 4, fp) != 4) {
      *size = -1;
      return true;
    }
    if (!is_iff_tag(tag, IFF_TAG_ALBERTA)) {
      *size -= 4; /* amout to skip */
      return true;
    }
    if (fread(tag, 1, 4, fp) != 4) {
      *size = -1;
      return true;
    }
  }
  if (fread(size, 4, 1, fp) != 1) {
    *size = -1;
    return true;
  }
  *size = ntohl(*size);

  if (is_iff_tag(tag, IFF_TAG_MESH) ||
      is_iff_tag(tag, IFF_TAG_REAL_VEC) ||
      is_iff_tag(tag, IFF_TAG_REAL_D_VEC) ||
      is_iff_tag(tag, IFF_TAG_INT_VEC) ||
      is_iff_tag(tag, IFF_TAG_UCHAR_VEC) ||
      is_iff_tag(tag, IFF_TAG_SCHAR_VEC)) {

    return false; /* !!success */
  }
  return true;
}

FILE *read_iff(const char *filename, char tag[4], unsigned int *size)
{
  FILE *fp;
  
  if ((fp = fopen(filename, "rb")) == NULL) {
    ERROR("Cannot open file '%s' for reading.\n", filename);
    return NULL;
  }
  if (fread_iff(fp, tag, size) == true) {
    fclose(fp);
    return NULL;
  }
  return fp;
}

#endif
