/*
 Authors
 Martin Schlather, schlather@math.uni-mannheim.de

 simulation of a random field by hyperplane tessellation

 Copyright (C) 2001 -- 2017 Martin Schlather, 

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USV.
*/

#include <stdio.h>  
#include "def.h"
#include <Basic_utils.h>
#include <General_utils.h>
#include <zzz_RandomFieldsUtils.h>
#include "extern.h"

#include "questions.h"
#include "Processes.h"
#include "avltr_modified.h"
#include "operator.h"

#define BLOCKSIZE 1000
#define BLOCKS 1000
// typedef double *colour_type[BLOCKS];
//typedef unsigned int *code_type[BLOCKS];
typedef double (*randomvar_type)(double p);

#define HYPER_SUPERPOS (COMMON_GAUSS + 1)
#define HYPER_MAXLINES (COMMON_GAUSS + 2)
#define HYPER_MAR_DISTR (COMMON_GAUSS + 3)
#define HYPER_MAR_PARAM (COMMON_GAUSS + 4)
#define HYPER_ADDITIVE (COMMON_GAUSS + 5)



double uniform(double VARIABLE_IS_NOT_USED p) {return UNIFORM_RANDOM;}
double frechet(double p) { 
  return EXP(LOG(-1.0/LOG(UNIFORM_RANDOM)) / p);
}
double bernoulli(double p) {
  return (double) (UNIFORM_RANDOM <= p);
}


int check_hyperplane(model *cov) {
 model 
   *key = cov->key,
   *next= cov->sub[0],
   *sub = key != NULL ? key : next;
 int err,
   dim = OWNLOGDIM(0)
    ; // taken[MAX DIM],
  hyper_param *gp  = &(GLOBAL.hyper);

  ASSERT_CARTESIAN;
  ASSERT_ONESYSTEM;
  ASSERT_UNREDUCED;

  kdefault(cov, HYPER_SUPERPOS, gp->superpos);
  kdefault(cov, HYPER_MAXLINES, gp->maxlines);
  kdefault(cov, HYPER_MAR_DISTR, gp->mar_distr);
  kdefault(cov, HYPER_MAR_PARAM, gp->mar_param);
  kdefault(cov, HYPER_ADDITIVE, true);
  if ((err = checkkappas(cov, false)) != NOERROR) RETURN_ERR(err);
 
  isotropy_type iso = (cov->calling != NULL && CALLINGNR == HYPERPLANE_INTERN)
    ? ISOTROPIC : SYMMETRIC;

  if (key == NULL) {
    if ((err = CHECK(next, dim, dim, PosDefType, XONLY, iso, SCALAR,
		     GaussMethodType)) != NOERROR) RETURN_ERR(err);
  } else { // wenn dies eintritt dann ruft HyperIntern Hyper auf
    model *intern = sub;
    while (intern != NULL && //intern->nr != HYPERPLANE_INTERN && 
	   isAnyDollar(intern)) {
      intern = intern->key != NULL ? intern->key : intern->sub[0];
    }
    if (intern == NULL || MODELNR(intern) != HYPERPLANE_INTERN) {
     BUG;
    } else if (intern != cov) 
      paramcpy(intern, cov, true, true, false, false, false);
 
    if ((err = CHECK(sub, dim,dim, ProcessType, XONLY, CARTESIAN_COORD, SCALAR,
		     GaussMethodType))!= NOERROR) RETURN_ERR(err);
  }


  setbackward(cov, sub);
  if ((err = kappaBoxCoxParam(cov, GAUSS_BOXCOX)) != NOERROR) RETURN_ERR(err);
  if ((err = checkkappas(cov)) != NOERROR) RETURN_ERR(err);

  RETURN_NOERROR;
}


