/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/* file:     memory_1d.c                                                    */
/*                                                                          */
/*                                                                          */
/* description:  special routines for getting new FE_SPACEs, 1D-Version     */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Daniel Koester                                               */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*             Claus-Justus Heine                                           */
/*             Abteilung fuer Angewandte Mathematik                         */
/*             Albert-Ludwigs-Universitaet Freiburg                         */
/*             Hermann-Herder-Str. 10                                       */
/*             D-79104 Freiburg im Breisgau, Germany                        */
/*                                                                          */
/*  http://www.alberta-fem.de                                               */
/*                                                                          */
/*  (c) by D. Koester (2005),                                               */
/*         C.-J. Heine (2006-2007)                                          */
/*--------------------------------------------------------------------------*/

/****************************************************************************/
/* LOGICAL_EL_1D:                                                           */
/* This structure defines a MESH element at any point in the hierarchy in   */
/* terms of global indices for vertices and elements. This information is   */
/* calculated by the routines below. A temporary representation of the      */
/* entire mesh is used to fill DOF pointers on all elements. This rather    */
/* general (and costly) approach was used since this information might be   */
/* useful in other contexts in the future                                   */
/*                                                                          */
/* "neigh" is defined to be the neighbour on the same refinement level.     */
/* If there is no such neighbour we take the corresponding neighbour of the */
/* parent element.                                                          */
/****************************************************************************/

typedef struct logical_el_1d LOGICAL_EL_1D;

struct logical_el_1d
{
  int parent;
  int child[2];
  int neigh[N_NEIGH_1D];
  int oppv[N_NEIGH_1D];
  int vertex[N_VERTICES_1D];
  EL *el;
};


/****************************************************************************/
/* fill_logical_el_rec_1d(l_els, el, current_el, index):                    */
/* This routine fills basic information on logical els, namely childhood    */
/* relationships and EL pointers. Elements are also counted here.           */
/****************************************************************************/

static void fill_logical_el_rec_1d(LOGICAL_EL_1D *l_els, EL *el,
				   int current_el, int *index)
{
  /* FUNCNAME("fill_logical_el_rec_1d");*/
  int i_child, child_ind[2];

  if(el->child[0]) {
    /* Fill basic child information.                                        */

    for(i_child = 0; i_child < 2; i_child++) {
      child_ind[i_child] = *index + i_child;

      l_els[current_el].child[i_child]   = child_ind[i_child];
      l_els[child_ind[i_child]].el       = el->child[i_child];
      l_els[child_ind[i_child]].parent   = current_el;
    }

    *index += 2;

    /* Now recurse to the two children.                                     */

    fill_logical_el_rec_1d(l_els, el->child[0], child_ind[0], index);
    fill_logical_el_rec_1d(l_els, el->child[1], child_ind[1], index);
  }

  return;
}


/****************************************************************************/
/* fill_connectivity_rec_1d(l_els, current_el, v_index):                    */
/* This routine is the workhorse for filling a LOGICAL_EL array. It sets    */
/* vertex and neighbour indices on the children of "current_el".            */
/*   In 1D this is possible by a simple mesh recursion of "PRE_ORDER" type. */
/****************************************************************************/

