/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     Common/refine.c                                                */
/*                                                                          */
/*                                                                          */
/* description:  Common refinement routines shared among all dimensions     */
/*               This file contains refine_?d.c for ? = 1,2,3 !!            */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  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"

/*--------------------------------------------------------------------------*/
/*  refinement routines for global refinement:				    */
/*--------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*/
/*  global_refine:							    */
/*  refines every element of mesh at least mark times			    */
/*--------------------------------------------------------------------------*/

U_CHAR global_refine(MESH *mesh, int mark, FLAGS fill_flags)
{
  if (mark <= 0) {
    return 0;
  }
  TRAVERSE_FIRST(mesh, -1, CALL_LEAF_EL) {
    el_info->el->mark = mark;
  } TRAVERSE_NEXT();

  return refine(mesh, fill_flags);
}


DOF_VEC_LIST *AI_get_dof_vec_list(MESH *mesh)
{
  MESH_MEM_INFO  *mem_info = (MESH_MEM_INFO *)mesh->mem_info;

  if (!mem_info->dvlist)
    mem_info->dvlist = MEM_CALLOC(1, DOF_VEC_LIST);

  return mem_info->dvlist;
}

void AI_free_dof_vec_list(MESH *mesh)
{
  MESH_MEM_INFO  *mem_info = (MESH_MEM_INFO *)mesh->mem_info;

  if (mem_info->dvlist) {
    MEM_FREE(mem_info->dvlist, 1, DOF_VEC_LIST);
    mem_info->dvlist = NULL;
  }
}

DOF_VEC_LIST *AI_get_dof_vec_list_np(MESH *mesh)
{
  MESH_MEM_INFO  *mem_info = (MESH_MEM_INFO *)mesh->mem_info;

  if (!mem_info->dvlist_np)
    mem_info->dvlist_np = MEM_CALLOC(1, DOF_VEC_LIST);

  return mem_info->dvlist_np;
}

void AI_free_dof_vec_list_np(MESH *mesh)
{
  MESH_MEM_INFO  *mem_info = (MESH_MEM_INFO *)mesh->mem_info;

  if (mem_info->dvlist_np) {
    MEM_FREE(mem_info->dvlist_np, 1, DOF_VEC_LIST);
    mem_info->dvlist_np = NULL;
  }
}

/* Determine how many vectors/matrices request an adaptation
 * call-back. Fill FILL_FLAGS with the fill-flag requirements of the
 * underlying basis functions. FILL_FLAGS is assumed to be initialized
 * by the caller.
 */