int check_hyperplane_intern(model *cov) {  
  assert(cov->key == NULL);

  model *next= cov->sub[0];  
  int err,
    dim = OWNLOGDIM(0);    
  hyper_param *gp  = &(GLOBAL.hyper);

  kdefault(cov, HYPER_SUPERPOS, gp->superpos);
  kdefault(cov, HYPER_MAXLINES, gp->maxlines);
  kdefault(cov, HYPER_MAR_DISTR, gp->mar_distr);
  kdefault(cov, HYPER_MAR_PARAM, gp->mar_param);
  kdefault(cov, HYPER_ADDITIVE, true);
 
 ASSERT_UNREDUCED;

  if ((err = CHECK(next, dim,  dim, PosDefType, XONLY, SYMMETRIC, 
		   SCALAR, GaussMethodType)) != NOERROR) {
     RETURN_ERR(err);
  }

  //  printf("OK!!!!! %d %d\n", next->pref[Hyperplane], PREF_NONE);  PMI(cov);
  
  if ( P0INT(HYPER_ADDITIVE) && next->pref[Hyperplane] == PREF_NONE)
    //      ((NEXTNR >= DOLLAR && NEXTNR <= LASTDOLLAR) || 
    //       next->sub[0]->pref[Hyperplane] == PREF_NONE))
    RETURN_ERR(ERRORPREFNONE);

  setbackward(cov, next);

  RETURN_NOERROR;
}


void range_hyperplane(model VARIABLE_IS_NOT_USED *cov, range_type *range) {
  GAUSS_COMMON_RANGE;

  range->min[HYPER_SUPERPOS] = 1;
  range->max[HYPER_SUPERPOS] = RF_INF;
  range->pmin[HYPER_SUPERPOS] = 100;
  range->pmax[HYPER_SUPERPOS] = 1000;
  range->openmin[HYPER_SUPERPOS] = false;
  range->openmax[HYPER_SUPERPOS] = true; 

  range->min[HYPER_MAXLINES] = 1;
  range->max[HYPER_MAXLINES] = RF_INF;
  range->pmin[HYPER_MAXLINES] = 1;
  range->pmax[HYPER_MAXLINES] = 5000;
  range->openmin[HYPER_MAXLINES] = false;
  range->openmax[HYPER_MAXLINES] = true; 

  range->min[HYPER_MAR_DISTR] = HYPER_UNIFORM;
  range->max[HYPER_MAR_DISTR] = HYPER_BERNOULLI;
  range->pmin[HYPER_MAR_DISTR] = HYPER_UNIFORM;
  range->pmax[HYPER_MAR_DISTR] = HYPER_BERNOULLI;
  range->openmin[HYPER_MAR_DISTR] = false;
  range->openmax[HYPER_MAR_DISTR] = false; 

  range->min[HYPER_MAR_PARAM] = 0;
  range->max[HYPER_MAR_PARAM] = RF_INF;
  range->pmin[HYPER_MAR_PARAM] = 0.01;
  range->pmax[HYPER_MAR_PARAM] = 10;
  range->openmin[HYPER_MAR_PARAM] = false;
  range->openmax[HYPER_MAR_PARAM] = false; 

  booleanRange(HYPER_ADDITIVE);

}

int struct_hyperplane(model *cov, model VARIABLE_IS_NOT_USED **newmodel) {
  
  if (cov->sub[0]->pref[Hyperplane] == PREF_NONE) RETURN_ERR(ERRORPREFNONE);

  RETURN_NOERROR;
}

