#include <assert.h>
#include "table.h"
#include <float.h>
#include <stdlib.h>
#include <math.h>

double table_c::operator()(double x) const
{
 if(!within_data(x))	{
	assert(all_set());
	return out_of_range(x);
	}

 double delta;
 int i = (int)(delta = (x-off)*ratio);
 return data[i].tslope*(delta-i) + data[i].tintercept;
}

table_c::table_c(const nRange<double>& rr, int n,
						double (*f)(double x, const void *p),
						const void *p,
						BOOL up_ok, BOOL dn_ok)
: abstract_table_c(rr, up_ok, dn_ok), data(n+1), off(rr.low)
{
 assert(!!rr);
 assert(n > 0);
 assert(f);
 ratio = (n-1)/(rr.high-rr.low);
 int i;
 for(i=0;i<n;i++)
	data[i].tintercept = (*f)(rr.low + i/ratio, p);
 data[n].tintercept = data[n-1].tintercept;
 for(i=0;i<n;i++)
	data[i].tslope = data[i+1].tintercept-data[i].tintercept;
 data[n].tslope = 0.0;
 dnval = data[0].tintercept;	upval = data[data.size()-1].tintercept;
}

table_c::table_c(const nRange<double>& rr, int n, const float *x,
		BOOL up_ok, BOOL dn_ok)
: abstract_table_c(rr, up_ok, dn_ok), data(n+1), off(rr.low)
{
 assert(rr && n>0 && x);
 ratio = (n-1)/(rr.high-rr.low);
 int i;
 for(i=0;i<n;i++)
	data[i].tintercept = x[i];
 data[n].tintercept = data[n-1].tintercept;
 for(i=0;i<n;i++)
	data[i].tslope = data[i+1].tintercept-data[i].tintercept;
 data[n].tslope = 0.0;
 dnval = data[0].tintercept;	upval = data[data.size()-1].tintercept;
}

table_c::table_c(const nRange<double>& rr, C Block<double>& x,
		BOOL up_ok, BOOL dn_ok)
: abstract_table_c(rr, up_ok, dn_ok), data(x.size()+1), off(rr.low)
{
 assert(rr && x.size()>0 && x.size()>0);
 ratio = (x.size()-1)/(rr.high-rr.low);
 int i;
 for(i=0;i<x.size();i++)
	data[i].tintercept = x[i];
 data[x.size()].tintercept = data[x.size()-1].tintercept;
 for(i=0;i<x.size();i++)
	data[i].tslope = data[i+1].tintercept-data[i].tintercept;
 data[x.size()].tslope = 0.0;
 dnval = data[0].tintercept;	upval = data[data.size()-1].tintercept;
}

table_c::table_c(const nRange<double>& rr, C Block<float>& x,
		BOOL up_ok, BOOL dn_ok)
: abstract_table_c(rr, up_ok, dn_ok), data(x.size()+1), off(rr.low)
{
 assert(rr && x.size()>0 && (const float *)x);
 ratio = (x.size()-1)/(rr.high-rr.low);
 int i;
 for(i=0;i<x.size();i++)
	data[i].tintercept = x[i];
 data[x.size()].tintercept = data[x.size()-1].tintercept;
 for(i=0;i<x.size();i++)
	data[i].tslope = data[i+1].tintercept-data[i].tintercept;
 data[x.size()].tslope = 0.0;
 dnval = data[0].tintercept;	upval = data[data.size()-1].tintercept;
}


nRange<double> abstract_table_c::valid_range() const
{
 return nRange<double>(dnok?-infinity():covered_range.low,
						upok?infinity():covered_range.high);
}


double ntable_c::operator()(double x) const
{
 if(!within_data(x))	{
	assert(all_set());
	return out_of_range(x);
	}

 int i = (int)((x-initoff)*initratio);
 if(i < 0)	i = 0;
 else if(i >= data.size()-1)	i = data.size()-2;

 while(data[i].x() < x)	{
	i++;
	assert(i>=0);
	}
 while(data[i].x() > x)	{
	i--;
	assert(i>=0);
	}
 int j = i;
 while(data[j].x() < x)	{
	j++;
	assert(j < data.size());
	}

 if(j == i)
	return data[i].y();

 return (data[j].y()*(x-data[i].x()) + data[i].y()*(data[j].x()-x))
			/ (data[j].x() - data[i].x());
}

double abstract_table_c::out_of_range(double x) C
{
	if(x<covered_range && dnok)
		return dnval;
	else if(x>covered_range && upok)
		return upval;
	return quiet_nan(0);
}

ntable_c::ntable_c()
	: data(), abstract_table_c()
{}

table_c::table_c()
	: data(), abstract_table_c()
{}


LOCAL int table_ddoublet_comp(C void *a, C void *b)
{
 C table_ddoublet *aa = (C table_ddoublet *)a;
 C table_ddoublet *bb = (C table_ddoublet *)b;

 return aa->x()>bb->x() ? 1 : (aa->x()<bb->x() ? -1 : 0);
}