static int
count_refine_interpol(MESH *mesh, DOF_VEC_LIST *dvlist,
		      int non_periodic, FLAGS *fill_flags)
{
  FUNCNAME("count_refine_interpol");
  DOF_ADMIN       *admin;
  int             iadmin;
  DOF_REAL_VEC    *drv;
  DOF_REAL_D_VEC  *drdv;
  DOF_INT_VEC     *div;
  DOF_DOF_VEC     *ddv;
  DOF_UCHAR_VEC   *duv;
  DOF_SCHAR_VEC   *dsv;
  DOF_PTR_VEC     *dpv;
  DOF_MATRIX      *dm;
  int             nri = 0;
  int             n_dof_int_vec = 0,  n_dof_dof_vec = 0;
  int             n_dof_uchar_vec = 0, n_dof_schar_vec = 0;
  int             n_dof_real_vec = 0, n_dof_real_d_vec = 0;
  int             n_dof_ptr_vec = 0;
  int             n_dof_matrix = 0, n_dof_dowb_matrix = 0, n_dof_rdr_matrix = 0;

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

    if (mesh->is_periodic) {
      if (non_periodic && (admin->flags & ADM_PERIODIC)) {
	continue;
      } else if (!non_periodic && !(admin->flags & ADM_PERIODIC)) {
	continue;
      }
    }

    for (div = admin->dof_int_vec; div; div = div->next) {
      if (div->refine_interpol) {
	n_dof_int_vec++;
	if (div->fe_space->bas_fcts) {
	  *fill_flags |= div->fe_space->bas_fcts->fill_flags;
	}
      }
    }
    for (ddv = admin->dof_dof_vec; ddv; ddv = ddv->next) {
      if (ddv->refine_interpol) {
	n_dof_dof_vec++;
	if (ddv->fe_space->bas_fcts) {
	  *fill_flags |= ddv->fe_space->bas_fcts->fill_flags;
	}
      }
    }
    for (ddv = admin->int_dof_vec; ddv; ddv = ddv->next) {
      if (ddv->refine_interpol) {
	n_dof_dof_vec++;
	if (ddv->fe_space->bas_fcts) {
	  *fill_flags |= ddv->fe_space->bas_fcts->fill_flags;
	}
      }
    }

    for (duv = admin->dof_uchar_vec; duv; duv = duv->next) {
      if (duv->refine_interpol) {
	n_dof_uchar_vec++;
	if (duv->fe_space->bas_fcts) {
	  *fill_flags |= duv->fe_space->bas_fcts->fill_flags;
	}
      }
    }
    for (dsv = admin->dof_schar_vec; dsv; dsv = dsv->next) {
      if (dsv->refine_interpol) {
	n_dof_schar_vec++;
	if (dsv->fe_space->bas_fcts) {
	  *fill_flags |= dsv->fe_space->bas_fcts->fill_flags;
	}
      }
    }

    for (drv = admin->dof_real_vec; drv; drv = drv->next) {
      if (drv->refine_interpol) {
	n_dof_real_vec++;
	if (drv->fe_space->bas_fcts) {
	  *fill_flags |= drv->fe_space->bas_fcts->fill_flags;
	}
      }
    }
    for (drdv = admin->dof_real_d_vec; drdv; drdv = drdv->next) {
      if (drdv->refine_interpol)  {
	n_dof_real_d_vec++;
	if (drdv->fe_space->bas_fcts) {
	  *fill_flags |= drdv->fe_space->bas_fcts->fill_flags;
	}
      }
    }

    for (dpv = admin->dof_ptr_vec; dpv; dpv = dpv->next) {
      if (dpv->refine_interpol) {
	n_dof_ptr_vec++;
	if (dpv->fe_space->bas_fcts) {
	  *fill_flags |= dpv->fe_space->bas_fcts->fill_flags;
	}
      }
    }

    for (dm = admin->dof_matrix; dm; dm = dm->next) {
      if (dm->refine_interpol) {
	n_dof_matrix++;
	if (dm->row_fe_space->bas_fcts) {
	  *fill_flags |= dm->row_fe_space->bas_fcts->fill_flags;
	}
	if (dm->col_fe_space->bas_fcts) {
	  *fill_flags |= dm->col_fe_space->bas_fcts->fill_flags;
	}
      }
    }
  }

  nri =
    + n_dof_int_vec + n_dof_dof_vec + n_dof_uchar_vec + n_dof_schar_vec
    + n_dof_real_vec + n_dof_real_d_vec
    + n_dof_ptr_vec
    + n_dof_matrix + n_dof_rdr_matrix + n_dof_dowb_matrix;

  if (nri > 0) {
    if (dvlist->size < nri) {
      dvlist->list = MEM_REALLOC(dvlist->list, dvlist->size, nri+5, void *);
      dvlist->size = nri+5;
    }

    nri = 0;

    if (n_dof_int_vec)
      dvlist->dof_int_vec    = (DOF_INT_VEC **)(dvlist->list+nri);
    else
      dvlist->dof_int_vec    = NULL;
    nri += n_dof_int_vec;

    if (n_dof_dof_vec)
      dvlist->dof_dof_vec    = (DOF_DOF_VEC **)(dvlist->list+nri);
    else
      dvlist->dof_dof_vec    = NULL;
    nri += n_dof_dof_vec;

    if (n_dof_uchar_vec)
      dvlist->dof_uchar_vec  = (DOF_UCHAR_VEC **)(dvlist->list+nri);
    else
      dvlist->dof_uchar_vec  = NULL;
    nri += n_dof_uchar_vec;

    if (n_dof_schar_vec)
      dvlist->dof_schar_vec  = (DOF_SCHAR_VEC **)(dvlist->list+nri);
    else
      dvlist->dof_schar_vec  = NULL;
    nri += n_dof_schar_vec;

    if (n_dof_real_vec)
      dvlist->dof_real_vec   = (DOF_REAL_VEC **)(dvlist->list+nri);
    else
      dvlist->dof_real_vec   = NULL;
    nri += n_dof_real_vec;

    if (n_dof_real_d_vec)
      dvlist->dof_real_d_vec = (DOF_REAL_D_VEC **)(dvlist->list+nri);
    else
      dvlist->dof_real_d_vec = NULL;
    nri += n_dof_real_d_vec;

    if (n_dof_ptr_vec)
      dvlist->dof_ptr_vec    = (DOF_PTR_VEC **)(dvlist->list+nri);
    else
      dvlist->dof_ptr_vec    = NULL;
    nri += n_dof_ptr_vec;

    if (n_dof_matrix)
      dvlist->dof_matrix     = (DOF_MATRIX **)(dvlist->list+nri);
    else
      dvlist->dof_matrix     = NULL;
    nri += n_dof_matrix;

    DEBUG_TEST_EXIT(nri <= dvlist->size, "error in dvlist->size");

    dvlist->n_dof_int_vec     = 0;
    dvlist->n_dof_dof_vec     = 0;
    dvlist->n_dof_uchar_vec   = 0;
    dvlist->n_dof_schar_vec   = 0;
    dvlist->n_dof_real_vec    = 0;
    dvlist->n_dof_real_d_vec  = 0;
    dvlist->n_dof_ptr_vec     = 0;
    dvlist->n_dof_matrix      = 0;

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

      if (mesh->is_periodic) {
	if (non_periodic && (admin->flags & ADM_PERIODIC)) {
	  continue;
	} else if (!non_periodic && !(admin->flags & ADM_PERIODIC)) {
	  continue;
	}
      }

      for (div = admin->dof_int_vec; div; div = div->next) {
	if (div->refine_interpol) 
	  dvlist->dof_int_vec[dvlist->n_dof_int_vec++] = div;
      }
      for (ddv = admin->dof_dof_vec; ddv; ddv = ddv->next) {
	if (ddv->refine_interpol) 
	  dvlist->dof_dof_vec[dvlist->n_dof_dof_vec++] = ddv;
      }
      for (ddv = admin->int_dof_vec; ddv; ddv = ddv->next) {
	if (ddv->refine_interpol) 
	  dvlist->dof_dof_vec[dvlist->n_dof_dof_vec++] = ddv;
      }

      for (duv = admin->dof_uchar_vec; duv; duv = duv->next) {
	if (duv->refine_interpol)
	  dvlist->dof_uchar_vec[dvlist->n_dof_uchar_vec++] = duv;
      }
      for (dsv = admin->dof_schar_vec; dsv; dsv = dsv->next) {
	if (dsv->refine_interpol) 
	  dvlist->dof_schar_vec[dvlist->n_dof_schar_vec++] = dsv;
      }

      for (drv = admin->dof_real_vec; drv; drv = drv->next) {
	if (drv->refine_interpol)
	  dvlist->dof_real_vec[dvlist->n_dof_real_vec++] = drv;
      }
      for (drdv = admin->dof_real_d_vec; drdv; drdv = drdv->next) {
	if (drdv->refine_interpol) 
	  dvlist->dof_real_d_vec[dvlist->n_dof_real_d_vec++] = drdv;
      }

      for (dpv = admin->dof_ptr_vec; dpv; dpv = dpv->next) {
	if (dpv->refine_interpol)
	  dvlist->dof_ptr_vec[dvlist->n_dof_ptr_vec++] = dpv;
      }

      for (dm = admin->dof_matrix; dm; dm = dm->next) {
	if (dm->refine_interpol) 
	  dvlist->dof_matrix[dvlist->n_dof_matrix++] = dm;
      }
    }

    DEBUG_TEST_EXIT(dvlist->n_dof_int_vec == n_dof_int_vec,
		"error in n_dof_int_vec");
    DEBUG_TEST_EXIT(dvlist->n_dof_dof_vec == n_dof_dof_vec,
		"error in n_dof_dof_vec");
    DEBUG_TEST_EXIT(dvlist->n_dof_uchar_vec == n_dof_uchar_vec,
		"error in n_dof_uchar_vec");
    DEBUG_TEST_EXIT(dvlist->n_dof_schar_vec == n_dof_schar_vec,
		"error in n_dof_schar_vec");
    DEBUG_TEST_EXIT(dvlist->n_dof_real_vec == n_dof_real_vec,
		"error in n_dof_real_vec");
    DEBUG_TEST_EXIT(dvlist->n_dof_real_d_vec == n_dof_real_d_vec,
		"error in n_dof_real_d_vec");
    DEBUG_TEST_EXIT(dvlist->n_dof_ptr_vec == n_dof_ptr_vec,
		"error in n_dof_ptr_vec");
    DEBUG_TEST_EXIT(dvlist->n_dof_matrix == n_dof_matrix,
		"error in n_dof_matrix");
  } else {
    dvlist->dof_int_vec     = NULL;
    dvlist->dof_dof_vec     = NULL;
    dvlist->dof_uchar_vec   = NULL;
    dvlist->dof_schar_vec   = NULL;
    dvlist->dof_real_vec    = NULL;
    dvlist->dof_real_d_vec  = NULL;
    dvlist->dof_ptr_vec     = NULL;
    dvlist->dof_matrix      = NULL;
  }

  return nri;
}