int init_hyperplane(model *cov, gen_storage VARIABLE_IS_NOT_USED *S){
  model *next = cov->sub[0];
  location_type *loc = Loc(cov);
  hyper_storage *s = NULL;
  long int lines;

  assert(!PisNULL(HYPER_MAXLINES));
  int d,
    maxlines = P0INT(HYPER_MAXLINES),
    dim = ANYDIM,
    err = NOERROR,  
    optdim=2;// falls dies gelockert wird, so kc->idx[d] nicht vergessen!
  double 
    min[MAXHYPERDIM], max[MAXHYPERDIM],
    *hx = NULL, *hy = NULL, *hz = NULL;

  if (sizeof(unsigned int) != 4)
    SERR("method is written for machines with sizeof(unsigned int) == 4");

  cov->method = Hyperplane;
 

  if (loc->distances) RETURN_ERR(ERRORFAILED);
  if (dim > MAXHYPERDIM) {
    err=ERRORMAXDIMMETH; goto ErrorHandling;
  } 
 
  /* n == number of fields superposed 
     lx+1== grid points on x-axis
     ly+1==                y-
     gridlength== grid distance
     lambda== intensity for the Poisson hyperplanes
     res == result (matrix of simulated values)
     normalize==1 then mean=0, var=1 of the random field
     ==0, n>1 then field is devided by SQRT(n)
  */
  
  
  /* cells are coded by a sequence of binary information.
     Each binary unit indicates if the cell is on the "left"
     hand side or the "right" hand side of a line 
  */


  ONCE_NEW_STORAGE(hyper);
  s = cov->Shyper;
    
  
/****************************************************************/
  /*            Extraction of matching covariances                */
  /****************************************************************/
  if (dim == 1)
    GERR1("'%.50s' valid for dim=2. Got genuine dim=1; this has not been programmed yet.", NICK(cov));
 

  if (dim > optdim || dim < 1) { 
    err = ERRORWRONGDIM;
    goto ErrorHandling;
  }

  if (!loc->grid) {
    GERR("Hyperplane currently only allows for grids and anisotropies along the axes");
  }
  

  /****************************************************************/
  /*            determine size of surrounding rectangle           */
  /****************************************************************/

  assert(loc->caniso == NULL ||
	 (GetLoctsdim(cov) == loc->cani_nrow && PREVXDIM(0) == loc->cani_ncol));

  s->radius = 0.5 * GetDiameter(loc, min, max, s->center);
  for (d=0; d-dim; d++) s->rx[d] = 0.5 * (max[d] - min[d]);
  s->hyperplane =  DefList[NEXTNR].hyperplane;

  if (s->hyperplane == NULL)  {
    err = ERRORFAILED;
     goto ErrorHandling;
  }
  lines = s->hyperplane(s->radius, s->center, s->rx, cov /* !! */, 
			    false, &hx, &hy, &hz);

  if (lines > maxlines) {
    GERR("estimated number of lines exceeds hyper.maxlines");  
  } else if (lines < 0) { err = -lines; goto ErrorHandling; }
  
  err = ReturnOwnField(cov);
  // 
  
ErrorHandling:
  FREE(hx);
  FREE(hy);
  FREE(hz);

  cov->simu.active = err == NOERROR;
  RETURN_ERR(err);
}



int cmpcells(cell_type *aa, cell_type *bb, int *n) {
    return memcmp(aa->code,  bb->code, *n * sizeof(unsigned int));
}

void delcell(cell_type* aa, int VARIABLE_IS_NOT_USED *n) {
    FREE(aa->code);
    FREE(aa);
}

