/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     trav_xy.c                                                      */
/*                                                                          */
/*                                                                          */
/* description: Find element at position (xy) and local coordinates         */
/*              return 0 if xy \not\in domain.                              */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  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                                    */
/*                                                                          */
/*             Oliver Kriessl                                               */
/*             Institut fuer Mathematik                                     */
/*             Albert-Ludwigs-Universitaet Freiburg                         */
/*             Hermann-Herder-Str. 10                                       */
/*             D-79104 Freiburg                                             */
/*                                                                          */
/*  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"

static const REAL *g_xy0, *g_xy;
static REAL       *g_sp;
static EL_INFO final_el_info[1];
static REAL_B  final_lambda = {0};

static int find_el_at_pt_recursive(EL_INFO *el_info, REAL_B lambda,
				   int outside)
{
  FUNCNAME("find_el_at_pt_recursive");
  EL         *el = el_info->el;
  EL_INFO     c_el_info[1];
  REAL_B      c_lambda;
  int         i, inside, k, dim = el_info->mesh->dim;
  PARAMETRIC *parametric  = el_info->mesh->parametric;
  bool        notaffine   = false;

  REAL_D      g_xy_temp[1];
  EL_INFO    *el_info_tmp;

  REAL_B      lambda_tmp[1];

  if (IS_LEAF_EL(el)) {

    if (parametric) {
      notaffine = parametric->init_element(el_info, parametric);
    }
/*----------------------------------------------------------------------------*/
/*--  parametric case  -------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
    if (parametric && notaffine) {
      /* prevent expensive call of param_wtc when probability of
       * success is too small */
      if (outside >= 0) { 
	if (lambda[outside] < -1) {
#if 0
	  WARNING("The point is too far away from our curved boundary to get "
		  "reasonable results off param_wtc, returning nothing.\n");
#endif
	  return(false);
	}
      }

      COPY_DOW(g_xy, g_xy_temp[0]);
      COPY_BAR(DIM_MAX, lambda, lambda_tmp[0]);

      el_info_tmp = el_info;

      parametric->world_to_coord(el_info_tmp, 1,
				 (const REAL_D *)g_xy_temp,
				 lambda_tmp, &k);
      COPY_BAR(DIM_MAX, lambda_tmp[0], lambda);

      if (k == -1) {       /* we are inside the element/domain */
	*final_el_info = *el_info;
	for (i=0; i<=dim; i++) final_lambda[i] = lambda[i];
	return(true);
      }
      else if (k >= 0) {   /* point is outside the current element */
#if 0  /* this should work, activate when needed*/
	*final_el_info = *el_info;                         
	for (i=0; i<=dim; i++) final_lambda[i] = lambda[i];
#endif
	if (g_xy0){
	  WARNING("Use of xy0 and s not implemented yet, "
		  "returning nothing.\n");
	  return(false);
	}
	return(false);
      }
      else if (k == -2) {  /* There's no hope of finding barycentric
			    * coordinates for this element.
			    * Return false */
	WARNING("Newton failed on leaf level, so the point is "
		"outside the domain. lambda is not usable!\n");
	return(false);
      }
/*----------------------------------------------------------------------------*/
/*--  non-parametric or affine case  -----------------------------------------*/
/*----------------------------------------------------------------------------*/
    } else {
      *final_el_info = *el_info;
      if (outside < 0) {
	for (i=0; i<=dim; i++) final_lambda[i] = lambda[i];
	return(true);
      }
      else 
      {  /* outside */
	if (g_xy0) 
	{ /* find boundary point of [xy0, xy] */
	  REAL s;

	  world_to_coord(el_info, g_xy0, c_lambda);

	  s = lambda[outside] / (lambda[outside] - c_lambda[outside]);
	  for (i=0; i<=dim; i++) {
	    final_lambda[i] = s * c_lambda[i] + (1.0-s) * lambda[i];
	  }
	  if (g_sp) *g_sp = s;

	  if(dim == 3)
	    MSG("outside finest level on el %d: s=%.3le\n", INDEX(el), s);

	  return(false);  /* ??? */
	}
	else return(false);
      }
    }
  }