static void refine_interpol(MESH *mesh, DOF_VEC_LIST *dvlist,
			    RC_LIST_EL *list, int n_el)
{
  DOF_REAL_VEC    *drv;
  DOF_REAL_D_VEC  *drdv;
  DOF_INT_VEC     *div;
  DOF_DOF_VEC     *ddv;
  DOF_UCHAR_VEC   *duv;
  DOF_SCHAR_VEC   *dsv;
  DOF_PTR_VEC     *dpv;
  DOF_MATRIX      *dm;
  int             i;

  for (i = 0; i < dvlist->n_dof_int_vec; i++) {
    div = dvlist->dof_int_vec[i];
    div->refine_interpol(div, list, n_el);
  }
  for (i = 0; i < dvlist->n_dof_dof_vec; i++) {
    ddv = dvlist->dof_dof_vec[i];
    ddv->refine_interpol(ddv, list, n_el);
  }

  for (i = 0; i < dvlist->n_dof_uchar_vec; i++) {
    duv = dvlist->dof_uchar_vec[i];
    duv->refine_interpol(duv, list, n_el);
  }
  for (i = 0; i < dvlist->n_dof_schar_vec; i++) {
    dsv = dvlist->dof_schar_vec[i];
    dsv->refine_interpol(dsv, list, n_el);
  }

  for (i = 0; i < dvlist->n_dof_real_vec; i++) {
    drv = dvlist->dof_real_vec[i];
    drv->refine_interpol(drv, list, n_el);
  }
  for (i = 0; i < dvlist->n_dof_real_d_vec; i++) {
    drdv = dvlist->dof_real_d_vec[i];
    drdv->refine_interpol(drdv, list, n_el);
  }

  /* Refine DOF_PTR_VECs _AFTER_ DOF_REAL_D_VECs; otherwise
   * slave_refine_interpolY() will not work. It is called from
   * master_interpol() and needs the refined coordinate information
   * from the master mesh.
   */
  for (i = 0; i < dvlist->n_dof_ptr_vec; i++) {
    dpv = dvlist->dof_ptr_vec[i];
    dpv->refine_interpol(dpv, list, n_el);
  }

  for (i = 0; i < dvlist->n_dof_matrix; i++) {
    dm = dvlist->dof_matrix[i];
    dm->refine_interpol(dm, list, n_el);
  }

  return;
}