cell_type *determine_cell(double gx, double gy, double* hx, double* hy, 
			  double* hr, int *integers, avltr_tree **tree, 
			  randomvar_type randomvar, double p,
			  cell_type *lastcell)
{ 
    // gx, gy : coordinates
    // hx, hy, hr: list of line coordinates
    // tree: tree for storing already detected cells with at least one
    //       one point in it
    // randomvar: random constant for each cell
    // resindex: numbering of the point with the coordinates (gx, gy)
    // add: maximum or addition of the values for overlaying 
    //      hyperplane tessellations?
    // res: result vector (random field)
  int tt, index, bb;
  unsigned int *cd;
  cell_type *cell;
  
  if ((cell = (cell_type*) MALLOC(sizeof(cell_type))) == NULL){
      goto ErrorHandling;
  }
  cell->code = NULL;
  //  if (*integers != 1)  printf("integ = %d\n", *integers);
  if ((cell->code = (unsigned int*) 
       MALLOC(*integers * sizeof(unsigned int))) == NULL)
      goto ErrorHandling;
 
  cd = cell->code;
  /* calculate the code; if a grid element with */
  /* this code exists, then take this colour    */
  /* (as the two points are then in the same    */
  /* cell) else create a new colour (new cell!) */
  for (tt=index=0; tt<*integers; tt++) {    /* calculate the code... */
    cd[tt] = 0; // of no value
    for (bb=0; bb<32; bb++, index++) {
      cd[tt] <<= 1;
      assert((int) true == 1);
      cd[tt] |= ((hx[index] * gx + hy[index] * gy) < hr[index]);
    }
  }
  if (*tree==NULL) { /* is it the very first point ? */    
      *tree = avltr_create((avl_comparison_func) cmpcells, integers);
      cell->colour = (double) randomvar(p);
      avltr_insert(*tree, cell);
      lastcell = cell; 
  } else { /* search if the calculated code has already appeared (makes
	      sense as it is not the very first point calculated */
      /* as the grid points are visited successively, there is good
	 chance that the previous (calculated) point belongs to the 
	 same cell. Let us check that first! */
      if (memcmp(lastcell->code, cell->code, 
		 *integers * sizeof(unsigned int))
	  && ((lastcell = (cell_type*) *avltr_probe(*tree, cell)) == cell)) {
//	  print("here %d %d \n", 
//		 (int) cell->code[0], (int) lastcell->code[0]);
	  lastcell->colour = (double) randomvar(p);
      } else {
	  delcell(cell, NULL); 
      }
  }
  return lastcell;
  
 ErrorHandling:
  if (cell != NULL) {
     FREE(cell->code);
     UNCONDFREE(cell);
  }
  return NULL;
}