ntable_c::ntable_c(C Block<table_ddoublet>& x,
							BOOL up_ok, BOOL dn_ok)
: abstract_table_c(up_ok, dn_ok), data(x)
{
 // Sort the data into ascending order:
 qsort((table_ddoublet*)data, data.size(),
			sizeof(table_ddoublet), table_ddoublet_comp);
 assert(data[0].x() <= data[data.size()-1].x());

 {			/* Eliminate duplicate X values. */
  int i, j, nsame=0;
  float jlastx = 0.0;
  double jsumy = 0.0;
  for(i=0,j=0;j<=data.size();j++)	{
	if(nsame>0 && (j==data.size() || data[j].x()!=jlastx))	{
		data[i] = table_ddoublet(jlastx, jsumy/nsame);
		jsumy = 0.0;	nsame = 0;
		i++;
		}
	if(j < data.size())	{
		jlastx = data[j].x();
		jsumy += data[j].y();
		nsame++;
		}
	}
 data.size(i);
 }

 set_cr(nRange<double>(data[0].x(), data[data.size()-1].x()));
 dnval = data[0].y();	upval = data[data.size()-1].y();
 // Initoff and initratio are a simple linear approximation to where the
 // desired data is to be found in the table.  I get them from a least-squares
 // fit.
 int i;
 double S=0, Sx=0, Sy=0, Sxx=0, Sxy=0;
 C double x0 = data[0].x();
 for(i=0;i<data.size();i++)	{
	C double tmp = data[i].x() - x0;
	S += 1;
	Sx += tmp;
	Sy += i;
	Sxx += tmp*tmp;
	Sxy += tmp*i;
	}
 C double delta = S*Sxx-Sx*Sx;
 assert(delta != 0.0);
 initratio = (S*Sxy - Sx*Sy)/delta;
 assert(initratio > 0.0);
 initoff = x0 - ((Sxx*Sy-Sx*Sxy)/delta ) / initratio;
}

abstract_table_c::abstract_table_c(C nRange<double>& r, BOOL u_o, BOOL d_o)
: covered_range(r), upok(u_o), dnok(d_o)
{}

abstract_table_c::abstract_table_c(BOOL u_o, BOOL d_o)
: covered_range(), upok(u_o), dnok(d_o)
{}


ntable_c operator*(C abstract_table_c& a, double b)
{
 table_iterator i(a);
 Block<table_ddoublet> x(a.size());

 for(i.reset();i.valid();i.next())
	x[i] = table_ddoublet(i.x(), i.y()*b);

 return ntable_c(x, a.valid_range().high==infinity(),
					a.valid_range().low==-infinity());
}

ntable_c operator+(C abstract_table_c& a, double b)
{
 table_iterator i(a);
 Block<table_ddoublet> x(a.size());

 for(i.reset();i.valid();i.next())
	x[i] = table_ddoublet(i.x(), i.y()+b);

 return ntable_c(x, a.valid_range().high==infinity(),
					a.valid_range().low==-infinity());
}

ntable_c operator+(C abstract_table_c& a, C abstract_table_c& b)
{
 table_iterator i(a), j(b);
 Block<table_ddoublet> x(a.size()+b.size());

 int k=0;
 for(i.reset();i.valid();i.next())
	if(b.valid_range() == i.x())
		x[k++] = table_ddoublet(i.x(), i.y()+b(i.x()));
 for(j.reset();j.valid();j.next())
	if(a.valid_range() == j.x())
		x[k++] = table_ddoublet(j.x(), j.y()+a(j.x()));
 x.size(k);

 return ntable_c(x,
				 a.valid_range().high==infinity()
					&& b.valid_range().high==infinity(),
				 a.valid_range().low==-infinity()
					&& b.valid_range().low==-infinity());
}

ntable_c operator*(C abstract_table_c& a, C abstract_table_c& b)
{
 table_iterator i(a), j(b);
 Block<table_ddoublet> x(a.size()+b.size());

 C Range<double> bcr = b.valid_range();
 int k=0;
 for(i.reset();i.valid();i.next())
	if(bcr == i.x())
		x[k++] = table_ddoublet(i.x(), i.y()*b(i.x()));
 C Range<double> acr = a.valid_range();
 for(j.reset();j.valid();j.next())
	if(acr == j.x())
		x[k++] = table_ddoublet(j.x(), j.y()*a(j.x()));
 x.size(k);

 return ntable_c(x,
				 a.valid_range().high==infinity()
					&& b.valid_range().high==infinity(),
				 a.valid_range().low==-infinity()
					&& b.valid_range().low==-infinity());
}

ntable_c apply(C abstract_table_c& a, double (*f)(double))
{
 assert(f);
 table_iterator i(a);
 Block<table_ddoublet> x(a.size());

 for(i.reset();i.valid();i.next())
	x[i] = table_ddoublet(i.x(), (*f)(i.y()));

 return ntable_c(x, a.valid_range().high==infinity(),
					a.valid_range().low==-infinity());
}