void _AI_refine_update_bbox(MESH *mesh, const REAL_D new_coord)
{
  int i;
  
  for (i = 0; i < DIM_OF_WORLD; i++) {
    if (new_coord[i] < mesh->bbox[0][i]) {
      mesh->bbox[0][i] = new_coord[i];
      mesh->diam[i] = mesh->bbox[1][i] - mesh->bbox[0][i];
    } else if (new_coord[i] > mesh->bbox[1][i]) {
      mesh->bbox[1][i] = new_coord[i];
      mesh->diam[i] = mesh->bbox[1][i] - mesh->bbox[0][i];
    }  
  }
}

static int             do_more_refine_1d = 0; 
static int             call_refine_interpol_1d = 0;

#include "refine_1d.c"

#if DIM_MAX > 1
static int             do_more_refine_2d = false; 
static int             call_refine_interpol_2d = false;
static int             call_refine_interpol_np_2d = false;

#include "refine_2d.c"

#if DIM_MAX > 2
static int             do_more_refine_3d = false; 
static int             call_refine_interpol_3d = false;
static int             call_refine_interpol_np_3d = false;

#include "refine_3d.c"
#endif
#endif

/*--------------------------------------------------------------------------*/
/*  refine:     							    */
/*  traversal routine for recursive refinement of a triangulation; basic    */
/*  refinement module; used by all other refinement routines like           */
/*  global_refine()                                                         */
/*--------------------------------------------------------------------------*/

