/*******************************************************************************
 *
 * ALBERTA:  an Adaptive multi Level finite element toolbox  using
 *           Bisectioning refinement and Error control by Residual
 *           Techniques
 *
 * www.alberta-fem.de
 *
 * File:     reparam-demo.c
 *
 * Description: This demo-program reparametrizes a degenerated sphere-mesh 
 *              with a conformal mapping. It is just an example how to use 
 *              the function 'reparam_cm()'.
 *               
 *              Variables:
 *              - coords is the coordinate vector of the generated mesh
 *              - sphere_coords is the coordinate vector of the nice mesh
 *                 on S^2 (a sphere embedded into R^3).     
 *
 *
 ******************************************************************************
 *
 * author(s): Rebecca Stotz
 *            Abteilung fuer Angewandte Mathematik
 *            Albert-Ludwigs-Universitaet Freiburg
 *            Hermann-Herder-Str. 10
 *            79104 Freiburg
 *            Germany
 *            rebecca.stotz@mathematik.uni-freiburg.de
 *
 * (c) by R. Stotz (2008)
 *
 * Based on the ellipt-sphere.c demo-program by C.-J. Heine.
 *
 ******************************************************************************/

#include <alberta.h>

#include "alberta-demo.h"

#include "reparam_cm.h"

/* whether or not to do some graphics, using geomview or whatever */
static bool do_graphics = 1;

#define MESH_DIM (DIM_OF_WORLD-1)
#if MESH_DIM > DIM_MAX
# error This test-program is for an embedded sphere with co-dimension 1
#endif

void togeomview(MESH *mesh,
		const DOF_REAL_VEC *u_h,
		REAL uh_min, REAL uh_max,
		REAL (*get_est)(EL *el),
		REAL est_min, REAL est_max,
		REAL (*u_loc)(const EL_INFO *el_info,
			      const REAL_B lambda,
			      void *ud),
		void *ud, FLAGS fill_flags,
		REAL u_min, REAL u_max);

/*******************************************************************************
 * global variables: finite element space
 ******************************************************************************/

static const FE_SPACE *fe_space;  /* initialized by main() */
static struct reparam_cm_data *crd;

struct ellipt_leaf_data
{
  REAL estimate; /* one real for the estimate */
};


/*******************************************************************************
 * The sphere-case is really easy: simply scale the given vertex to
 * unit-length. This works because the input to this routine always is
 * some polygonal interpolation between existing nodes of the
 * triangulation; at worst it is a linear interpolation between the
 * vertices of the simplex.
 ******************************************************************************/
static void sphere_proj_func(REAL_D vertex,
			     const EL_INFO *el_info,
			     const REAL_B lambda)
{
  SCAL_DOW(1.0/NORM_DOW(vertex), vertex);
}

/* init_node_projection() for the macro nodes. Return value != NULL
 * for c == 0 means to install a default projection for all nodes,
 * otherwise (c-1) means the number of the wall this projection is
 * meant for.
 */
static NODE_PROJECTION sphere_proj = { sphere_proj_func };

static NODE_PROJECTION *init_node_proj(MESH *mesh, MACRO_EL *mel, int c)
{
  if (c == 0) {
    return &sphere_proj;
  }

  return NULL;
}

