/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     read_mesh_xdr.c                                                */
/*                                                                          */
/* description:  reading data of mesh and vectors in machine-independent    */
/*               binary format                                              */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  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.alberta-fem.de/                                              */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*  (c) by A. Schmidt and K.G. Siebert (1996-2004)                          */
/*                                                                          */
/*     This program is free software; you can redistribute it and/or modify */
/*     it under the terms of the GNU General Public License as published by */
/*     the Free Software Foundation; either version 2 of the License, or    */
/*     any later version.                                                   */
/*                                                                          */
/*     This program is distributed in the hope that it will be useful,      */
/*     but WITHOUT ANY WARRANTY; without even the implied warranty of       */
/*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        */
/*     GNU General Public License for more details.                         */
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
/* 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                                              */
/*--------------------------------------------------------------------------*/

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

#include <string.h>
#include <rpc/types.h>
#include <rpc/xdr.h>
#include "alberta_intern.h"
#include "alberta.h"

/*
   XDR-Routinen zur Uebertragung von ALBERTA-Datentypen

   muessen bei abgeaenderter Definition auch veraendert werden !!!

   akt. Def.:  REAL   = double
               U_CHAR = unsigned char
               S_CHAR = signed char
               DOF    = int
*/

#include "alberta_intern.h"

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

static DOF_ADMIN  *admin = NULL;
static MESH       *mesh = NULL;
static U_CHAR     preserve_coarse_dofs;

static int        n_vert_dofs;
static DOF        **vert_dofs;

#if DIM_MAX > 1
static int        n_edge_dofs;
static DOF        **edge_dofs;
#endif


#if 0
#if DIM_MAX == 2
#define OLD_EDGE   CENTER
#define OLD_CENTER EDGE
#endif

#if DIM_MAX == 3
#define OLD_EDGE   CENTER
#define OLD_FACE   EDGE
#define OLD_CENTER FACE
#endif
#endif


#if DIM_MAX == 3
static int        n_face_dofs;
static DOF        **face_dofs;
#endif

static EL *read_el_recursive(EL *el);
static void read_dof_admins_xdr(MESH *mesh, bool preserve_coarse);

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

MESH *_AI_read_mesh_1_2(REAL *timeptr)
{
  FUNCNAME("read_mesh_1_2");
  MACRO_EL       *mel;
  int            i, j, n;
  REAL_D         *v, x_min, x_max;
  int            neigh_i[N_NEIGH_MAX];
  char           *name, s[sizeof(ALBERTA_VERSION)];
  int            iDIM, iDIM_OF_WORLD, ne, nv;
  REAL           time, diam[DIM_OF_WORLD];
  int            n_vertices, n_elements, n_hier_elements;
#if DIM_MAX > 1
  int            n_edges;
#endif
  int            *vert_i;
  int            *mel_vertices;
  static int     funccount=0;
#if DIM_MAX==3
  int        n_faces, max_edge_neigh;
#endif

  _AI_read_int(&iDIM);
  
#if 0
  if (iDIM != DIM_MAX) 
  { 
    ERROR("wrong DIM %d. abort.\n", iDIM); 
    goto error_exit; 
  } 
#else
  if (iDIM > DIM_MAX) { 
    ERROR("dim == %d is greater than DIM_MAX == %d!\n", iDIM, DIM_MAX);
    goto error_exit; 
  } 
#endif

  _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_vertices);

#if DIM_MAX > 1
  _AI_read_int(&n_edges);
#endif
  _AI_read_int(&n_elements);
  _AI_read_int(&n_hier_elements);

#if DIM_MAX == 3
  
  _AI_read_int(&n_faces);
  _AI_read_int(&max_edge_neigh);