static void transfer_fct(const EL_INFO *elinfo, void *data)
{
  MESH            *mesh = elinfo->mesh; 
  EL              *s_el  = elinfo->el,
                  *m_el;
  DOF_PTR_VEC     *master_binding;
  const DOF_ADMIN *s_admin;

  if (s_el->mark > 0) { 
    master_binding = ((MESH_MEM_INFO *)mesh->mem_info)->master_binding;
    s_admin = master_binding->fe_space->admin;
    
    m_el = (EL *)master_binding->vec[s_el->dof[mesh->node[CENTER]]
				     [s_admin->n0_dof[CENTER]]];
    
    m_el->mark = MAX(m_el->mark, 1);
  }

  return;
}

U_CHAR refine(MESH *mesh, FLAGS fill_flags)
{
  FUNCNAME("refine");
  MESH_MEM_INFO *mem_info = ((MESH_MEM_INFO *)mesh->mem_info);
  U_CHAR         mesh_refined = 0;

  if (mem_info->n_slaves) {
/****************************************************************************/
/* We are on a master mesh.                                                 */
/****************************************************************************/

#if DIM_MAX > 1
/****************************************************************************/
/* Check if we have an entire hierarchy of meshes. In this case, we need    */
/* to set call_refine_interpol_?d to nonzero to make the triple refinement  */
/* work!                                                                    */
/****************************************************************************/
    if (mesh->dim == 2) {
      int i;

      call_refine_interpol_1d = 0;

      for(i = 0; i < mem_info->n_slaves; i++) {
	MESH *slave = mem_info->slaves[i];
	
	call_refine_interpol_1d +=
	  count_refine_interpol(
	    slave, AI_get_dof_vec_list(slave), false, &fill_flags);
      }
      
    }

#if DIM_MAX > 2
    if (mesh->dim == 3) {
      int i, j;

      call_refine_interpol_1d = 0;
      call_refine_interpol_2d = 0;

      for(i = 0; i < mem_info->n_slaves; i++) {
	MESH *slave = mem_info->slaves[i];
	MESH_MEM_INFO *mem_info_2 = (MESH_MEM_INFO *)slave->mem_info;
	
	call_refine_interpol_2d +=
	  count_refine_interpol(
	    slave, AI_get_dof_vec_list(slave), false, &fill_flags);
	if (slave->is_periodic)
	  call_refine_interpol_np_2d +=
	    count_refine_interpol(
	      slave, AI_get_dof_vec_list_np(slave), true, &fill_flags);
	
	for(j = 0; j < mem_info_2->n_slaves; j++) {
	  MESH *slave_2 = mem_info_2->slaves[j];
	  
	  call_refine_interpol_1d +=
	    count_refine_interpol(
	      slave_2, AI_get_dof_vec_list(slave_2), false, &fill_flags);
	}
      }
    }
#endif
#endif
  }

  if (mem_info->master) {
/****************************************************************************/
/* We are on a slave mesh.                                                  */
/****************************************************************************/
    int    n_slave_elements = mesh->n_elements;

/* Transfer the refinement marks to the master mesh.                        */
    do {
      mesh_traverse(mesh, 0, FILL_NOTHING|CALL_LEAF_EL, transfer_fct, NULL);
      mesh_refined = refine(mem_info->master, fill_flags);
    } while (mesh_refined);
    return (mesh->n_elements > n_slave_elements) ? MESH_REFINED : 0; 
#if 0
  } else {
/****************************************************************************/
/* We are on a top level master mesh.                                       */
/****************************************************************************/

    /* Advance cookies on this mesh and all its slaves. */
    AI_advance_cookies_rec(mesh);
#endif
  }

  switch(mesh->dim) {
  case 0:
    WARNING("No refinement possible for dim == 0!\n");
    break;
  case 1:
    mesh_refined = refine_1d(mesh, fill_flags);
    break;
#if DIM_MAX > 1
  case 2:
    mesh_refined = refine_2d(mesh, fill_flags);
    break;
#if DIM_MAX > 2
  case 3:
    mesh_refined = refine_3d(mesh, fill_flags);
    break;
#endif
#endif
  default:
    ERROR_EXIT("Illegal dim during refining!\n");
    break;
  }

  if (mesh_refined) {
    /* Advance cookies on this mesh and all its slaves. */
    AI_advance_cookies_rec(mesh);
  }
  
  return mesh_refined;
}
