/*
 * \file Observations.c
 * \brief: implementation of the Observations class, derived from DataSet class
 */

/*Headers: {{{*/
#ifdef HAVE_CONFIG_H
	#include <config.h>
#else
#error "Cannot compile with HAVE_CONFIG_H symbol! run configure first!"
#endif

#include <vector>
#include <functional>
#include <algorithm>
#include <iostream>

#include "./DataSet.h"
#include "./Observations.h"
#include "../shared/shared.h"
#include "../Container/Container.h"
#include "../classes/classes.h"
#include "../include/include.h"
#include "../modules/modules.h"
#include "../EnumDefinitions/EnumDefinitions.h"
#include "../io/io.h"

using namespace std;
/*}}}*/

/*Object constructors and destructor*/
/*FUNCTION Observations::Observations(){{{*/
Observations::Observations(){
	this->quadtree = NULL;
	return;
}
/*}}}*/
/*FUNCTION Observations::Observations(IssmPDouble* observations_list,IssmPDouble* x,IssmPDouble* y,int n,Options* options){{{*/
Observations::Observations(IssmPDouble* observations_list,IssmPDouble* x,IssmPDouble* y,int n,Options* options){

	/*Intermediaries*/
	int          i,maxdepth,level,counter,index;
	int          xi,yi;
	IssmPDouble  xmin,xmax,ymin,ymax;
	IssmPDouble  offset,minlength,minspacing,mintrimming,maxtrimming;
	Observation *observation = NULL;

	/*Check that observations is not empty*/
	if(n==0) _error_("No observation found");

	/*Get extrema*/
	xmin=x[0]; ymin=y[0];
	xmax=x[0]; ymax=y[0];
	for(i=1;i<n;i++){
		xmin=min(xmin,x[i]); ymin=min(ymin,y[i]);
		xmax=max(xmax,x[i]); ymax=max(ymax,y[i]);
	}
	offset=0.05*(xmax-xmin); xmin-=offset; xmax+=offset;
	offset=0.05*(ymax-ymin); ymin-=offset; ymax+=offset;

	/*Get trimming limits*/
	options->Get(&mintrimming,"mintrimming",-1.e+21);
	options->Get(&maxtrimming,"maxtrimming",+1.e+21);
	options->Get(&minspacing,"minspacing",0.01);
	if(minspacing<=0) _error_("minspacing must > 0");

	/*Get Minimum box size*/
	if(options->GetOption("boxlength")){
		options->Get(&minlength,"boxlength");
		if(minlength<=0)_error_("boxlength should be a positive number");
		maxdepth=reCast<int,IssmPDouble>(log(max(xmax-xmin,ymax-ymin)/minlength +1)/log(2.0));
	}
	else{
		maxdepth = 30;
		minlength=max(xmax-xmin,ymax-ymin)/IssmPDouble((1L<<maxdepth)-1);
	}

	/*Initialize Quadtree*/
	_pprintString_("Generating quadtree with a maximum box size " << minlength << " (depth=" << maxdepth << ")... ");
	this->quadtree = new Quadtree(xmin,xmax,ymin,ymax,maxdepth);

	/*Add observations one by one*/
	counter = 0;
	for(i=0;i<n;i++){

		/*First check limits*/
		if(observations_list[i]>maxtrimming) continue;
		if(observations_list[i]<mintrimming) continue;

		/*First check that this observation is not too close from another one*/
		this->quadtree->ClosestObs(&index,x[i],y[i]);
		if(index>=0){
			observation=dynamic_cast<Observation*>(this->GetObjectByOffset(index));
			if(pow(observation->x-x[i],2)+pow(observation->y-y[i],2) < minspacing) continue;
		}

		this->quadtree->IntergerCoordinates(&xi,&yi,x[i],y[i]);
		this->quadtree->QuadtreeDepth2(&level,xi,yi);
		if((int)level <= maxdepth){
			observation = new Observation(x[i],y[i],xi,yi,counter++,observations_list[i]);
			this->quadtree->Add(observation);
			this->AddObject(observation);
		}
		else{
			/*We need to average with the current observations*/
			this->quadtree->AddAndAverage(x[i],y[i],observations_list[i]);
		}
	}
	_pprintLine_("done");
	_pprintLine_("Initial number of observations: " << n);
	_pprintLine_("  Final number of observations: " << this->quadtree->NbObs);
}
/*}}}*/
/*FUNCTION Observations::~Observations(){{{*/
Observations::~Observations(){
	delete quadtree;
	return;
}
/*}}}*/