#endif

  _AI_read_vector((char *)diam, DIM_OF_WORLD, sizeof(REAL),
	     (xdrproc_t)AI_xdr_REAL);
  _AI_read_U_CHAR(&preserve_coarse_dofs);

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

  read_dof_admins_xdr(mesh, preserve_coarse_dofs);

  MEM_FREE(name, i+1, char);
  /* mesh->parametric = NULL;     */

  _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] = get_dof(mesh, VERTEX); 
      
      _AI_read_vector((char *)vert_dofs[i], n, sizeof(DOF),
		 (xdrproc_t) AI_xdr_DOF); 
    }
  } 

#if DIM_MAX > 1
  _AI_read_int(&n_edge_dofs);

  if (n_edge_dofs > 0) 
  {
    edge_dofs = MEM_ALLOC(n_edge_dofs, DOF *); 
    n = mesh->n_dof[EDGE]; 
    for (i = 0; i < n_edge_dofs; i++) 
    { 
      edge_dofs[i] = get_dof(mesh, EDGE); 
     
      _AI_read_vector((char *)edge_dofs[i], n, sizeof(DOF),
		 (xdrproc_t) AI_xdr_DOF); 
    }
  } 
#endif

#if DIM_MAX==3 
 
  _AI_read_int(&n_face_dofs);

  if (n_face_dofs > 0) 
  {
    face_dofs = MEM_ALLOC(n_face_dofs, DOF *); 
    n = mesh->n_dof[FACE]; 
    for (i = 0; i < n_face_dofs; i++) 
    {
      face_dofs[i] = get_dof(mesh, FACE); 
     
      _AI_read_vector((char *)face_dofs[i], n, sizeof(DOF),
		 (xdrproc_t) AI_xdr_DOF);
    } 
  } 
#endif 

  _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((char *)v[i], DIM_OF_WORLD, sizeof(REAL),
		(xdrproc_t) AI_xdr_REAL);

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

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

  for (j = 0; j < DIM_OF_WORLD; j++) 
    mesh->diam[j] = x_max[j] - x_min[j]; 


  mel  = MEM_CALLOC(ne, MACRO_EL); 

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

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

  for (n = 0; n < ne; n++) 
  {
    mel[n].index = n;
    memset(mel[n].neigh_vertices, -1, sizeof(mel[n].neigh_vertices));

    _AI_read_vector((char *)vert_i, N_VERTICES(iDIM), sizeof(int),
	       (xdrproc_t)xdr_int);
    
    for (i = 0; i < N_VERTICES(iDIM); i++) 
    { 
      if ((*vert_i >= 0) && (*vert_i < nv)) 
	mel[n].coord[i] = v + *vert_i++;
      else 
	mel[n].coord[i] = NULL; 
    } 

/* vertex- and edge_bound are set by _AI_fill_bound_info, therefore we
 * just read dummies.
 */
#if 1
    if (iDIM == 1) {
      _AI_read_vector(
	(char *)mel[n].wall_bound, N_VERTICES(iDIM), sizeof(S_CHAR), 
	(xdrproc_t) AI_xdr_S_CHAR);
#if DIM_MAX > 1
    } else {
      S_CHAR dummy[N_VERTICES_MAX];
      _AI_read_vector(
	(char *)dummy, N_VERTICES(iDIM), sizeof(S_CHAR), 
	(xdrproc_t) AI_xdr_S_CHAR);
#endif
    }

#if DIM_MAX >= 2
    if (iDIM == 2) {
      _AI_read_vector(
		 (char *)mel[n].wall_bound, N_EDGES(iDIM), sizeof(S_CHAR),
		 (xdrproc_t) AI_xdr_S_CHAR);
    }
#endif

#if DIM_MAX >= 3
    if (iDIM == 3) {
      S_CHAR      bound_sc[N_FACES(iDIM)+N_EDGES(iDIM)];
      _AI_read_vector(bound_sc, (N_FACES(iDIM)+N_EDGES(iDIM)),
		 sizeof(S_CHAR), (xdrproc_t) AI_xdr_S_CHAR);
      for (i = 0; i < N_FACES(iDIM); i++) {
	mel[n].wall_bound[i] = bound_sc[i];
      }
    }
#endif
#endif
/******************************************************************************/
    
    _AI_read_vector((char *)neigh_i, N_NEIGH(iDIM), sizeof(int),
	       (xdrproc_t) xdr_int);
   
    for (i = 0; i < N_NEIGH(iDIM); i++) 
    { 
      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((char *)mel[n].opp_vertex, N_NEIGH(iDIM), sizeof(U_CHAR),
	       (xdrproc_t) AI_xdr_U_CHAR);

#if DIM_MAX==3 
    _AI_read_U_CHAR(&(mel[n].el_type));            

    {
      mel[n].orientation = AI_get_orientation_3d(&mel[n]);
    }
#endif 
    mel[n].el = read_el_recursive(NULL);
  }

  /* 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 (iDIM > 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_vertices != n_vert_dofs) 
    { 
      ERROR("n_vertices != n_vert_dofs.\n"); 
      mesh->n_vertices = n_vert_dofs; 
      goto error_exit; 
    }
  }
  mesh->n_vertices = n_vertices;

#if DIM_MAX > 1
  mesh->n_edges = n_edges;
#endif

#if DIM_MAX == 3
  mesh->n_faces = n_faces;

  mesh->max_edge_neigh = max_edge_neigh;
#endif

  for (i=0; i<DIM_OF_WORLD; i++) 
  {
    if (ABS(mesh->diam[i]-diam[i]) > (mesh->diam[i]/10000.0))
    {
      ERROR("diam[%i] != mesh->diam[%i].\n",i,i);
      goto error_exit; 
    }
  }

  name = s;
  _AI_read_string(name, sizeof(s)-1);  
  if (strncmp(s, "EOF.", 4))                    /* file end marker */
  { 
    ERROR("no FILE END MARK.\n"); 
    goto error_exit; 
  } 

 error_exit:
  
  return mesh;
}

