/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     check.c                                                        */
/*                                                                          */
/* description:  checks on the initial grid and utilities                   */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  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)                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

#include "alberta.h"

typedef struct check_traverse_data {
  int error_detected;
  int iadmin;
  int dof_size;
  int *dof_used;
} CHECK_TRAVERSE_DATA;

static void check_fct(const EL_INFO *el_info, void *data)
{
  FUNCNAME("check_fct");
  CHECK_TRAVERSE_DATA *ud = (CHECK_TRAVERSE_DATA *)data;
  MESH                *mesh = el_info->mesh;
  int                  i, j, k, opp_v = -1;
  int                  dim = el_info->mesh->dim;
  EL                  *el, *neigh;
  
  TEST_FLAG(FILL_NEIGH, el_info);

  el = el_info->el;
  for (i = 0; i < N_NEIGH(dim); i++)
  {
    if ((neigh = el_info->neigh[i]))
    {
      if (wall_bound(el_info, i) != INTERIOR) {
	if (!ud->error_detected)
	  MSG("error detected!!!\n");
	ud->error_detected++;
	MSG("interior boundary to neighbor %d nonzero on element = %d\n",
	    i, INDEX(el));
      }

      if (dim > 0) {
	opp_v = el_info->opp_vertex[i];
	if(opp_v < 0  ||  opp_v >= N_NEIGH(dim))
	  {
	    if (!ud->error_detected)
	      MSG("error detected!!!\n");
	    ud->error_detected++;
	    MSG("opp_v = %d\n", opp_v);
	  }
      }

      if(mesh->n_dof[VERTEX]) {
	for (j = 1; j < N_VERTICES(dim); j++) {
	  for (k = 1; k < N_VERTICES(dim); k++)
	    if (el->dof[(i+j)%(dim+1)] == neigh->dof[(opp_v+k)%(dim+1)]) break;

	  if (k >= N_VERTICES(dim)) {
	    if (!ud->error_detected)
	      MSG("error detected!!!\n");
	    ud->error_detected++;
	    MSG("dof %d of el %d at face %d isn't dof of neigh %d at face %d\n",
		el->dof[(i+j)%(dim+1)][0], INDEX(el), i, INDEX(neigh), opp_v);
	  }	   
	}
      }
    }
    else
    {
      if (wall_bound(el_info, i) == INTERIOR) {
	if (!ud->error_detected)
	  MSG("error detected!!!\n");
	ud->error_detected++;
	MSG("boundary to neigh %d on domains boundary is zero on element %d\n",
	    i, INDEX(el));
      }
    }
  }

  return;
}

/*--------------------------------------------------------------------------*/
/*  check dofs from mesh->dof_admin[iadmin]                                 */
/*--------------------------------------------------------------------------*/