static void fill_connectivity_rec_1d(LOGICAL_EL_1D *l_els, int current_el,
				     int *v_index)
{
  /* FUNCNAME("fill_connectivity_rec_1d"); */
  int i_child, child_ind[2], neigh;
  int i, neigh_child, oppv, child;

  if(l_els[current_el].child[0] > -1) {
    for(i_child = 0; i_child < 2; i_child++)
      child_ind[i_child] = l_els[current_el].child[i_child];

    /* Fill vertex information. Two vertices are handed down from parents.  */

    l_els[child_ind[0]].vertex[0] = l_els[current_el].vertex[0];
    l_els[child_ind[1]].vertex[1] = l_els[current_el].vertex[1];

    /* One vertex is allocated where the two children of "current_el" meet. */

    l_els[child_ind[0]].vertex[1] =
      l_els[child_ind[1]].vertex[0] = *v_index;

    *v_index += 1;

    /* Set neighbourhood information among children of "current_el".        */

    l_els[child_ind[0]].neigh[0] = child_ind[1];
    l_els[child_ind[1]].neigh[1] = child_ind[0];
    l_els[child_ind[0]].oppv[0]  = 1;
    l_els[child_ind[1]].oppv[1]  = 0;

    /* Now spread neighbourhood info to the direct neighbours.               */

    for(i = 0; i < N_NEIGH_1D; i++) {
      neigh = l_els[current_el].neigh[i];

      if(neigh > -1) {
	if(l_els[neigh].child[0] > -1) {
	  oppv = l_els[current_el].oppv[i];

	  child = child_ind[1-i];
	  neigh_child = l_els[neigh].child[1-oppv];
	  
	  l_els[child].neigh[i] = neigh_child;
	  l_els[child].oppv[i]  = oppv;

	  l_els[neigh_child].neigh[oppv] = child;
	  l_els[neigh_child].oppv[oppv]  = 1-oppv;
	} else {
	  l_els[child_ind[1-i]].neigh[i] = neigh;
	  l_els[child_ind[1-i]].oppv[i]  = l_els[current_el].oppv[i];  
	}
      }
    }

    /* Now recurse to the two children.                                     */

    fill_connectivity_rec_1d(l_els, child_ind[0], v_index);
    fill_connectivity_rec_1d(l_els, child_ind[1], v_index);
  }

  return;
}


/****************************************************************************/
/* fill_logical_els_1d(mesh, n_elements, n_vertices):                       */
/* Return a filled array of LOGICAL_EL_1D elements corresponding to the     */
/* current state of the mesh refinement tree.                               */
/****************************************************************************/

static LOGICAL_EL_1D *fill_logical_els_1d(MESH *mesh, int *n_elements,
					  int *n_vertices)
{
  FUNCNAME("fill_logical_els_1d");
  REAL_D        *coords = ((MESH_MEM_INFO *)mesh->mem_info)->coords;
  int            i, j, n_els = mesh->n_hier_elements;
  LOGICAL_EL_1D *l_els = NULL;
  int            index, v_index = 0;
  MACRO_EL      *mel;

  l_els = MEM_ALLOC(n_els, LOGICAL_EL_1D);

  /* First pass: Initialize everything, set some information on macro
   * elements: Neighbour indices and vertices. Make use of the fact
   * that macro coordinates are stored in a simple linear array to get
   * vertices!
   */
  for (i = 0; i < n_els; i++) {
    l_els[i].parent   = -1;
    l_els[i].child[0] = -1;
    l_els[i].child[1] = -1;

    if (i < mesh->n_macro_el) {
      mel = mesh->macro_els + i;

      for (j = 0; j < N_VERTICES_1D; j++)
	l_els[i].vertex[j] = (REAL_D *)(mel->coord[j]) - coords;
      
      for (j = 0; j < N_NEIGH_1D; j++) {
	if (mel->neigh[j]) {
	  l_els[i].neigh[j] = mel->neigh[j]->index;
	  l_els[i].oppv[j] = mel->opp_vertex[j];
	} else {
	  l_els[i].neigh[j] = -1;
	  l_els[i].oppv[j] = 0;
	}
      }
    
      l_els[i].el = mel->el;
    } else {
      for (j = 0; j < N_NEIGH_1D; j++)
	l_els[i].neigh[j] = -1;
      
      for (j = 0; j < N_VERTICES_1D; j++)
	l_els[i].vertex[j] = -1;
    }
  }

  index   = mesh->n_macro_el;
  v_index = ((MESH_MEM_INFO *)mesh->mem_info)->count;

  /* Now we fill basic information about all mesh elements.                 */

  for (i = 0; i < mesh->n_macro_el; i++)
    fill_logical_el_rec_1d(l_els, mesh->macro_els[i].el, i, &index);

  /* Connectivity and vertex/edge indices are built in a "PRE_ORDER" style  */
  /* mesh traversal recursion.                                              */

  for (i = 0; i < mesh->n_macro_el; i++)
    fill_connectivity_rec_1d(l_els, i, &v_index);

#if ALBERTA_DEBUG == 1
  /* Do a quick and cheap check. For more checks please call check_mesh().  */

  for (i = 0; i < index; i++) {
    for (j = 0; j < N_VERTICES_1D; j++)
      TEST_EXIT(l_els[i].vertex[j] > -1,
		"Error while checking element %d, vertex %d==-1!\n", 
		i, j);
  }
#endif

  *n_elements = index;
  *n_vertices = v_index;

  return l_els;
}


