/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     Common/coarsen.c                                               */
/*                                                                          */
/*                                                                          */
/* description:  coarsening of ? dim. hierarchical meshes;                  */
/*               file includes all coarsen_?d.c files !!                    */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Alfred Schmidt                                               */
/*             Zentrum fuer Technomathematik                                */
/*             Fachbereich 3 Mathematik/Informatik                          */
/*             Universitaet 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)                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

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

#include <stdlib.h>
#include "alberta_intern.h"
#include "alberta.h"

/*--------------------------------------------------------------------------*/
/* restriction of dof vectors during mesh coarsening                        */
/* using DOF_VEC_LIST structure                                             */
/*--------------------------------------------------------------------------*/

static int
count_coarse_restrict(MESH *mesh, DOF_VEC_LIST *dvlist, int non_periodic)
{
  FUNCNAME("count_coarse_restrict");
  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             ncr = 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->coarse_restrict) n_dof_int_vec++;
    }
    for (ddv = admin->dof_dof_vec; ddv; ddv = ddv->next) {
      if (ddv->coarse_restrict) n_dof_dof_vec++;
    }
    for (ddv = admin->int_dof_vec; ddv; ddv = ddv->next) {
      if (ddv->coarse_restrict) n_dof_int_vec++;
    }

    for (duv = admin->dof_uchar_vec; duv; duv = duv->next) {
      if (duv->coarse_restrict) n_dof_uchar_vec++;
    }
    for (dsv = admin->dof_schar_vec; dsv; dsv = dsv->next) {
      if (dsv->coarse_restrict) n_dof_schar_vec++;
    }

    for (drv = admin->dof_real_vec; drv; drv = drv->next) {
      if (drv->coarse_restrict) n_dof_real_vec++;
    }
    for (drdv = admin->dof_real_d_vec; drdv; drdv = drdv->next) {
      if (drdv->coarse_restrict) n_dof_real_d_vec++;
    }

    for (dpv = admin->dof_ptr_vec; dpv; dpv = dpv->next) {
      if (dpv->coarse_restrict) n_dof_ptr_vec++;
    }

    for (dm = admin->dof_matrix; dm; dm = dm->next) {
      if (dm->coarse_restrict) n_dof_matrix++;
    }
  }

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

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

    ncr = 0;

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

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

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

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

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

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

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

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

    DEBUG_TEST_EXIT(ncr <= 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->coarse_restrict)
	  dvlist->dof_int_vec[dvlist->n_dof_int_vec++] = div;
      }
      for (ddv = admin->dof_dof_vec; ddv; ddv = ddv->next) {
	if (ddv->coarse_restrict) 
	  dvlist->dof_dof_vec[dvlist->n_dof_dof_vec++] = ddv;
      }
      for (ddv = admin->int_dof_vec; ddv; ddv = ddv->next) {
	if (ddv->coarse_restrict) 
	  dvlist->dof_dof_vec[dvlist->n_dof_dof_vec++] = ddv;
      }
      for (duv = admin->dof_uchar_vec; duv; duv = duv->next) {
	if (duv->coarse_restrict)
	  dvlist->dof_uchar_vec[dvlist->n_dof_uchar_vec++] = duv;
      }
      for (dsv = admin->dof_schar_vec; dsv; dsv = dsv->next) {
	if (dsv->coarse_restrict) 
	  dvlist->dof_schar_vec[dvlist->n_dof_schar_vec++] = dsv;
      }
      for (drv = admin->dof_real_vec; drv; drv = drv->next) {
	if (drv->coarse_restrict)
	  dvlist->dof_real_vec[dvlist->n_dof_real_vec++] = drv;
      }
      for (drdv = admin->dof_real_d_vec; drdv; drdv = drdv->next) {
	if (drdv->coarse_restrict) 
	  dvlist->dof_real_d_vec[dvlist->n_dof_real_d_vec++] = drdv;
      }
      for (dpv = admin->dof_ptr_vec; dpv; dpv = dpv->next) {
	if (dpv->coarse_restrict)
	  dvlist->dof_ptr_vec[dvlist->n_dof_ptr_vec++] = dpv;
      }
      for (dm = admin->dof_matrix; dm; dm = dm->next) {
	if (dm->coarse_restrict) 
	  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 ncr;
}