static void check_dof_fct(const EL_INFO *el_info, void *data)
{
  FUNCNAME("check_dof_fct");
  CHECK_TRAVERSE_DATA *ud = (CHECK_TRAVERSE_DATA *)data;
  MESH      *mesh  = el_info->mesh;
  DOF_ADMIN *admin = mesh->dof_admin[ud->iadmin];
  EL        *el = el_info->el;
  DOF       *dof;
  int       i, j, jdof, ndof, i0, j0;
  int       dim = el_info->mesh->dim;
  int       ov;
  EL        *neigh;
  int       in, k, found;

  if (!ud->dof_used) return;

  if ((ndof = admin->n_dof[VERTEX]))
  {
    j0 = admin->n0_dof[VERTEX];
    TEST_EXIT(j0 + ndof <= mesh->n_dof[VERTEX],
	      "admin->n0_dof[VERTEX] %d + n_dof %d > mesh->n_dof %d\n",
	      j0, ndof, mesh->n_dof[VERTEX]);
    i0 = mesh->node[VERTEX];
    for (i = 0; i < N_VERTICES(dim); i++)
    {
      if ((dof = el->dof[i0+i]) == NULL)
	ERROR("no vertex dof %d on element %d\n", i, INDEX(el_info->el));
      else
	for (j = 0; j < ndof; j++)
	{
	  jdof = dof[j0 + j];
	  TEST(jdof >= 0 && jdof < ud->dof_size,
	       "vertex dof=%d invalid? size=%d\n",jdof, ud->dof_size);
	  ud->dof_used[jdof]++;
	}
    }
    /* neighbour vertex dofs have been checked in check_fct() */
  }

  if(dim > 1) {
    if ((ndof = admin->n_dof[EDGE])) {
      j0 = admin->n0_dof[EDGE];
      TEST_EXIT(j0 + ndof <= mesh->n_dof[EDGE],
		"admin->n0_dof[EDGE] %d + n_dof %d > mesh->n_dof %d\n",
		j0, ndof, mesh->n_dof[EDGE]);
      i0 = mesh->node[EDGE];
      for (i = 0; i < N_EDGES(dim); i++) {
	if ((dof = el->dof[i0 + i]) == NULL)
	  ERROR("no edge dof %d on element %d\n", i, INDEX(el_info->el));
	else
	  for (j = 0; j < ndof; j++) {
	    jdof = dof[j0 + j];
	    TEST(jdof >= -1 && jdof < ud->dof_size,
		 "edge dof=%d invalid? size=%d\n",jdof, ud->dof_size);
	    if(jdof >= 0)
	      ud->dof_used[jdof]++;
	  }
	if (el->child[0] == NULL) {
	  if(dim == 2) {
	    if ((neigh = el_info->neigh[i])) {
	      ov = el_info->opp_vertex[i];
	      TEST(neigh->dof[i0 + ov] == dof,
   "el %d edge %d dof %p: wrong dof %p in neighbour %d edge %d\n",
		   INDEX(el), i, dof, neigh->dof[i0 + ov], INDEX(neigh), ov);
	    }
	  }
	  else
	    for (in = 0; in < N_NEIGH(dim); in++) {
	      if ((in != vertex_of_edge_3d[i][0]) &&
		  (in != vertex_of_edge_3d[i][0]) && 
		  (neigh = el_info->neigh[in])) {
		found = 0;
		for (k = 0; k < N_EDGES(dim); k++)
		  if (neigh->dof[i0 + k] == dof) found++;
		TEST(found==1,
		     "el %d edge %d dof found=%d in neighbour %d\n",
		     INDEX(el), i, found, INDEX(neigh));
	      }
	    }
	}
      }
    }
  }

  if ((dim == 3) && (ndof = admin->n_dof[FACE])) {
    j0 = admin->n0_dof[FACE];
    TEST_EXIT(j0 + ndof <= mesh->n_dof[FACE],
	      "admin->n0_dof[FACE] %d + n_dof %d > mesh->n_dof %d\n",
	      j0, ndof, mesh->n_dof[FACE]);
    i0 = mesh->node[FACE];
    for (i = 0; i < N_FACES_3D; i++) {
      TEST(dof = el->dof[i0 + i],
	   "no face dof %d ???\n", i);
      for (j = 0; j < ndof; j++) {
	jdof = dof[j0 + j];
	TEST(jdof >= -1 && jdof < ud->dof_size,
	     "face dof=%d invalid? size=%d\n",jdof, ud->dof_size);
	if(jdof >= 0)
	  ud->dof_used[jdof]++;
      }
      
      if (el->child[0] == NULL) {
	if ((neigh = el_info->neigh[i])) {
	  ov = el_info->opp_vertex[i];
	  TEST(neigh->dof[i0 + ov] == dof,
	       "el %d face %d dof %p: wrong dof %p in neighbour %d face %d\n", 
	       INDEX(el), i, dof, neigh->dof[i0 + ov], INDEX(neigh), ov);
	}
      }
    }
  }


  if ((ndof = admin->n_dof[CENTER]))
  {
    i0 = mesh->node[CENTER];
    TEST(dof = el->dof[i0], "no center dof???\n");
    j0 = admin->n0_dof[CENTER];
    TEST_EXIT(j0 + ndof <= mesh->n_dof[CENTER],
	      "admin->n0_dof[CENTER] %d + n_dof %d > mesh->n_dof %d\n",
	      j0, ndof, mesh->n_dof[CENTER]);
    for (j = 0; j < ndof; j++)
    {
      jdof = dof[j0 + j];
      TEST(jdof >= -1 && jdof < ud->dof_size,
	   "center dof=%d invalid? size=%d\n", jdof, ud->dof_size);
      if(jdof >= 0)
	ud->dof_used[jdof]++;
    }
  }

  return;
}

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