/*--------------------------------------------------------------------------*/
/****************************************************************************/
/* In ALBERTA_VERSION 1.2 the NODE_TYPES are defined as follows:            */
/*                                                                          */
/* #if DIM == 1                                                             */
/* #define VERTEX        0                                                  */
/* #define CENTER        1                                                  */
/* #endif                                                                   */
/*                                                                          */
/* #if DIM == 2                                                             */
/* #define VERTEX        0                                                  */
/* #define EDGE          1                                                  */
/* #define CENTER        2                                                  */
/* #endif                                                                   */
/*                                                                          */
/* #if DIM == 3                                                             */
/* #define VERTEX        0                                                  */
/* #define EDGE          1                                                  */
/* #define FACE          2                                                  */
/* #define CENTER        3                                                  */
/* #endif                                                                   */
/*                                                                          */
/* But in our new ALBERTA_VERSION the NODE_TYPES are defined as follows:    */
/*                                                                          */
/* enum node_types {                                                        */
/*   VERTEX = 0,                                                            */
/*   CENTER,                                                                */
/*   EDGE,                                                                  */
/*   FACE,                                                                  */
/*   N_NODE_TYPES                                                           */
/* };                                                                       */
/*                                                                          */
/* So there is something to be done to "match_node_types"                   */
/*                                                                          */
/****************************************************************************/
void _AI_match_node_types(int *node_vec)
{
#if DIM_MAX == 1
  return;
#elif DIM_MAX == 2
  int tmp = node_vec[CENTER];
  node_vec[CENTER] = node_vec[EDGE];
  node_vec[EDGE] = tmp;
#elif DIM_MAX == 3
  int tmp = node_vec[CENTER];
  node_vec[CENTER] = node_vec[FACE];
  node_vec[FACE] = node_vec[EDGE];
  node_vec[EDGE] = tmp;
#endif
}

