/* This file is part of the Neper software package. */
/* Copyright (C) 2003-2014, Romain Quey. */
/* See the COPYING file in the top-level directory. */

#include"net_seed_neigh_.h"

void
SeedSetSize (struct SEEDSET *pSeedSet)
{
  int i, j, k;
  int ver, poly;
  double *tmp;
  int **DFaceVer;
  double **s = (*pSeedSet).Size;
  struct SEEDSET SeedSet;
  struct TESL Tesl;

  neut_tesl_set_zero (&Tesl);
  neut_seedset_set_zero (&SeedSet);
  SeedSet.morpho = ut_alloc_1d_char (8);
  sprintf (SeedSet.morpho, "poisson");

  if ((*pSeedSet).N <= 0)
  {
    ut_print_messagewnc (2, 72, "The domain has no seed.");
    abort ();
  }

  if (((*pSeedSet).Size[0][0] >= (*pSeedSet).Size[0][1])
      || ((*pSeedSet).Size[1][0] >= (*pSeedSet).Size[1][1])
      || ((*pSeedSet).Size[2][0] >= (*pSeedSet).Size[2][1]))
  {
    ut_print_messagewnc (2, 72, "Bad domain size.");
    abort ();
  }

  /* Building SeedSet */
  SeedSet.N = (*pSeedSet).N;
  /* SeedSet.Id=(*pSeedSet).Id; */
  SeedSet.SeedCoo = ut_alloc_2d (SeedSet.N + 1, 3);
  ut_array_2d_memcpy (SeedSet.SeedCoo + 1, SeedSet.N, 3,
		      (*pSeedSet).SeedCoo + 1);
  SeedSet.SeedWeight = ut_alloc_1d (SeedSet.N + 1);

  /* Building tessellation to calculate the neighbourhood zone. */
  CreateTesl (SeedSet, (*pSeedSet).Size, &Tesl);

  DFaceVer = ut_alloc_2d_int (7, Tesl.VerQty + 1);
  neut_tesl_domainfacever (Tesl, DFaceVer);

  (*pSeedSet).nSize = ut_alloc_2d (3, 2);

  for (i = 1; i <= 6; i++)
  {
    /* tmp[j] is the distance bw ver j and its poly centres. */
    tmp = ut_alloc_1d (DFaceVer[i][0] + 1);

    for (j = 1; j <= DFaceVer[i][0]; j++)
    {
      ver = DFaceVer[i][j];

      for (k = 0; k <= 3; k++)
      {
	poly = Tesl.VerSeed[ver][k];

	if (poly >= 1)
	{
	  tmp[j] = ut_space_dist (Tesl.VerCoo[ver], Tesl.SeedCoo[poly]);
	  break;
	}
      }
    }

    (*pSeedSet).nSize[(i - 1) / 2][(i - 1) % 2] =
      s[(i - 1) / 2][(i - 1) % 2] + pow (-1, i) * ut_array_1d_max (tmp + 1, DFaceVer[i][0]);

    ut_free_1d (tmp);
  }

  /* Calculating the volume */
  CalcNeighVol (pSeedSet);

  return;
}


/* SeedSetSeedsPer defines:
 * nSeedCoo, nSeedQty, nMaster
 */