static void node_fct(const EL_INFO *el_info, void *data)
{
  FUNCNAME("node_fct");
  int     i, dim = el_info->mesh->dim;
  EL      *el = el_info->el;

  if(IS_LEAF_EL(el))
    MSG("leaf el %4d: ", INDEX(el));
  else
    MSG("     el %4d: ", INDEX(el));

  for (i = 0; i < N_VERTICES(dim); i++)
    print_msg("%4d%s", el->dof[i][0], i < N_VERTICES(dim)-1 ? ", " : "\n");

  return;
}

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

void check_mesh(MESH *mesh)
{
  FUNCNAME("check_mesh");
  static CHECK_TRAVERSE_DATA td[1];
  int       i, nused, nfree;
  DOF_ADMIN *admin;
  FLAGS     fill_flag;
  int       info = -1;

  if (info < 0)
  {
    info = 2;
    GET_PARAMETER(0, "refine/coarsen info", "%d", &info);
  }

  if (info >= 2) {
    if(mesh->name)
      MSG("checking mesh '%s'\n", mesh->name);
    else
      MSG("checking mesh ''\n");
  }

  td->error_detected = 0;


  fill_flag = CALL_EVERY_EL_INORDER|FILL_NEIGH|FILL_MACRO_WALLS;
  mesh_traverse(mesh, -1, fill_flag, check_fct, td);

  fill_flag = CALL_EVERY_EL_INORDER | FILL_NEIGH;

  for (td->iadmin = 0; td->iadmin < mesh->n_dof_admin; td->iadmin++)
  {
    TEST_EXIT(admin = mesh->dof_admin[td->iadmin],
	      "no dof_admin[%d]\n", td->iadmin);
    if (admin->size > 0)
    {
      if (td->dof_size < admin->size)
      {
	td->dof_used = MEM_REALLOC(td->dof_used, td->dof_size, 
				   admin->size + 1000, int);
	td->dof_size = admin->size + 1000;
      }
      for (i = 0; i < td->dof_size; i++) td->dof_used[i] = 0;

      nused = nfree = 0;

      mesh_traverse(mesh, -1, fill_flag, check_dof_fct, td);

      FOR_ALL_DOFS(admin, 
		   nused++;
		   if (!td->dof_used[dof]) {
		     td->error_detected++;
		     MSG("admin '%s': dof[%d] not used??\n",admin->name,dof);
		   });

      FOR_ALL_FREE_DOFS(admin,
			nfree++;
			if (td->dof_used[dof]) {
			  td->error_detected++;
			  MSG("admin '%s': dof[%d] used??\n",admin->name,dof);
			});

      TEST(nused + nfree == admin->size,
	   "nused = %d, nfree = %d, admin->size = %d ????\n",
	   nused, nfree, admin->size);
      TEST(nused == admin->used_count,
	   "nused = %d, admin->used_count = %d ?????\n",
	   nused, admin->used_count);
    }
  }

  if(td->dof_used)
    MEM_FREE(td->dof_used, td->dof_size, int);
  td->dof_used = NULL;
  td->dof_size = 0;

  if (!td->error_detected)
  {
    if (info >= 2)
      MSG("checking done; no error detected\n");
  }
  else
  {
    MSG("checking done; %d error%s detected\n", td->error_detected,
	td->error_detected == 1 ? "" : "s");
    mesh_traverse(mesh, -1, CALL_EVERY_EL_INORDER, node_fct, td);
    WAIT_REALLY;
  }

  return;
}