static void coarse_restrict(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;

  DEBUG_TEST_EXIT(((MESH_MEM_INFO *)mesh->mem_info)->dvlist,
		  "Huh? No dvlist found?\n");

  for (i = 0; i < dvlist->n_dof_ptr_vec; i++) {
    dpv = dvlist->dof_ptr_vec[i];
    if (dpv->coarse_restrict)  dpv->coarse_restrict(dpv, list, n_el);
  }

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

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

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

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

/*--------------------------------------------------------------------------*/
/*  calculate the maximal level of the triangulation                        */
/*--------------------------------------------------------------------------*/

typedef struct coarsen_traverse_data {
  int max_level_val;
  U_CHAR  global_mark;
} COARSEN_TRAVERSE_DATA;


static void max_level_fct(const EL_INFO *el_info, void *data)
{
  ((COARSEN_TRAVERSE_DATA *)data)->max_level_val = 
    MAX(((COARSEN_TRAVERSE_DATA *)data)->max_level_val, el_info->level);
}

int get_max_level(MESH *mesh)
{
  COARSEN_TRAVERSE_DATA td[1] = {{0}};

  mesh_traverse(mesh, -1, CALL_LEAF_EL, max_level_fct, td);
  return td->max_level_val;
}

/*--------------------------------------------------------------------------*/
/*  global_coarsen:							    */
/*  tries to coarsen every element of mesh at least mark times		    */
/*--------------------------------------------------------------------------*/

U_CHAR global_coarsen(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 coarsen(mesh, fill_flags);
}

static int             call_coarse_restrict_1d;
#include "coarsen_1d.c"

#if DIM_MAX > 1
static int             call_coarse_restrict_2d;
static int             call_coarse_restrict_np_2d;

static int             do_more_coarsen_2d = true;

#include "coarsen_2d.c"

#if DIM_MAX > 2
static int             call_coarse_restrict_3d;
static int             call_coarse_restrict_np_3d;

static int             do_more_coarsen_3d = true;
static TRAVERSE_STACK *stack_3d;
#include "coarsen_3d.c"
#endif
#endif


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]]];

/****************************************************************************/
/* Transfer the coarsening marks to the master mesh. PLEASE NOTE: This      */
/* actually overwrites master mesh marks! Otherwise, we could never do      */
/* any coarsening for slave meshes. This may not be what the user wishes    */
/* and should be well documented!                                           */
/****************************************************************************/
    m_el->mark = MIN(m_el->mark, -1);
  }
  return;
}

static void blank_marks_fct(const EL_INFO *elinfo, void *data)
{
  EL                *s_el  = elinfo->el;

  s_el->mark = MAX(s_el->mark, 0);
  return;
}

U_CHAR coarsen(MESH *mesh, FLAGS fill_flags)
{
  FUNCNAME("coarsen");
  MESH_MEM_INFO *mem_info = ((MESH_MEM_INFO *)mesh->mem_info);
  U_CHAR mesh_coarsened = 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_coarse_restrict_2d to nonzero to make the triple coarsening  */
/* work!                                                                    */
/****************************************************************************/
    if (mesh->dim == 2) {
      int i;
      call_coarse_restrict_1d = false;

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

#if DIM_MAX == 3
    if (mesh->dim == 3) {
      int i, j;
      call_coarse_restrict_1d = 0;
      call_coarse_restrict_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_coarse_restrict_2d +=
	  count_coarse_restrict(slave, AI_get_dof_vec_list(slave), false);
	if (slave->is_periodic)
	  call_coarse_restrict_np_2d +=
	    count_coarse_restrict(slave, AI_get_dof_vec_list_np(slave), true);

	for(j = 0; j < mem_info_2->n_slaves; j++) {
	  MESH *slave_2 = mem_info_2->slaves[j];

	  call_coarse_restrict_1d +=
	    count_coarse_restrict(slave_2, AI_get_dof_vec_list(slave_2), false);
	}
      }
    }
#endif
#endif
  }

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

/* Transfer the coarsening marks to the master mesh. See note above!        */
    do {
      mesh_traverse(mesh, 0, FILL_NOTHING|CALL_LEAF_EL, transfer_fct, NULL);
      mesh_coarsened = coarsen(mem_info->master, fill_flags);
    } while (mesh_coarsened);

/* Delete the unfortunate remaining coarsening marks.                       */
    mesh_traverse(mesh, 0, FILL_NOTHING|CALL_LEAF_EL, blank_marks_fct, NULL);

    return (mesh->n_elements < n_slave_elements) ? MESH_COARSENED : 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 coarsening possible for dim==0!\n");
    break;
  case 1:
    return coarsen_1d(mesh, fill_flags);
    break;
#if DIM_MAX > 1
  case 2:
    return coarsen_2d(mesh, fill_flags);
    break;
#if DIM_MAX > 2
  case 3:
    return coarsen_3d(mesh, fill_flags);
    break;
#endif
#endif
  default:
    ERROR_EXIT("Illegal dim during coarsening!\n");
    break;
  }

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

  return mesh_coarsened;
}