extern void
SeedSetSeedsPer (struct SEEDSET *pSeedSet)
{
  int i, j, id;
  int *per = ut_alloc_1d_int (3);
  double *coo = ut_alloc_1d (3);
  /* shortcuts: */
  double **s = (*pSeedSet).Size;	/* domain size          */
  double **ns = (*pSeedSet).nSize;	/* domain neigh size    */

  /* Distributing the seeds that are into the sd+neigh zone
   * these are the max sizes.
   */
  (*pSeedSet).nSeedCoo = ut_alloc_2d (26 * (*pSeedSet).N + 1, 3);
  (*pSeedSet).nMaster = ut_alloc_1d_int (26 * (*pSeedSet).N + 1);

  id = 0;
  for (per[2] = -1; per[2] <= 1; per[2]++)	/* Z-periodicity */
    for (per[1] = -1; per[1] <= 1; per[1]++)	/* Y-periodicity */
      for (per[0] = -1; per[0] <= 1; per[0]++)	/* X-periodicity */
	if (per[2] == 0 && per[1] == 0 && per[0] == 0)
	  continue;
	else
	  for (i = 1; i <= (*pSeedSet).N; i++)
	  {
	    for (j = 0; j < 3; j++)
	      coo[j] = (*pSeedSet).SeedCoo[i][j] +
		per[j] * (s[j][1] - s[j][0]);

	    if ((coo[0] > ns[0][0] && coo[0] < s[0][0])
	     || (coo[0] < ns[0][1] && coo[0] > s[0][1])
	     || (coo[1] > ns[1][0] && coo[1] < s[1][0])
	     || (coo[1] < ns[1][1] && coo[1] > s[1][1])
	     || (coo[2] > ns[2][0] && coo[2] < s[2][0])
	     || (coo[2] < ns[2][1] && coo[2] > s[2][1]))
	    {
	      id++;
	      ut_array_1d_memcpy ((*pSeedSet).nSeedCoo[id], 3, coo);
	      (*pSeedSet).nMaster[id] = i;
	    }
	  }

  (*pSeedSet).nN = id;

  ut_free_1d_int (per);
  ut_free_1d (coo);

  return;
}

extern void
SeedSetSeedsSD (struct SEEDSET *pSeedSet)
{
  int i, j, id;
  double *coo = ut_alloc_1d (3);
  int RandId;

  double **s = (*pSeedSet).Size;
  double **ns = (*pSeedSet).nSize;
  gsl_rng *r;


  /* Calculation of the number of seeds into the neighbourhood zone
   * the mean is NeighRealVol*(*pSeedSet).NDensity.
   * the rand seed is the same than for the inside seed, and
   * we take the second value (the first was for the inside seeds).
   */
  (*pSeedSet).nN = neut_rand_poisson ((*pSeedSet).nVol * (*pSeedSet).NDensity,
				      (*pSeedSet).Random, 2);

  (*pSeedSet).nSeedCoo = ut_alloc_2d ((*pSeedSet).nN + 1, 3);
  (*pSeedSet).nSeedRandId = ut_alloc_1d_int ((*pSeedSet).nN + 1);

  /* Calculating the neighbourhood seeds coo */

  r = gsl_rng_alloc (gsl_rng_ranlxd2);
  gsl_rng_set (r, (*pSeedSet).Random);

  /* skipping the 10*SeedSet.NDensity first values:
   * near SeedSet.NDensity are used for the inside seeds
   * we skip 2*SeedSet.NDensity more to avoid correllation
   * bw the seeds positions of:
   * -MM -n 10 -id 1 -ttype 0 (case 1)
   * -MM -n 10 -id 1 -ttype 2 (case 2)
   * indeed, assuming that the second case has 7 inside seeds, 
   * and if we do not skip 10*10 but 10, the position of the 8th
   * seed of case 1 would be correlated to the 1st neighbourhood
   * seed of case 2 
   * ((one more condition is that the 1st seed is
   * in the neigh zone (ie outside the domain))).
   *... let's keep everything perfect :-)...
   */

  for (i = 1; i <= 10 * (*pSeedSet).NDensity; i++)
  {
    if (gsl_rng_uniform (r) > 10)	// this is to trash
      r = NULL;
  }

  id = 0;
  RandId = 10 * (*pSeedSet).NDensity;
  while (id != (*pSeedSet).nN)
  {
    /* calculating tmp coo */
    for (j = 0; j < 3; j++)
    {
      coo[j] =
	ns[j][0] + gsl_rng_uniform (r) * (ns[j][1] - ns[j][0]);
      RandId++;
    }

    /* if the point is not within the domain */
    if ((coo[0] < s[0][0] || coo[0] > s[0][1])
     || (coo[1] < s[1][0] || coo[1] > s[1][1])
     || (coo[2] < s[2][0] || coo[2] > s[2][1]))
    {
      id++;			/* registering! */
      ut_array_1d_memcpy ((*pSeedSet).nSeedCoo[id], 3, coo);

      (*pSeedSet).nSeedRandId[id] = RandId - 2;
    }
  }

  gsl_rng_free (r);
  ut_free_1d (coo);

  return;
}