/*Methods*/
/*FUNCTION Observations::ClosestObservation{{{*/
void Observations::ClosestObservation(IssmPDouble *px,IssmPDouble *py,IssmPDouble *pobs,IssmPDouble x_interp,IssmPDouble y_interp,IssmPDouble radius){

	/*Output and Intermediaries*/
	int          nobs,i,index;
	IssmPDouble  hmin,h2,hmin2,radius2;
	int         *indices      = NULL;
	Observation *observation  = NULL;

	/*If radius is not provided or is 0, return all observations*/
	if(radius==0) radius=this->quadtree->root->length;

	/*First, find closest point in Quadtree (fast but might not be the true closest obs)*/
	this->quadtree->ClosestObs(&index,x_interp,y_interp);
	if(index>=0){
		observation=dynamic_cast<Observation*>(this->GetObjectByOffset(index));
		hmin = sqrt((observation->x-x_interp)*(observation->x-x_interp) + (observation->y-y_interp)*(observation->y-y_interp));
		if(hmin<radius) radius=hmin;
	}

	/*Compute radius square*/
	radius2 = radius*radius;

	/*Find all observations that are in radius*/
	this->quadtree->RangeSearch(&indices,&nobs,x_interp,y_interp,radius);
	for (i=0;i<nobs;i++){
		observation=dynamic_cast<Observation*>(this->GetObjectByOffset(indices[i]));
		h2 = (observation->x-x_interp)*(observation->x-x_interp) + (observation->y-y_interp)*(observation->y-y_interp);
		if(i==0){
			hmin2 = h2;
			index = indices[i];
		}
		else{
			if(h2<hmin2){
				hmin2 = h2;
				index = indices[i];
			}
		}
	}  

	/*Assign output pointer*/
	if(index>=0){
		observation=dynamic_cast<Observation*>(this->GetObjectByOffset(index));
		*px=observation->x;
		*py=observation->y;
		*pobs=observation->value;
	}
	else{

		*px=UNDEF;
		*py=UNDEF;
		*pobs=UNDEF;
	}
	xDelete<int>(indices);

}/*}}}*/
/*FUNCTION Observations::Distances{{{*/
void Observations::Distances(IssmPDouble* distances,IssmPDouble *x,IssmPDouble *y,int n,IssmPDouble radius){

	IssmPDouble xi,yi,obs;

	for(int i=0;i<n;i++){
		this->ClosestObservation(&xi,&yi,&obs,x[i],y[i],radius);
		if(xi==UNDEF && yi==UNDEF)
		 distances[i]=UNDEF;
		else
		 distances[i]=sqrt( (x[i]-xi)*(x[i]-xi) + (y[i]-yi)*(y[i]-yi) );
	}
}/*}}}*/
/*FUNCTION Observations::ObservationList(IssmPDouble **px,IssmPDouble **py,IssmPDouble **pobs,int* pnobs,IssmPDouble x_interp,IssmPDouble y_interp,IssmPDouble radius,int maxdata){{{*/
void Observations::ObservationList(IssmPDouble **px,IssmPDouble **py,IssmPDouble **pobs,int* pnobs,IssmPDouble x_interp,IssmPDouble y_interp,IssmPDouble radius,int maxdata){

	/*Output and Intermediaries*/
	bool         stop;
	int          nobs,tempnobs,i,j,k,n,counter;
	IssmPDouble  h2,radius2;
	int         *indices      = NULL;
	int         *tempindices  = NULL;
	IssmPDouble *dists        = NULL;
	IssmPDouble *x            = NULL;
	IssmPDouble *y            = NULL;
	IssmPDouble *obs          = NULL;
	Observation *observation  = NULL;

	/*If radius is not provided or is 0, return all observations*/
	if(radius==0) radius=this->quadtree->root->length;

	/*Compute radius square*/
	radius2 = radius*radius;

	/*Find all observations that are in radius*/
	this->quadtree->RangeSearch(&tempindices,&tempnobs,x_interp,y_interp,radius);
	if(tempnobs){
		indices = xNew<int>(tempnobs);
		dists   = xNew<IssmPDouble>(tempnobs);
	}
	nobs = 0;
	for (i=0;i<tempnobs;i++){
		observation=dynamic_cast<Observation*>(this->GetObjectByOffset(tempindices[i]));
		h2 = (observation->x-x_interp)*(observation->x-x_interp) + (observation->y-y_interp)*(observation->y-y_interp);

		if(nobs==maxdata && h2>radius2) continue;
		if(nobs<=maxdata){
			indices[nobs]   = tempindices[i];
			dists[nobs]     = h2;
			nobs++;
		}
		if(nobs==1) continue;

		/*Sort all dists up to now*/
		n=nobs-1;
		stop = false;
		for(k=0;k<n-1;k++){
			if(h2<dists[k]){
				counter=1;
				for(int jj=k;jj<n;jj++){
					j  = n-counter;
					dists[j+1]   = dists[j];
					indices[j+1] = indices[j];
					counter++;
				}
				dists[k]   = h2;
				indices[k] = tempindices[i];
				stop = true;
				break;
			}
			if(stop) break;
		}
	}  
	xDelete<IssmPDouble>(dists);
	xDelete<int>(tempindices);

	if(nobs){
		/*Allocate vectors*/
		x   = xNew<IssmPDouble>(nobs);
		y   = xNew<IssmPDouble>(nobs);
		obs = xNew<IssmPDouble>(nobs);

		/*Loop over all observations and fill in x, y and obs*/
		for (i=0;i<nobs;i++){
			observation=dynamic_cast<Observation*>(this->GetObjectByOffset(indices[i]));
			observation->WriteXYObs(&x[i],&y[i],&obs[i]);
		}
	}

	/*Assign output pointer*/
	xDelete<int>(indices);
	*px=x;
	*py=y;
	*pobs=obs;
	*pnobs=nobs;
}/*}}}*/
/*FUNCTION Observations::ObservationList(IssmPDouble **px,IssmPDouble **py,IssmPDouble **pobs,int* pnobs){{{*/
void Observations::ObservationList(IssmPDouble **px,IssmPDouble **py,IssmPDouble **pobs,int* pnobs){

	/*Output and Intermediaries*/
	int          nobs;
	IssmPDouble *x            = NULL;
	IssmPDouble *y            = NULL;
	IssmPDouble *obs          = NULL;
	Observation *observation  = NULL;

	nobs = this->Size();

	if(nobs){
		x   = xNew<IssmPDouble>(nobs);
		y   = xNew<IssmPDouble>(nobs);
		obs = xNew<IssmPDouble>(nobs);
		for(int i=0;i<this->Size();i++){
			observation=dynamic_cast<Observation*>(this->GetObjectByOffset(i));
			observation->WriteXYObs(&x[i],&y[i],&obs[i]);
		}
	}

	/*Assign output pointer*/
	*px=x;
	*py=y;
	*pobs=obs;
	*pnobs=nobs;
}/*}}}*/
/*FUNCTION Observations::InterpolationIDW{{{*/
void Observations::InterpolationIDW(IssmPDouble *pprediction,IssmPDouble x_interp,IssmPDouble y_interp,IssmPDouble radius,int mindata,int maxdata,IssmPDouble power){

	/*Intermediaries*/
	int         i,n_obs;
	IssmPDouble prediction;
	IssmPDouble numerator,denominator,h,weight;
	IssmPDouble *x   = NULL;
	IssmPDouble *y   = NULL;
	IssmPDouble *obs = NULL;

	/*Some checks*/
	_assert_(maxdata>0);
	_assert_(pprediction);
	_assert_(power>0);

	/*If radius is not provided or is 0, return all observations*/
	if(radius==0) radius=this->quadtree->root->length;

	/*Get list of observations for current point*/
	this->ObservationList(&x,&y,&obs,&n_obs,x_interp,y_interp,radius,maxdata);

	/*If we have less observations than mindata, return UNDEF*/
	if(n_obs<mindata){
		prediction = UNDEF; 
	}
	else{
		numerator   = 0.;
		denominator = 0.;
		for(i=0;i<n_obs;i++){
			h = sqrt( (x[i]-x_interp)*(x[i]-x_interp) + (y[i]-y_interp)*(y[i]-y_interp));
			if (h<0.0000001){
				numerator   = obs[i];
				denominator = 1.;
				break;
			}
			weight = 1./pow(h,power);
			numerator   += weight*obs[i];
			denominator += weight;
		}
		prediction = numerator/denominator; 
	}

	/*clean-up*/
	*pprediction = prediction;
	xDelete<IssmPDouble>(x);
	xDelete<IssmPDouble>(y);
	xDelete<IssmPDouble>(obs);
}/*}}}*/
/*FUNCTION Observations::InterpolationKriging{{{*/
void Observations::InterpolationKriging(IssmPDouble *pprediction,IssmPDouble *perror,IssmPDouble x_interp,IssmPDouble y_interp,IssmPDouble radius,int mindata,int maxdata,Variogram* variogram){

	/*Intermediaries*/
	int           i,j,n_obs;
	IssmPDouble   prediction,error;
	IssmPDouble   numerator,denominator,ratio;
	IssmPDouble  *x            = NULL;
	IssmPDouble  *y            = NULL;
	IssmPDouble  *obs          = NULL;
	IssmPDouble  *Gamma        = NULL;
	IssmPDouble  *GinvG0       = NULL;
	IssmPDouble  *Ginv1        = NULL;
	IssmPDouble  *GinvZ        = NULL;
	IssmPDouble  *gamma0       = NULL;
	IssmPDouble  *ones         = NULL;

	/*Some checks*/
	_assert_(mindata>0 && maxdata>0);
	_assert_(pprediction && perror);

	/*If radius is not provided or is 0, return all observations*/
	if(radius==0) radius=this->quadtree->root->length;

	/*Get list of observations for current point*/
	this->ObservationList(&x,&y,&obs,&n_obs,x_interp,y_interp,radius,maxdata);

	/*If we have less observations than mindata, return UNDEF*/
	if(n_obs<mindata){
		*pprediction = -999.0; 
		*perror      = -999.0; 
		return;
	}

	/*Allocate intermediary matrix and vectors*/
	Gamma  = xNew<IssmPDouble>(n_obs*n_obs);
	gamma0 = xNew<IssmPDouble>(n_obs);
	ones   = xNew<IssmPDouble>(n_obs);

	/*First: Create semivariogram matrix for observations*/
	for(i=0;i<n_obs;i++){
		for(j=0;j<=i;j++){
			//Gamma[i*n_obs+j] = variogram->SemiVariogram(x[i]-x[j],y[i]-y[j]);
			Gamma[i*n_obs+j] = variogram->Covariance(x[i]-x[j],y[i]-y[j]);
			Gamma[j*n_obs+i] = Gamma[i*n_obs+j];
		}
	}
	for(i=0;i<n_obs;i++) ones[i]=1;

	/*Get semivariogram vector associated to this location*/
	//for(i=0;i<n_obs;i++) gamma0[i] = variogram->SemiVariogram(x[i]-x_interp,y[i]-y_interp);
	for(i=0;i<n_obs;i++) gamma0[i] = variogram->Covariance(x[i]-x_interp,y[i]-y_interp);

	/*Solve the three linear systems*/
#if _HAVE_GSL_
	SolverxSeq(&GinvG0,Gamma,gamma0,n_obs); // Gamma^-1 gamma0
	SolverxSeq(&Ginv1, Gamma,ones,n_obs);   // Gamma^-1 ones
	SolverxSeq(&GinvZ, Gamma,obs,n_obs);    // Gamma^-1 Z
#else
	_error_("GSL is required");
#endif

	/*Prepare predictor*/
	numerator=-1.; denominator=0.;
	for(i=0;i<n_obs;i++) numerator  +=GinvG0[i];
	for(i=0;i<n_obs;i++) denominator+=Ginv1[i];
	ratio=numerator/denominator;

	prediction = 0.;
	error      = - numerator*numerator/denominator;
	for(i=0;i<n_obs;i++) prediction += (gamma0[i]-ratio)*GinvZ[i];
	for(i=0;i<n_obs;i++) error += gamma0[i]*GinvG0[i];

	/*clean-up*/
	*pprediction = prediction;
	*perror = error;
	xDelete<IssmPDouble>(x);
	xDelete<IssmPDouble>(y);
	xDelete<IssmPDouble>(obs);
	xDelete<IssmPDouble>(Gamma);
	xDelete<IssmPDouble>(gamma0);
	xDelete<IssmPDouble>(ones);
	xDelete<IssmPDouble>(GinvG0);
	xDelete<IssmPDouble>(Ginv1);
	xDelete<IssmPDouble>(GinvZ);

}/*}}}*/
/*FUNCTION Observations::InterpolationNearestNeighbor{{{*/
void Observations::InterpolationNearestNeighbor(IssmPDouble *pprediction,IssmPDouble x_interp,IssmPDouble y_interp,IssmPDouble radius){

	/*Intermediaries*/
	IssmPDouble x,y,obs;

	/*Get clostest observation*/
	this->ClosestObservation(&x,&y,&obs,x_interp,y_interp,radius);

	/*Assign output pointer*/
	*pprediction = obs;
}/*}}}*/
/*FUNCTION Observations::InterpolationV4{{{*/
void Observations::InterpolationV4(IssmPDouble *pprediction,IssmPDouble x_interp,IssmPDouble y_interp,IssmPDouble radius,int mindata,int maxdata){
	/* Reference:  David T. Sandwell, Biharmonic spline interpolation of GEOS-3
	 * and SEASAT altimeter data, Geophysical Research Letters, 2, 139-142,
	 * 1987.  Describes interpolation using value or gradient of value in any
	 * dimension.*/

	/*Intermediaries*/
	int         i,j,n_obs;
	IssmPDouble prediction,h;
	IssmPDouble *x       = NULL;
	IssmPDouble *y       = NULL;
	IssmPDouble *obs     = NULL;
	IssmPDouble *Green   = NULL;
	IssmPDouble *weights = NULL;
	IssmPDouble *g       = NULL;

	/*Some checks*/
	_assert_(maxdata>0);
	_assert_(pprediction);

	/*If radius is not provided or is 0, return all observations*/
	if(radius==0) radius=this->quadtree->root->length;

	/*Get list of observations for current point*/
	this->ObservationList(&x,&y,&obs,&n_obs,x_interp,y_interp,radius,maxdata);

	/*If we have less observations than mindata, return UNDEF*/
	if(n_obs<mindata || n_obs<2){
		prediction = UNDEF; 
	}
	else{

		/*Allocate intermediary matrix and vectors*/
		Green = xNew<IssmPDouble>(n_obs*n_obs);
		g     = xNew<IssmPDouble>(n_obs);

		/*First: distance vector*/
		for(i=0;i<n_obs;i++){
			h = sqrt( (x[i]-x_interp)*(x[i]-x_interp) + (y[i]-y_interp)*(y[i]-y_interp) );
			if(h>0){
				g[i] = h*h*(log(h)-1.);
			}
			else{
				g[i] = 0.;
			}
		}

		/*Build Green function matrix*/
		for(i=0;i<n_obs;i++){
			for(j=0;j<=i;j++){
				h = sqrt( (x[i]-x[j])*(x[i]-x[j]) + (y[i]-y[j])*(y[i]-y[j]) );
				if(h>0){
					Green[j*n_obs+i] = h*h*(log(h)-1.);
				}
				else{
					Green[j*n_obs+i] = 0.;
				}
				Green[i*n_obs+j] = Green[j*n_obs+i];
			}
		}

		/*Compute weights*/
#if _HAVE_GSL_
		SolverxSeq(&weights,Green,obs,n_obs); // Green^-1 obs
#else
		_error_("GSL is required");
#endif

		/*Interpolate*/
		prediction = 0;
		for(i=0;i<n_obs;i++) prediction += weights[i]*g[i];

	}

	/*clean-up*/
	*pprediction = prediction;
	xDelete<IssmPDouble>(x);
	xDelete<IssmPDouble>(y);
	xDelete<IssmPDouble>(obs);
	xDelete<IssmPDouble>(Green);
	xDelete<IssmPDouble>(g);
	xDelete<IssmPDouble>(weights);
}/*}}}*/
/*FUNCTION Observations::QuadtreeColoring{{{*/
void Observations::QuadtreeColoring(IssmPDouble* A,IssmPDouble *x,IssmPDouble *y,int n){

	int xi,yi,level;

	for(int i=0;i<n;i++){
		this->quadtree->IntergerCoordinates(&xi,&yi,x[i],y[i]);
		this->quadtree->QuadtreeDepth(&level,xi,yi);
		A[i]=(IssmPDouble)level;
	}

}/*}}}*/
/*FUNCTION Observations::Variomap{{{*/
void Observations::Variomap(IssmPDouble* gamma,IssmPDouble *x,int n){

	/*Output and Intermediaries*/
	int          i,j,k;
	IssmPDouble  distance;
	Observation *observation1 = NULL;
	Observation *observation2 = NULL;

	IssmPDouble *counter = xNew<IssmPDouble>(n);
	for(j=0;j<n;j++) counter[j] = 0.0;
	for(j=0;j<n;j++) gamma[j]   = 0.0;

	for(i=0;i<this->Size();i++){
		observation1=dynamic_cast<Observation*>(this->GetObjectByOffset(i));

		for(j=i+1;j<this->Size();j++){
			observation2=dynamic_cast<Observation*>(this->GetObjectByOffset(j));

			distance=sqrt(pow(observation1->x - observation2->x,2.) + pow(observation1->y - observation2->y,2.));
			if(distance>x[n-1]) continue;

			int index = int(distance/(x[1]-x[0]));
			if(index>n-1) index = n-1;
			if(index<0)   index = 0;

			gamma[index]   += 1./2.*pow(observation1->value - observation2->value,2.);
			counter[index] += 1.;
		}
	}

	/*Normalize semivariogram*/
	gamma[0]=0.;
	for(k=0;k<n;k++){
		if(counter[k]) gamma[k] = gamma[k]/counter[k];
	}

	/*Assign output pointer*/
	xDelete<IssmPDouble>(counter);
}/*}}}*/