static void
read_dof_admins_xdr(MESH *mesh, bool preserve_coarse_dofs)
{
  FUNCNAME("read_dof_admins_xdr");
  int  i, n_dof_admin, iadmin, used_count;

  int  n_dof_el, n_dof[N_NODE_TYPES] = {0,};
  int  n_node_el, node[N_NODE_TYPES] = {0,};
  int  a_n_dof[N_NODE_TYPES] = {0,};
  char *name;
  int  dim = mesh->dim;
 
  _AI_read_int(&n_dof_el);
  _AI_read_vector((char *)n_dof, dim+1, sizeof(int), (xdrproc_t) xdr_int);
  _AI_match_node_types(n_dof); /* might be correct */
  
  _AI_read_int(&n_node_el);
  _AI_read_vector((char *)node, dim+1, sizeof(int), (xdrproc_t) xdr_int);
#if 0
  _AI_match_node_types(node); /* this one stinks */
#else
  /* mesh->node[] is the index of the first vertex, ... etc. DOF. As
   * the ordering of the nodes has changed, there is more to do than
   * do a simple reordering of the array. We actually only used to
   * utilize the stored values for a consistency check. Therefore I
   * choose to ignore the stored values altogether.
   */
#endif
  /* use data later for check */
  
  _AI_read_int(&n_dof_admin); 
  for (iadmin = 0; iadmin < n_dof_admin; iadmin++)
  {
    _AI_read_vector((char *)a_n_dof, dim+1, sizeof(int), 
	       (xdrproc_t) xdr_int); 
    _AI_match_node_types(a_n_dof); /* probably correct here ... */
    _AI_read_int(&used_count);

    _AI_read_int(&i);                /* length without terminating \0 */
    name = MEM_ALLOC(i+1, char);
    _AI_read_string(name, i);  

    admin = AI_get_dof_admin(mesh, name, a_n_dof);
    admin->flags = preserve_coarse_dofs ? ADM_PRESERVE_COARSE_DOFS : 0U;
    name = NULL;

    if (used_count > 0) enlarge_dof_lists(admin, 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<=dim; 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);
#if 0
  for (i=0; i<=dim; i++)
    TEST(mesh->node[i] == node[i],
	 "wrong node[%d]: %d %d\n", i, mesh->node[i], node[i]); 
#endif

  return;
}

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


static EL *read_el_recursive(EL *parent)
{
  FUNCNAME("read_el_recursive");
  int    i, j, n, node0;
  EL     *el;
  U_CHAR uc;
#if DIM_MAX > 1
  U_CHAR nc;
#endif

  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_MAX > 1
  _AI_read_U_CHAR(&nc);  
  if (nc) 
  {
    el->new_coord = get_real_d(mesh);
    
    _AI_read_vector((char *)el->new_coord, DIM_OF_WORLD, sizeof(REAL),
	       (xdrproc_t) AI_xdr_REAL);
  }
  else 
  {
    el->new_coord = NULL;
  }
#endif  

  if (mesh->n_dof[VERTEX] > 0)
  {
    node0 = mesh->node[VERTEX];
    for (i = 0; i < N_VERTICES(mesh->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 ((!uc) || preserve_coarse_dofs)
  {
#if DIM_MAX > 1
    if (mesh->n_dof[EDGE] > 0)
    {
      node0 = mesh->node[EDGE];
      for (i = 0; i < N_EDGES(mesh->dim); i++)
      {
        _AI_read_int(&j);

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

#if DIM_MAX == 3
    if ((n = mesh->n_dof[FACE]) > 0)
    {
      node0 = mesh->node[FACE];
      for (i = 0; i < N_FACES(mesh->dim); i++)
      {
        _AI_read_int(&j);

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

    if ((n = mesh->n_dof[CENTER]) > 0)
    {
      node0 = mesh->node[CENTER];
      el->dof[node0] = get_dof(mesh, CENTER);
      
      _AI_read_vector((char *)el->dof[node0], n, sizeof(DOF),
		 (xdrproc_t) AI_xdr_DOF);
    }
  }

#if NEIGH_IN_EL
  for (i = 0; i < N_NEIGH; i++)
  {
    el->neigh[i] = NULL;
    el->opp_vertex[i] = 0;
  }
#endif

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

  return(el);
}