/* SeedSet4EmpD defines the neighbouhood for an empty domain (no seed inside
 * it):
 * nSize, nN, nSeedCoo.
 */
void
SeedSet4EmpD (struct SEEDSET *pSeedSet)
{
  int i, j, id;
  int tmpnN = 0, tmpnN2 = 0, RandId;
  double tmpnVol, tmpnVol2;
  double **tmpSeedCoo = NULL;
  int *tmpRandId = NULL;
  double dist;
  double *VerToSeedDist = ut_alloc_1d (9);
  double *coo = ut_alloc_1d (3);
  double *tmpnSize = ut_alloc_1d (7);
  double **VerCoo = ut_alloc_2d (9, 3);

  double **s = (*pSeedSet).Size;	/* MUST be [0,1]**3 */
  gsl_rng *r;

  VerCoo[1][0] = 0;
  VerCoo[1][1] = 0;
  VerCoo[1][2] = 0;
  VerCoo[2][0] = 1;
  VerCoo[2][1] = 0;
  VerCoo[2][2] = 0;
  VerCoo[3][0] = 1;
  VerCoo[3][1] = 1;
  VerCoo[3][2] = 0;
  VerCoo[4][0] = 0;
  VerCoo[4][1] = 1;
  VerCoo[4][2] = 0;
  VerCoo[5][0] = 0;
  VerCoo[5][1] = 0;
  VerCoo[5][2] = 1;
  VerCoo[6][0] = 1;
  VerCoo[6][1] = 0;
  VerCoo[6][2] = 1;
  VerCoo[7][0] = 1;
  VerCoo[7][1] = 1;
  VerCoo[7][2] = 1;
  VerCoo[8][0] = 0;
  VerCoo[8][1] = 1;
  VerCoo[8][2] = 1;
  tmpnSize[1] = tmpnSize[3] = tmpnSize[5] = 0;
  tmpnSize[2] = tmpnSize[4] = tmpnSize[6] = 1;

  ut_array_1d_set (VerToSeedDist + 1, 8, DBL_MAX);

  /* Calculating the neighbourhood seeds coo */

  r = gsl_rng_alloc (gsl_rng_ranlxd2);
  gsl_rng_set (r, (*pSeedSet).Random);

  for (i = 1; i <= 10 * (*pSeedSet).NDensity; i++)
    if (gsl_rng_uniform (r) > 10)	// this is to trash
      r = NULL;
  RandId = 10 * (*pSeedSet).NDensity;

  /* The neigh size is increased until there is at least one seed
   * inside it. */
  while (tmpnN == 0)
  {
    for (i = 1; i <= 6; i++)
      tmpnSize[i] += pow (-1, i);

    tmpnVol = (tmpnSize[2] - tmpnSize[1]) *
      (tmpnSize[4] - tmpnSize[3]) * (tmpnSize[6] - tmpnSize[5]) - 1;

    tmpnN = neut_rand_poisson (tmpnVol * (*pSeedSet).NDensity,
			       (*pSeedSet).Random, 2);
  }

  tmpSeedCoo = ut_alloc_2d (tmpnN + 1, 3);
  tmpRandId = ut_alloc_1d_int (tmpnN + 1);

  /* distributing seeds */
  id = 0;
  while (id != tmpnN)
  {
    /* calculating tmp coo */
    for (j = 0; j < 3; j++)
    {
      coo[j] = tmpnSize[2 * j + 1] +
	gsl_rng_uniform (r) * (tmpnSize[2 * j + 2] - tmpnSize[2 * j + 1]);
      RandId++;
    }

    /* if the point is not within the domain */
    if ((coo[0] < s[0][0] || coo[0] > s[0][1])
     || (coo[1] < s[1][0] || coo[1] > s[1][1])
     || (coo[2] < s[2][0] || coo[2] > s[2][1]))
    {
      id++;			/* registering! */
      ut_array_1d_memcpy (tmpSeedCoo[id], 3, coo);

      tmpRandId[id] = RandId - 2;
    }
  }

  /* Calculating the minimum distances bw the vertices and the seeds. 
   * to determine the real neighbouhood size 
   */
  for (i = 1; i <= 8; i++)
    for (j = 1; j <= tmpnN; j++)
    {
      dist = ut_space_dist (VerCoo[i], tmpSeedCoo[j]);
      VerToSeedDist[i] = ut_num_min (VerToSeedDist[i], dist);
    }

  (*pSeedSet).nSize = ut_alloc_2d (3, 2);

  /* Calculating the real neighbouhood size from tmpnSize, the size of the
   * domain, and the seeds distribution.
   */
  for (i = 1; i <= 3; i++)
  {
    /* lower bounds */
    (*pSeedSet).nSize[i][0] = tmpnSize[2 * i - 1];

    for (j = 1; j <= 8; j++)
      (*pSeedSet).nSize[i][0] =
	ut_num_min ((*pSeedSet).nSize[i][0],
		    VerCoo[j][i] - VerToSeedDist[j]);

    /* upper bounds */
    (*pSeedSet).nSize[i][1] = tmpnSize[2 * i];

    for (j = 1; j <= 8; j++)
      (*pSeedSet).nSize[i][1] =
	ut_num_max ((*pSeedSet).nSize[i][1],
		    VerCoo[j][i] + VerToSeedDist[j]);
  }
  CalcNeighVol (pSeedSet);

  /* parameters for the extra neighbouring zone: vol and N */
  tmpnVol2 = ((*pSeedSet).nSize[0][1] - (*pSeedSet).nSize[0][0]) *
    ((*pSeedSet).nSize[1][1] - (*pSeedSet).nSize[1][0]) *
    ((*pSeedSet).nSize[2][1] - (*pSeedSet).nSize[2][0]) - tmpnVol - 1;

  tmpnN2 =
    neut_rand_poisson (tmpnVol2 * (*pSeedSet).NDensity, (*pSeedSet).Random,
		       3);

  /* Recording the real nN, nSeedCoo and nSeedRandId */
  (*pSeedSet).nN = tmpnN + tmpnN2;
  (*pSeedSet).nSeedCoo = ut_alloc_2d ((*pSeedSet).nN + 1, 3);
  (*pSeedSet).nSeedRandId = ut_alloc_1d_int ((*pSeedSet).nN + 1);

  for (i = 1; i <= tmpnN; i++)
  {
    ut_array_1d_memcpy ((*pSeedSet).nSeedCoo[i], 3, tmpSeedCoo[i]);
    (*pSeedSet).nSeedRandId[i] = tmpRandId[i];
  }

  id = tmpnN;
  while (id != (*pSeedSet).nN)
  {
    /* calculating tmp coo */
    for (j = 0; j < 3; j++)
    {
      coo[j] = (*pSeedSet).nSize[j][0] +
	gsl_rng_uniform (r) * ((*pSeedSet).nSize[j][1] - (*pSeedSet).nSize[j][0]);
      RandId++;
    }

    /* if the point is not within the domain */
    if ((coo[1] < tmpnSize[1] || coo[1] > tmpnSize[2])
	|| (coo[2] < tmpnSize[3] || coo[2] > tmpnSize[4])
	|| (coo[3] < tmpnSize[5] || coo[3] > tmpnSize[6]))
    {
      id++;			/* registering! */
      ut_array_1d_memcpy ((*pSeedSet).nSeedCoo[id], 3, coo);
      (*pSeedSet).nSeedRandId[id] = RandId - 2;
    }
  }

  gsl_rng_free (r);

  ut_free_1d_int (tmpRandId);
  ut_free_2d (tmpSeedCoo, tmpnN + 1);

  ut_free_1d (VerToSeedDist);
  ut_free_1d (coo);
  ut_free_1d (tmpnSize);
  ut_free_2d (VerCoo, 9);

  return;
}