/****************************************************************************/
/* adjust_dofs_and_dof_ptrs_1d(mesh, new_admin, old_n_node_el,              */
/*                             old_n_dof, old_node):                        */
/* 1) If el->dof pointers have changed, adjust these. Keep old information! */
/* 2) Determine what types of new DOF pointers must be allocated.           */
/* 3) Build an index based mesh representation using fill_logical_els()     */
/* 4) Change all DOFs on the leaf level. (non-coarse DOFs!)                 */
/* 5) Change all coarse DOFs on non-leaf elements.                          */
/****************************************************************************/

static void adjust_dofs_and_dof_ptrs_1d(MESH *mesh, DOF_ADMIN *new_admin,
					int old_n_node_el,
					int *old_n_dof, int *old_node)
{
  FUNCNAME("adjust_dofs_and_dof_ptrs_1d");
  DOF            **old_dof_ptr;
  int            i, n, n_elements = 0, n_hier_elements, n_vertices, node;
  int            change_v_d = 0, change_c_d = 0;
  EL             *el;
  TRAVERSE_STACK *stack = get_traverse_stack();
  const EL_INFO  *el_info = NULL;
  LOGICAL_EL_1D  *l_els = NULL;
  DOF            **new_vertex_dofs = NULL;
  int            is_periodic;
  int            per_n_vertices = 0;

  is_periodic = mesh->is_periodic && (new_admin->flags & ADM_PERIODIC);

  /* Adjust the length of el->dof pointer fields. Allocate new ones if      */
  /* necessary and transfer old information.                                */
  
  if (mesh->n_node_el > old_n_node_el) {
    el_info = traverse_first(stack, mesh, -1,
			       CALL_EVERY_EL_PREORDER);
    while (el_info) {
      el = el_info->el;

      old_dof_ptr = el->dof;
      el->dof = get_dof_ptrs(mesh);

      if (old_n_dof[VERTEX])
	for (i = 0; i < N_VERTICES_1D; i++)
	  el->dof[mesh->node[VERTEX] + i] = old_dof_ptr[old_node[VERTEX] + i];
      if (old_n_dof[CENTER])
	el->dof[mesh->node[CENTER]] = old_dof_ptr[old_node[CENTER]];

      el_info = traverse_next(stack, el_info);
    }
  }

  /* Determine which DOF types have changed on the mesh with the new admin. */

  if (mesh->n_dof[VERTEX] > old_n_dof[VERTEX])
    change_v_d = 1;
  if (mesh->n_dof[CENTER] > old_n_dof[CENTER])
    change_c_d = 1;

  /* Build an array containing an index based mesh representation.          */

  l_els = fill_logical_els_1d(mesh, &n_hier_elements, &n_vertices);
  
  /* Allocate an array containing new DOF pointers. We make use of the      */
  /* index based mesh representation to ensure the correct setting.         */

  if (change_v_d)
    new_vertex_dofs = MEM_CALLOC(n_vertices, DOF *);

  /* Change all DOFs on all elements of the mesh.                           */

  for (n = 0; n < n_hier_elements; n++) {
    el = l_els[n].el;
    
    if (!el->child[0]) n_elements++;

    if (change_v_d) {
      node = mesh->node[VERTEX];
      
      for (i = 0; i < N_VERTICES_1D; i++) {
	int vertex;
	
	vertex = l_els[n].vertex[i];
	if (!new_vertex_dofs[vertex]) {
	  DOF *twin_dof = NULL;

	  if (is_periodic) {
	    int neigh, wall;
	    int twin_v;       /* local vertex number in neighbour */
	    int neigh_vertex; /* global vertex number in neighbour */
	    
	    wall = 1-i;
	    neigh = l_els[n].neigh[wall];
	    if (neigh >= 0) {
	      twin_v = 1 - l_els[n].oppv[wall];
	      neigh_vertex = l_els[neigh].vertex[twin_v];
	      if (neigh_vertex != vertex) {
		twin_dof = new_vertex_dofs[neigh_vertex];
		if (twin_dof) {
		  --per_n_vertices;		  
		}
	      }
	    }
	  }

	  new_vertex_dofs[vertex] = 
	    transfer_dofs(mesh, new_admin, el->dof[node + i], VERTEX,
			  false, twin_dof);
	}
	
	el->dof[node + i] = new_vertex_dofs[vertex];
      }
    }
      
    if (change_c_d) {
      node = mesh->node[CENTER];
      
      if (!el->child[0])
	el->dof[node] = 
	  transfer_dofs(mesh, new_admin, el->dof[node], CENTER, false, NULL);
      else
	el->dof[node] = 
	  transfer_dofs(mesh, new_admin, el->dof[node], CENTER, true, NULL);
    }
  }

  /*  A few checks and settings.                                            */

  TEST_EXIT(n_elements == mesh->n_elements,
	    "Did not count correct number of leaf elements in mesh!\n");

  if (mesh->n_vertices > -1) {
    TEST_EXIT(n_vertices == mesh->n_vertices,
	      "Did not count correct number of vertices in mesh!\n");
  } else {
    mesh->n_vertices = n_vertices;
  }

  if (is_periodic && change_v_d) {
    per_n_vertices += n_vertices;
    if (mesh->per_n_vertices > -1) {
      TEST_EXIT(per_n_vertices == mesh->per_n_vertices,
		"Did not count correct number of vertices in periodic mesh!\n");
    } else {
      mesh->per_n_vertices = per_n_vertices;
    } 
  }

  /*  Clean up operations. */

  if (new_vertex_dofs)
    MEM_FREE(new_vertex_dofs, n_vertices, DOF *);
  if (l_els)
    MEM_FREE(l_els, n_hier_elements, LOGICAL_EL_1D);

  free_traverse_stack(stack);
  
  return;
}


/****************************************************************************/
/* fill_missing_dofs_1d(mesh): See master routine in memory.c.              */
/****************************************************************************/

static void fill_missing_dofs_1d(MESH *mesh)
{
  /* FUNCNAME("fill_missing_dofs_1d"); */
  int             n, n_elements, n_vertices, node;
  EL              *el;
  LOGICAL_EL_1D   *l_els = NULL;

  
  if(!mesh->n_dof[CENTER]) return;
  l_els = fill_logical_els_1d(mesh, &n_elements, &n_vertices);

  /* All new DOFs are set to -1 (unused) by transfer_dofs().                */

  for(n = 0; n < n_elements; n++) {
    el = l_els[n].el;
    
    if(mesh->n_dof[CENTER]) {
      node = mesh->node[CENTER];
      if(!el->dof[node])
	el->dof[node] = transfer_dofs(mesh, NULL, NULL, CENTER, false, NULL);
    }
  }

/*  Clean up operations.                                                    */

  MEM_FREE(l_els, n_elements, LOGICAL_EL_1D);

  return;
}