/*----------------------------------------------------------------------------*/
/*--  descend further if leaf-level not reached yet  -------------------------*/
/*----------------------------------------------------------------------------*/

  el_info->fill_flag |= FILL_COORDS; // TODO: quick workaround,
				     // vertex_coords should do this

  switch(dim) {
  case 1:
    if (lambda[0] >= lambda[1]) {
      fill_elinfo(0, FILL_ANY, el_info, c_el_info);
      if (outside >= 0) 
      {
	outside = world_to_coord_1d(c_el_info, g_xy, c_lambda);
	if (outside >= 0) ERROR("point outside domain\n");
      }
      else {
	c_lambda[0] = lambda[0] - lambda[1];
	c_lambda[1] = 2.0 * lambda[1];
      }
    }
    else {
      fill_elinfo(1, FILL_ANY, el_info, c_el_info);
      if (outside >= 0) 
      {
	outside = world_to_coord_1d(c_el_info, g_xy, c_lambda);
	if (outside >= 0) ERROR("point outside domain\n");
      }
      else {
	c_lambda[1] = lambda[1] - lambda[0];
	c_lambda[0] = 2.0 * lambda[0];
      }
    }
    break;
#if DIM_MAX > 1
  case 2:
#if 1

    if (lambda[0] >= lambda[1]) {
      fill_elinfo(0, FILL_ANY, el_info, c_el_info);

      if (!parametric) {
	if (el->new_coord) {
	  outside = world_to_coord_2d(c_el_info, g_xy, c_lambda);
	
	  if (c_lambda[1] < 0.0) {
	    /* choose the other child */
	    fill_elinfo(1, FILL_ANY, el_info, c_el_info);
	    outside = world_to_coord_2d(c_el_info, g_xy, c_lambda);
	  }
	  if (outside >= 0 && IS_LEAF_EL(c_el_info->el)) {
	    ERROR("outside curved boundary child 0\n");
	  }
	} else {
	  c_lambda[0] = lambda[2];
	  c_lambda[1] = lambda[0] - lambda[1];
	  c_lambda[2] = 2.0 * lambda[1];
	}
      } else {
	/* get correct coords so we can use world_to_coord */
	parametric->vertex_coords(c_el_info);

	outside = world_to_coord_2d(c_el_info, g_xy, c_lambda);
	
	if (c_lambda[1] < 0.0) {
	  /* choose the other child */
	  fill_elinfo(1, FILL_ANY, el_info, c_el_info);

	  /* get correct coords so we can use world_to_coord */
	  parametric->vertex_coords(c_el_info);
	  outside = world_to_coord_2d(c_el_info, g_xy, c_lambda);
	}
	if (outside >= 0 && IS_LEAF_EL(c_el_info->el)) {
	  ERROR("outside curved boundary child 0\n");
	}

      }
    } else {
      fill_elinfo(1, FILL_ANY, el_info, c_el_info);

      if (!parametric) {
	if (el->new_coord) {
	  outside = world_to_coord_2d(c_el_info, g_xy, c_lambda);

	  if (c_lambda[0] < 0.0) {
	    /* choose the other child */
	    fill_elinfo(0, FILL_ANY, el_info, c_el_info);
	    outside = world_to_coord_2d(c_el_info, g_xy, c_lambda);
	  }
	  if (outside >= 0 && IS_LEAF_EL(c_el_info->el)) {
	    ERROR("outside curved boundary child 1\n");
	  }
	} else {
	  c_lambda[0] = lambda[1] - lambda[0];
	  c_lambda[1] = lambda[2];
	  c_lambda[2] = 2.0 * lambda[0];
	}
      } else {
	/* get correct coords so we can use world_to_coord */
	parametric->vertex_coords(c_el_info);

	outside = world_to_coord_2d(c_el_info, g_xy, c_lambda);
	
	if (c_lambda[0] < 0.0) {
	  /* choose the other child */
	  fill_elinfo(0, FILL_ANY, el_info, c_el_info);
	  /* get correct coords so we can use world_to_coord */
	  parametric->vertex_coords(c_el_info);

	  outside = world_to_coord_2d(c_el_info, g_xy, c_lambda);
	}
	if (outside >= 0 && IS_LEAF_EL(c_el_info->el)) {
	  ERROR("outside curved boundary child 1\n");
	}
      }
    }
#endif
    break;
#if DIM_MAX > 2
  case 3:

    if(parametric) {
      ERROR_EXIT("Refined meshes are not yet supported for 3D! Abort...\n");
    }

    if (!parametric) {
      if (el->new_coord) {
	int ichild, c_outside;

	if (lambda[0] >= lambda[1])
	  ichild = 0;
	else
	  ichild = 1;
	fill_elinfo(ichild, FILL_ANY, el_info, c_el_info);
	c_outside = world_to_coord_3d(c_el_info, g_xy, c_lambda);
      
	if (c_outside>=0) {  /* test is other child is better... */
	  REAL_B  c_lambda2;
	  int     c_outside2;
	  EL_INFO c_el_info2[1];
	
	  fill_elinfo(1-ichild, FILL_ANY, el_info, c_el_info2);
	  c_outside2 = world_to_coord_3d(c_el_info2, g_xy, c_lambda2);
	
	  MSG("new_coord CHILD %d: outside=%d, "
	      "lambda=(%.2lf %.2lf %.2lf %.2lf)\n",
	      ichild, c_outside, c_lambda[0], c_lambda[1], 
	      c_lambda[2],c_lambda[3]);
	  MSG("new_coord CHILD %d: outside=%d, "
	      "lambda=(%.2lf %.2lf %.2lf %.2lf)\n",
	      1-ichild, c_outside2, c_lambda2[0], c_lambda2[1],
	      c_lambda2[2], c_lambda2[3]);
	
	  /* if (c_lambda[0] < c_lambda2[0]) { */
	  if ((c_outside2 < 0) ||
	      (c_lambda2[c_outside2] > c_lambda[c_outside])) {

	    for (i=0; i <= dim; i++) c_lambda[i] = c_lambda2[i];
	    c_outside = c_outside2;
	    *c_el_info = *c_el_info2;
	    ichild = 1 - ichild;
	  }
	}
	outside = c_outside;
      
	/* if (outside >= 0) ERROR("outside curved boundary child 0\n"); */
      }
      else {  /* no new_coord */
	if (lambda[0] >= lambda[1]) {
	  fill_elinfo(0, FILL_ANY, el_info, c_el_info);
	  MSG("transform lambda child 0\n");
	  c_lambda[0] = lambda[0] - lambda[1];
	  c_lambda[1] = lambda[child_vertex_3d[el_info->el_type][0][1]];
	  c_lambda[2] = lambda[child_vertex_3d[el_info->el_type][0][2]];
	  c_lambda[3] = 2.0 * lambda[1];
	}
	else {
	  fill_elinfo(1, FILL_ANY, el_info, c_el_info);
	  MSG("transform lambda child 1\n");
	  c_lambda[0] = lambda[1] - lambda[0];
	  c_lambda[1] = lambda[child_vertex_3d[el_info->el_type][1][1]];
	  c_lambda[2] = lambda[child_vertex_3d[el_info->el_type][1][2]];
	  c_lambda[3] = 2.0 * lambda[0];
	}
      }
    } else {
	int ichild, c_outside;

	if (lambda[0] >= lambda[1])
	  ichild = 0;
	else
	  ichild = 1;
	fill_elinfo(ichild, FILL_ANY, el_info, c_el_info);
          /* get correct coords so we can use world_to_coord */
	parametric->vertex_coords(c_el_info);

	c_outside = world_to_coord_3d(c_el_info, g_xy, c_lambda);
	
	if (c_outside>=0) {  /* test is other child is better... */
	  REAL_B  c_lambda2;
	  int     c_outside2;
	  EL_INFO c_el_info2[1];
	
	  fill_elinfo(1-ichild, FILL_ANY, el_info, c_el_info2);
            /* get correct coords so we can use world_to_coord */
	  parametric->vertex_coords(c_el_info);

	  c_outside2 = world_to_coord_3d(c_el_info2, g_xy, c_lambda2);
	
	  MSG("new_coord CHILD %d: outside=%d, "
	      "lambda=(%.2lf %.2lf %.2lf %.2lf)\n",
	      ichild, c_outside, c_lambda[0], c_lambda[1], 
	      c_lambda[2],c_lambda[3]);
	  MSG("new_coord CHILD %d: outside=%d, "
	      "lambda=(%.2lf %.2lf %.2lf %.2lf)\n",
	      1-ichild, c_outside2, c_lambda2[0], c_lambda2[1],
	      c_lambda2[2], c_lambda2[3]);
	
	  /* if (c_lambda[0] < c_lambda2[0]) { */
	  if ((c_outside2 < 0) ||
	      (c_lambda2[c_outside2] > c_lambda[c_outside])) {

	    for (i=0; i <= dim; i++) c_lambda[i] = c_lambda2[i];
	    c_outside = c_outside2;
	    *c_el_info = *c_el_info2;
	    ichild = 1 - ichild;
	  }
	}
	outside = c_outside;
      
	/* if (outside >= 0) ERROR("outside curved boundary child 0\n"); */
    }
#endif
#endif
  }
    
  inside = find_el_at_pt_recursive(c_el_info, c_lambda, outside);
  return(inside);
}

