//#  eulerN -- program for statistical estimation of Euler characteristic of a 3-dimensional body
//#  given as a sufficiently dense "volumetric" point sampling
//#
//#  Copyright (c) 2005-2007 Bradford Jones, Konstantin Rybnikov, Karen Daniels, Daniel Klain
//#  Release 2: 1 June 2007 
//#
//#  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 2, or (at your option) any
//#  later version: http://www.gnu.org/licenses/gpl.txt.
//#
//#  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.
//#-----------------------------------------------------------------------------


#include "EulerIE.h"
double EulerIE::overlap2d = .5;
double EulerIE::overlap3d = .5;
math_vector EulerIE::crossproduct(math_vector &v, math_vector &b) // ONLY works for 3d
{
	return math_vector( (v[1]*b[2] - b[1]*v[2]), (v[2]*b[0] - b[2]*v[0]), (b[1]*v[0] - b[0]*v[1]) );
}
//////
///// finds euler characteristic of a 1 dimensional set
//////
int EulerIE::euler1d (vector<double> &pointcloud, double stripwidth)
{
	if (pointcloud.size() < 1)
		return 0;

	sort(pointcloud.begin(), pointcloud.end());
	int numholes = 0;
	double solidbegin = pointcloud[0];
	for (int i = 1; i < pointcloud.size(); i++)
	{
		if ( (pointcloud[i] - pointcloud[i-1]) >= stripwidth)
		{
			if ( (pointcloud[i-1] - solidbegin) >= stripwidth )
				numholes++;

			solidbegin = pointcloud[i];
		}
	}
	if ((pointcloud.back() - solidbegin) < stripwidth)
		numholes--;

	return (numholes +1);
}
//////
///// finds euler characteristic of a 2 dimensional set
//////
int EulerIE::euler2d (vector<d2point> &pointcloud, vector<double> &intervalWidths, int numdirections, vector<math_vector> &directions)
{
	double intervalWidth = intervalWidths[1]; // interval width for 2d
	//ut << " calling euler2d" << endl;
	if (pointcloud.size() == 0)
		return 0;

	int sumOfEuler = 0;
	int directionCounter = numdirections;
	bool isRandom = true;
	math_vector direction(0,0);

	if (directions.size() > 0)
	{
		isRandom = false;
		directionCounter = directions.size();
	}

	for (int i = 0; i < directionCounter; i++)
	{
		//generate ith direction
		if (isRandom)
		{

			direction = direction.getRandomUnitVector(2);
			if (direction[1] < 0) // we only want vectors pointing upwards
				direction = direction*-1; //flip directions

		}
		else
		{
			direction = directions[i];
			direction = direction.norm();
		}


		//find min along direction
		double min = (direction[0] * pointcloud[0].xcoord()) + (direction[1] * pointcloud[0].ycoord()); // dot product
		for (int i = 0; i < pointcloud.size(); i++)
		{
			double a = (direction[0] * pointcloud[i].xcoord()) + (direction[1] * pointcloud[i].ycoord());
			if (a < min)
				min = a;
		}
		//find max along direction
		double max = (direction[0] * pointcloud[0].xcoord()) + (direction[1] * pointcloud[0].ycoord()); // dot product
		for (int i = 0; i < pointcloud.size(); i++)
		{
			double a = (direction[0] * pointcloud[i].xcoord()) + (direction[1] * pointcloud[i].ycoord());
			if (a > max)
				max = a;
		}
		//sum
		int intervalsum = 0;
		double intervalStart = min;
		double intervalEnd = min + intervalWidth;
		vector<double> currentInterval;
		math_vector intervalDirection(2);

		double overlap = EulerIE::getOverlap2d();
		while ((intervalEnd - ((1 - overlap)*intervalWidth)) < max)
		{
			intervalDirection[0] = direction[1];
			intervalDirection[1] = -1 * direction[0];

			//find all points that are in the current interval
			for (int i = 0; i < pointcloud.size(); i++)
			{
				double a = (direction[0] * pointcloud[i].xcoord()) + (direction[1] * pointcloud[i].ycoord());
				//if (a >= intervalStart && a <= intervalEnd)
				if (a >= intervalStart && a < intervalEnd) // notice >=, <
					currentInterval.push_back(intervalDirection[0]*pointcloud[i].xcoord() + intervalDirection[1]*pointcloud[i].ycoord());
			}
			intervalsum = intervalsum + euler1d(currentInterval, intervalWidths[0]);
			currentInterval.clear();

			//find all points that are in the overlap interval
			if (intervalEnd <= max) // last overlap shouldnt be subtracted
			{
				for (int i = 0; i < pointcloud.size(); i++)
				{
					double a = (direction[0] * pointcloud[i].xcoord()) + (direction[1] * pointcloud[i].ycoord());
					//if (a > intervalEnd - (.5 * intervalWidth) && a < intervalEnd) // notice <, >
					if (a >= intervalEnd - (overlap * intervalWidth) && a < intervalEnd) // notice >=, <
						currentInterval.push_back(intervalDirection[0]*pointcloud[i].xcoord() + intervalDirection[1]*pointcloud[i].ycoord());
				}
				intervalsum = intervalsum - euler1d(currentInterval, intervalWidths[0]);
			}
			intervalStart = intervalStart + (1 - overlap)*intervalWidth;
			intervalEnd = intervalStart + intervalWidth;
			currentInterval.clear();
		}
		sumOfEuler = sumOfEuler + intervalsum;
	}
	double result =  (double) sumOfEuler/directionCounter;
	if ((ceil(result) - result) <= .5)
		return ((int)ceil(result));
	else
		return ((int)floor(result));
}
//////
///// finds euler characteristic of a 3 dimensional set
//////
int EulerIE::euler3d (vector<d3point> &pointcloud, d2euler previousdimension, vector<double> &intervalWidths, int numdirections,
				   int numdirections2d, vector<math_vector> directions3d, vector<math_vector> directions2d)
{
	// set up deltas
	double intervalWidth = intervalWidths[2]; // width for 3d

	if (pointcloud.size() == 0)
		return 0;

	int sumOfEuler = 0;
	int directionCounter = numdirections;
	bool isRandom = true;
	math_vector direction(3);

	if (directions3d.size() > 0)
	{
		isRandom = false;
		directionCounter = directions3d.size();
	}
	//cout << " = " << directionCounter << endl;
	for (int j = 0; j < directionCounter; j++)
	{
		//generate ith direction
		if (isRandom)
		{
			direction = direction.getRandomUnitVector(3);
			if (direction[2] < 0) // we only want vectors pointing upwards
				direction = direction*-1; //flip directions
		}
		else
		{
			direction = directions3d[j];
			direction = direction.norm();
		}

		direction = direction.norm();
			cout << "E.C. Estimate for " << direction;
		//generate orthonormal basis vectors for plane u1 and u2
		math_vector u1(3); // basis vector
		math_vector u2(3); // basis vector
		if (direction[2] < .00001 && direction[2] > -.00001)
			u1 = math_vector(0,0,1); //make sure z coordinate is non-zero, we devide by z below
		else //(1,0,-x/z)
		{
			//u1 = math_vector(1,1,(-1*direction[0] - direction[1])/direction[2]); // changed
			u1 = math_vector(1,0,(-1*direction[0])/direction[2]);
		}

		u2 = crossproduct(u1, direction);
		u1 = u1.norm();
		u2 = u2.norm();
		//cout << "u1 = " << u1 << " u2 = " << u2 << endl;


		//find min along direction
		double min = (direction[0] * pointcloud[0].xcoord()) + (direction[1] * pointcloud[0].ycoord()) + (direction[2] * pointcloud[0].zcoord());
		for (int i = 0; i < pointcloud.size(); i++)
		{
			double a = (direction[0] * pointcloud[i].xcoord()) + (direction[1] * pointcloud[i].ycoord()) + (direction[2] * pointcloud[i].zcoord());
			if (a < min)
				min = a;
		}
		//find max along direction
		double max = (direction[0] * pointcloud[0].xcoord()) + (direction[1] * pointcloud[0].ycoord()) + (direction[2] * pointcloud[0].zcoord()); // dot product
		for (int i = 0; i < pointcloud.size(); i++)
		{
			double a = (direction[0] * pointcloud[i].xcoord()) + (direction[1] * pointcloud[i].ycoord()) + (direction[2] * pointcloud[i].zcoord());
			if (a > max)
				max = a;
		}

		//summation
		int intervalsum = 0;
		double intervalStart = min;
		double intervalEnd = min + intervalWidth;
		vector<d2point> currentInterval;

		double overlap = EulerIE::getOverlap3d();
		while ((intervalEnd - ((1 - overlap)*intervalWidth)) < max)
		{
			//find all points that are in the current interval, insert them into currentInterval and compute previous dimension
			for (int i = 0; i < pointcloud.size(); i++)
			{
				double a = (direction[0] * pointcloud[i].xcoord()) + (direction[1] * pointcloud[i].ycoord()) + (direction[2] * pointcloud[i].zcoord());
				//if (a >= intervalStart && a <= intervalEnd)
				if (a >= intervalStart && a <= intervalEnd) // notice >=, <
					currentInterval.push_back(d2point(u1[0]*pointcloud[i].xcoord() + u1[1]*pointcloud[i].ycoord() + u1[2]*pointcloud[i].zcoord()
					, u2[0]*pointcloud[i].xcoord() + u2[1]*pointcloud[i].ycoord() + u2[2]*pointcloud[i].zcoord()));
			}
			//cout << "2D Slice size = " << currentInterval.size() << endl;

			intervalsum = intervalsum + previousdimension(currentInterval, intervalWidths, numdirections2d, directions2d);

			currentInterval.clear();

			//find all points that are in the overlap interval, insert them into currentInterval and compute previous dimension
			if (intervalEnd <= max) // last overlap shouldnt be subtracted
			{
				for (int i = 0; i < pointcloud.size(); i++)
				{
					double a = (direction[0] * pointcloud[i].xcoord()) + (direction[1] * pointcloud[i].ycoord()) + (direction[2] * pointcloud[i].zcoord());
					//if (a > intervalEnd - (.5 * intervalWidth) && a < intervalEnd) // notice <, >
					if (a >= intervalEnd - (overlap * intervalWidth) && a <= intervalEnd) // notice >=, <
						currentInterval.push_back(d2point(u1[0]*pointcloud[i].xcoord() + u1[1]*pointcloud[i].ycoord() + u1[2]*pointcloud[i].zcoord()
						, u2[0]*pointcloud[i].xcoord() + u2[1]*pointcloud[i].ycoord() + u2[2]*pointcloud[i].zcoord()));
				}

				intervalsum = intervalsum - previousdimension(currentInterval, intervalWidths, numdirections2d, directions2d);
			}
			//intervalStart = intervalStart + .5*intervalWidth;
			intervalStart = intervalStart + (1 - overlap)*intervalWidth;
			intervalEnd = intervalStart + intervalWidth;
			currentInterval.clear();
		}
		cout << " = " << intervalsum << endl;
		sumOfEuler = sumOfEuler + intervalsum;
	}
	double result =  (double) sumOfEuler/directionCounter;
	if ((ceil(result) - result) <= .5)
		return ((int)ceil(result));
	else
		return ((int)floor(result));
}