static 
struct reparam_cm_data *get_reparam_cm_parameter(struct reparam_cm_data *crd, 
						 int argc, char **argv)
{
  parse_parameters(argc, argv, "INIT/reparam-demo.dat");
  
  GET_PARAMETER(1, "Newton solver", "%d", &crd->newton_solver);
  GET_PARAMETER(1, "Newton->tolerance", "%f", &crd->ns_data->tolerance);
  GET_PARAMETER(1, "Newton->restart", "%d", &crd->ns_data->restart);
  GET_PARAMETER(1, "Newton->max_iter", "%d", &crd->ns_data->max_iter);
  GET_PARAMETER(1, "Newton->info", "%d", &crd->ns_data->info);
  
  GET_PARAMETER(1, "solver spp", "%d", &crd->spp_solver);
  if (crd->spp_solver == 1) {
    GET_PARAMETER(1, "gmres->tolerance", "%f",
		  &crd->spp_gmres_data[0].spp_oem->tolerance);
    GET_PARAMETER(1, "gmres->restart", "%d", 
		  &crd->spp_gmres_data[0].spp_oem->restart);
    GET_PARAMETER(1, "gmres->max_iter", "%d", 
		  &crd->spp_gmres_data[0].spp_oem->max_iter);
    GET_PARAMETER(1, "gmres->info", "%d", 
		  &crd->spp_gmres_data[0].spp_oem->info);
  } else if (crd->spp_solver == 2) {
    GET_PARAMETER(1, "uzawa->tolerance", "%f", 
		  &crd->spp_uzawa_data[0].tolerance);
    GET_PARAMETER(1, "uzawa->max_iter", "%d", 
		  &crd->spp_uzawa_data[0].max_iter);
    GET_PARAMETER(1, "uzawa->info", "%d", 
		  &crd->spp_uzawa_data[0].info);
    GET_PARAMETER(1, "uzawa->tolerance_ell", "%f", 
		  &crd->spp_uzawa_data[0].tolerance_ell);
    GET_PARAMETER(1, "uzawa->max_iter_ell", "%d", 
		  &crd->spp_uzawa_data[0].max_iter_ell);
    GET_PARAMETER(1, "uzawa->info_ell", "%d", 
		  &crd->spp_uzawa_data[0].info_ell);
  }

  GET_PARAMETER(1, "project info", "%d", &crd->project_info);
  GET_PARAMETER(1, "quad degree on S", "%d", &crd->quad_degree_on_S);
  
  return (struct reparam_cm_data *)crd;
  
}

static void sphere_refine_inter1_2d(DOF_REAL_D_VEC *drdv, 
				    RC_LIST_EL *list, int n)
{
  FUNCNAME("sphere_refine_inter1_2d");
  EL      *el;
  REAL_D  *vec = NULL;
  DOF     dof_new, dof0, dof1;
  int     n0, j;

  if (n < 1) return;
  GET_DOF_VEC(vec, drdv);
  n0 = drdv->fe_space->admin->n0_dof[VERTEX];
  el = list->el_info.el;
  dof0 = el->dof[0][n0];           /* 1st endpoint of refinement edge */
  dof1 = el->dof[1][n0];           /* 2nd endpoint of refinement edge */
  dof_new = el->child[0]->dof[2][n0];  /*     newest vertex is dim==2 */
  for (j = 0; j < DIM_OF_WORLD; j++) {
    vec[dof_new][j] = 0.5*(vec[dof0][j] + vec[dof1][j]);
  }
  SCAL_DOW(1.0/NORM_DOW(vec[dof_new]), vec[dof_new]);
  
  return;
}

/*******************************************************************************
 * main program
 ******************************************************************************/