int find_el_at_pt(MESH *mesh, const REAL_D xy, EL_INFO **el_info_p,
		  FLAGS fill_flag,
		  REAL_B bary, const MACRO_EL *start_mel,
		  const REAL_D xy0, REAL *sp)
{
  const MACRO_EL      *mel;
  REAL_B               lambda;
  EL_INFO              mel_info[1];
  int                  i, inside, k, dim = mesh->dim;
  PARAMETRIC          *parametric = mesh->parametric;

  TEST_EXIT(el_info_p, "need pointer to pointer to an el_info structure\n");

  if (start_mel)
    mel = start_mel;
  else
    mel = mesh->macro_els;

  mel_info->fill_flag = fill_flag|FILL_COORDS;
  g_xy  = xy;
  g_xy0 = xy0;
  g_sp  = sp;

  fill_macro_info(mesh, mel, mel_info);

  if (parametric) {
    DEBUG_TEST_EXIT(mesh->parametric->use_reference_mesh,
		    "You must enable the use_reference_mesh entry in the "
		    "PARAMETRIC structure to use this function.\n");

#if 0 // TODO: don't use LAGRANGE_PARAM_DATA
    if (data->strategy != PARAM_STRAIGHT_CHILDS) {
      ERROR_EXIT("Please use PARAM_STRAIGHT_CHILDS. Abort...\n");
    }
#endif
  }

/*    At this point, don't bother whether the mesh is parametric or    */
/*    not. This will be done at leaf level, for init_element does not  */
/*    work on macro elements. One may try to use preserve_coarse_blah  */
/*    but that replaces one problem with another:                      */
/*                                                                     */
/*    refined         unrefined     Triangulation of the unit ball     */
/*                                  starting with macro element        */
/*  y ^            y ^              (1,0), (0,1), (0,0). When using    */
/*   _|__           _|__            parametric degree 2, the quad      */
/*  1 |   \        1 |  \           point X of the refined simplex     */
/*    |   /\         |   \          will be projected to the border    */
/*    |  /  X        |    \X        of the unit ball, but the macro    */
/*    | /    \       |     \        element just uses a polynomial     */
/*    |/________>    |______\__>    of degree 2, so x seems to be      */
/*           |   x          |   x   outside the domain.                */
/*           1              1                                          */
/*                                                                     */
  while ((k = world_to_coord(mel_info, xy, lambda)) >= 0) {
    if (mel->neigh[k]) {
      mel = mel->neigh[k];
      fill_macro_info(mesh, mel, mel_info);
      continue;
    }
#if 0
    WARNING("No neighbour found! Check if your mesh is convex.\n");
#endif
    break;
  }
  
  /* now, descend in tree to find leaf element at point */
  /* don't do pointless descends */
  if ( (k>=0)  && parametric) { 
    if ((lambda[k] < -1)){
#if 0
      WARNING("The point is either too far away from our curved boundary to "
	      "get reasonable results off param_wtc or the domain is not "
	      "convex. Returning nothing.\n");
#endif
      return (false);
    }
  }
  inside = find_el_at_pt_recursive(mel_info, lambda, k);
  for (i = 0; i <= dim; i++) bary[i] = final_lambda[i];
  *el_info_p = final_el_info;
  
  /* if ((k>=0) || (!inside)) WAIT; */
  return(inside);
}