void do_hyperplane(model *cov, gen_storage VARIABLE_IS_NOT_USED *S) {
  location_type
    *loc = Loc(cov);
  int    integers, bits, q, endfor, err, len[MAXHYPERDIM],
    vdim = VDIM0,
    dim = OWNLOGDIM(0),
     totvdim = loc->totalpoints * vdim,
    superpos = P0INT(HYPER_SUPERPOS),
    mar_distr = P0INT(HYPER_MAR_DISTR);
  double
    gx, gy, 
    variance = isDollar(cov) ? P0(DVAR) : 1.0, 
    *hx = NULL,
    *hy = NULL, 
    *hr = NULL, 
     *res = cov->rf,
    E=RF_NA,
    sd=RF_NA,
    mar_param = P0(HYPER_MAR_PARAM);
  long i, j, resindex;
  randomvar_type randomvar=NULL;
  hyper_storage *s = cov->Shyper;
  avltr_tree *tree = NULL;
  cell_type *cell = NULL;
  bool
    additive = (bool) P0INT(HYPER_ADDITIVE);
  SAVE_GAUSS_TRAFO;

  
  switch (mar_distr) {
      case HYPER_UNIFORM : randomvar=uniform; break;
      case HYPER_FRECHET : randomvar=frechet; break;
      case HYPER_BERNOULLI : randomvar=bernoulli; break;
      default : ERR0("random var of unknown type");
  }


  if (additive) for (i=0; i < totvdim; res[i++]=0.0);
  else for (i=0; i < totvdim; res[i++]= (double) RF_NEGINF);// max-stable 
  /* how many Poisson Hyperplanes maximal (on circle x [0,rmax]) ?  --> p */
  

  //  warning("return");  return;
 

  switch (dim) {
      case 1 :
      	ERR0("wrong dimension (1) in hyperplane\n");
      case 2 :
	int nn;
	double deltax, deltay;

	deltax = loc->xgr[0][XSTEP];
	deltay = loc->xgr[1][XSTEP];
	for (i=0; i<dim; i++) len[i] = (int) loc->xgr[i][XLENGTH];

	for(nn=0; nn<superpos; nn++){
	  q = s->hyperplane(s->radius, s->center, s->rx,
			    cov, true, &hx, &hy, &hr);
	  
	  /* as the length of the codes for the cells are naturally a multiple 
	     of number of bits of an integer variable, some lines are added to
	     the simulated ones in order to get a number of lines that is a 
	     multiple of the number of bits of an integer --- the lines are 
	     placed in 2*rmax, so outside the rectangle */

	  bits = 8 * sizeof(unsigned int);
	  integers = (int) (q / bits);
	  if (integers * bits < q) {
	    integers++;
	    endfor = integers * bits;
	    for(; q < endfor; q++) {
	      hx[q] = hy[q] = 0; 
	      hr[q] = 2.0 * s->radius;
	    } 
	  }

	  /* temporary code */
	  if (q==0) {
	    double colour = randomvar(mar_param);
	    for (j=resindex=0; resindex < loc->totalpoints; resindex++) {
		if (additive) res[resindex] += colour;
		else res[resindex] = res[resindex] < colour ? colour
						     : res[resindex];
	    }
	  } else {	  
	    if (isMdiag(Type(loc->caniso, loc->cani_nrow, loc->cani_ncol))) {
	      for (gy=loc->xgr[1][XSTART], resindex=j=0; j<len[1]; j++) {
		for (gx= loc->xgr[0][XSTART], i=0; i<len[0]; i++,
		       resindex++) {
		  //		  print("\n%10g %10g\n", gx, gy);  
		  if ((cell = determine_cell(gx, gy, hx, hy, hr, &integers,
					     &tree, randomvar, mar_param,
					     cell))
		      == NULL) {
		    err = ERRORMEMORYALLOCATION;
		    goto ErrorHandling;
		  }
		  // 		print("%d %d %10g %d %d %d\n",
		  //	       resindex, avltr_count(tree),cell->colour, add, 
		  //		       integers, q);
		  if (additive) res[resindex] +=  cell->colour;
		  else res[resindex] = res[resindex] < cell->colour ?
			 cell->colour : res[resindex];
		  gx += deltax;
		}
		gy += deltay;
	      }  
	    } else {
	      for (j=resindex=0; resindex < loc->totalpoints; resindex++) {
		if ((cell=determine_cell(loc->x[j], loc->x[j+1], 
					 hx, hy, hr,
					 &integers, &tree, randomvar, 
					 mar_param, cell))==NULL){
		  err = ERRORMEMORYALLOCATION;
		  goto ErrorHandling;
		}
		if (additive) res[resindex] += cell->colour;
		else res[resindex] = res[resindex] < cell->colour ?
		       cell->colour : res[resindex];
		j += dim;
	      }
	    }
	    avltr_destroy(tree, delcell);
	  }
	  FREE(hx); FREE(hy); FREE(hr); 
	  tree = NULL;
	}/* for nn */
	break;
      default:	
	ERR0("wrong dimension (>2) in hyperplane\n"); 
  } // switch  (dim)


  return;
  if (additive) {
    if (hasGaussMethodFrame(cov)) {
      switch (mar_distr) {
      case HYPER_UNIFORM : 
	E = 0.5; 
	sd = 1.0 / 12.0;
	break;
      case HYPER_FRECHET :
	assert(mar_param > 2);
	NotProgrammedYet("frechet");
	break;
      case HYPER_BERNOULLI : 
	E = mar_param;
	sd = mar_param * (1.0 - mar_param);
	break;
      default : ERR0("distribution unknown in hyperplane\n");
      }
      sd = SQRT(variance / (superpos * sd));
      for(i=0; i<loc->totalpoints; i++) 
	res[i] = (double) (((double) res[i] - superpos * E) * sd);    
    
      BOXCOX_INVERSE;
    } else {
      // no standardization up to now -- later alpha-stable ?? TODO
    }
  } else {
    // no standardization up to now -- later max-stable ?? TODO
  }

  return;

 ErrorHandling: 
//  if (PL>0)
  FREE(hx);
  FREE(hy);
  FREE(hr);
  if (tree!=NULL) avltr_destroy(tree, delcell);
  XERR(err); 
  ERR0("hyperplane failed\n");
}
                      
		   


       
   