int main(int argc, char **argv)
{
  FUNCNAME("main");
  MACRO_DATA        *data;
  MESH              *mesh; 
  const BAS_FCTS    *lagrange;
  int               n_refine = 0, degree = 1, param_degree = -1;
  char              filename[PATH_MAX];
  DOF_REAL_D_VEC    *sphere_coords;
  DOF_REAL_D_VEC    *coords;
  

  crd = MEM_ALLOC(1, struct reparam_cm_data);
  crd->spp_gmres_data[0].spp_oem = MEM_ALLOC(1, struct oem_data);
  

  /*****************************************************************************
   * first of all, initialize the access to parameters of the init file
   ****************************************************************************/
  parse_parameters(argc, argv, "INIT/reparam-demo.dat");

  /*****************************************************************************
   * then read some basic parameters
   ****************************************************************************/
  GET_PARAMETER(1, "macro file name", "%s", filename);
  GET_PARAMETER(1, "polynomial degree", "%d", &degree);
  GET_PARAMETER(1, "global refinements", "%d", &n_refine);
  GET_PARAMETER(1, "parametric degree", "%d", &param_degree);
  GET_PARAMETER(1, "online graphics", "%d", &do_graphics);

  crd = get_reparam_cm_parameter(crd, argc, argv);
  
  /*****************************************************************************
  *  get a mesh, and read the macro triangulation from file
  ****************************************************************************/
  data = read_macro(filename);
  mesh = GET_MESH(MESH_DIM, "ALBERTA mesh", data,
		  init_node_proj, NULL /* init_wall_trafos */);
  free_macro_data(data);

  /*****************************************************************************
   * Initialize the leaf data for the storage of the estimate (why do
   * we not simply use a DOF_PTR_VEC with a center DOF-admin?)
   ****************************************************************************/
  init_leaf_data(mesh, sizeof(struct ellipt_leaf_data),
		 NULL /* refine_leaf_data */,
		 NULL /* coarsen_leaf_data */);


  /*****************************************************************************
   * Tell ALBERTA to use a parameterisation of p.w. degree
   * "param_degree". Per convention if "param_degree < 1" we use the
   * "new_coords" component of the EL structure to store the
   * parametric co-ordinates. In resulting parameterisation is
   * p.w. linear.
   ****************************************************************************/
  
  if (param_degree > 0) {
    use_lagrange_parametric(mesh, param_degree, &sphere_proj, PARAM_ALL);
  }

  /*****************************************************************************
   * Get the fe-space before calling global_refine() to reduce the startup-time.
   * Adding FE-spaces on refined meshes works but is somewhat inefficient.
   ****************************************************************************/
  lagrange = get_lagrange(MESH_DIM, degree);
  TEST_EXIT(lagrange, "no lagrange BAS_FCTS\n");
  fe_space = get_fe_space(mesh, lagrange->name,
			  lagrange, 1,
			  ADM_FLAGS_DFLT /* flags */);


  coords = get_dof_real_d_vec("coords", fe_space);
  sphere_coords =  get_dof_real_d_vec("sphere_coords", fe_space);
  
  sphere_coords->vec[0][0] = 1;
  sphere_coords->vec[1][1] = 1;
  sphere_coords->vec[2][2] = 1;
  sphere_coords->vec[3][0] = -1;
  sphere_coords->vec[4][1] = -1;
  sphere_coords->vec[5][2] = -1;
  


  sphere_coords->refine_interpol = sphere_refine_inter1_2d;
  

  /*sphere_coords->refine_interpol = lagrange->real_d_refine_inter;*/
  /*sphere_coords->coarse_restrict = lagrange->real_d_coarse_restr;*/

  /*****************************************************************************
   * Globally refine the mesh a little bit. Maybe do some graphics
   * output to amuse the spectators.
   ****************************************************************************/

  global_refine(mesh, MESH_DIM*n_refine);


  if (do_graphics) {
    MSG("\n\t\t****************************************\n");
    MSG(" Graphic-output shows the mesh \n ");
    MSG(" BEFORE the reparametrisation  " );
    MSG("\n\t\t****************************************\n");
    togeomview(
      mesh, NULL, 0.0, -1.0, NULL, 0.0, -1.0, NULL, NULL, 0, 0.0, -1.0);
    WAIT;
  }  


  copy_lagrange_coords(mesh, coords, false);
  reparam_cm(sphere_coords, coords, crd);
  copy_lagrange_coords(mesh, coords, true);

  if (do_graphics) {
    MSG("\n\t\t****************************************\n");
    MSG(" Graphic-output shows the mesh \n ");
    MSG(" AFTER the reparametrisation  " );
    MSG("\n\t\t****************************************\n");
    togeomview(
      mesh, NULL, 0.0, -1.0, NULL, 0.0, -1.0, NULL, NULL, 0, 0.0, -1.0);
    WAIT;
  }  

  WAIT_REALLY; /* should we? */


  MEM_FREE(crd->spp_gmres_data[0].spp_oem, 1, struct oem_data); 
  MEM_FREE(crd, 1, struct reparam_cm_data);
  free_dof_real_d_vec(coords);
  free_dof_real_d_vec(sphere_coords);

  return 0;
}
