//#  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 "Euler2dreeb.h"

coordinates::coordinates()
{
	x = 0; y = 0;
}
coordinates::coordinates(float x1, float y1)
{
	x = x1; y = y1;
}
block::block(float lower, float higher)
{
	lowerbound = lower;
	upperbound = higher;
	isNull = false;
	isExplored = false;
}
/*int strip::searchAdjacent(float low1, float high1) // returns -1 if not found // log(n) time binary search
{
	return searchHelper(low1, high1, 0, holes.size() - 1);
} */
/*int strip::searchHelper(float low1, float high1, int l, int u)
{
	if ( l <= u)
	{
		int m = (u + l) / 2;
		float low = holes[m]->lowerbound;
		float high = holes[m]->upperbound;
		if (overlap(low, high, low1, high1) == true)
			return m;
		else if (high < high1)
			return searchHelper(low1, high1, m +1, u);
		else
			return searchHelper(low1, high1, l, m - 1);
	}
	else
		return -1;
} */

int strip::searchAdjacent(float low1, float high1)
{
	int l = 0;
	int u = holes.size() -1;

	while (l <= u)
	{
		int m = (u + l) /2;
		float low = holes[m]->lowerbound;
		float high = holes[m]->upperbound;
		if (overlap(low, high, low1, high1) == true)
			return m;
		else if (high < high1)
		{
			l = m +1;
		}
		else
		{
			u = m - 1;
		}
	}
	return -1; // not found
}

strip::strip()
{
	top = 0; bottom = 0; //empty
}
strip::strip(float low, float high)
{
	top = high;
	bottom = low;
}
strip::~strip()
{
	for (int i = 0; i < holes.size(); i++)
		delete holes[i];
}

bool overlap (float low1, float high1, float low2, float high2)
{
	if ( (low1 <= high2 && low1 >= low2) || (high1 <= high2 && high1 >= low2) )
		return true;
	else if (high1 >= high2 && low1 <= low2)
		return true;
	else
		return false;
}
/*int processVisit(block* root) // this is a recursive version of processVisit that has been replaced below
{
	//configure current node
	root->isExplored = true;

	//explore all adjacent nodes
	int y = 1;
	for (int i = 0; i < root->adjacencyList.size(); i++)
	{
		if (root->adjacencyList[i]->isExplored == false)
		{
			y = y * processVisit(root->adjacencyList[i]);
		}
	}
	if (root->isNull == true)
		return 0;
	else
		return y;
} */
int processVisit (block* root)
{
	stack<block*> openList;
	openList.push(root);

	int answer = 1; // this will get set to 0 if a block that is adjacent to an infinite range is

	// explore entire subgraph and find any blocks adjacent to infinite ranges. If one such block is found, set answer = 0;
	while (!openList.empty())
	{
		//remove top node and mark it as visited
		block* currentNode = openList.top();
		openList.pop();
		currentNode->isExplored = true;

		if (currentNode->isNull == true)
			answer = 0;

		//add all adjacent nodes to the stack
		for (int i = 0; i < currentNode->adjacencyList.size(); i++)
		{
			if (currentNode->adjacencyList[i]->isExplored == false)
			{
				openList.push(currentNode->adjacencyList[i]);
			}
		}
	}

	return answer;

}
int processGraph(vector<block*> &result)
{
	int z = 0;
	for (int i = 0; i < result.size(); i++)
	{
		if (result[i]->isExplored == false)
		{
			if (processVisit(result[i]) == 1)
				z++;
		}
	};
	return z;
}





void modelData (vector<strip*> &dataModel, vector<d2point> &pointcloud, vector<double> &stripwidths, bool modelhole, math_vector &direction)
{
	double stripWidth = stripwidths[1]; // width for 2d
	int numblocks = 0;

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

	// Model data with an array of strip() objects
	// runs O(m*nlogn) where n = size of pointcloud<> and m # of strips

	strip* tempStrip;
	strip* solidStrip;
	tempStrip = new strip();
	dataModel.push_back(tempStrip); //pushes back an "empty" strip at the begining
	float stripWidthStart = min;
	float stripWidthEnd = min + stripWidth;
	vector<float> currentStrip;
	float a;

	math_vector intervalDirection(2);
	intervalDirection[0] = direction[1];
	intervalDirection[1] = -1 * direction[0];
	while (stripWidthStart <= max)
	{
		//create strip
		for (int i = 0; i < pointcloud.size(); i++)
		{
			a = pointcloud[i].xcoord()*direction[0] + pointcloud[i].ycoord()*direction[1];
			if (a >= stripWidthStart && a < stripWidthEnd)
					currentStrip.push_back(intervalDirection[0]*pointcloud[i].xcoord() + intervalDirection[1]*pointcloud[i].ycoord());
		}
		sort(currentStrip.begin(), currentStrip.end());//sort strip

		if (currentStrip.size() < 2)
			dataModel.push_back(new strip());
		else
		{
			//model data in a strip() object
			tempStrip = new strip(currentStrip[0], currentStrip.back());
			for (int i = 1; i < currentStrip.size(); i++) // find holes of sufficient size, and insert into array
			{
				if ( (currentStrip[i] - currentStrip[i-1]) >= stripwidths[0])
				{
					tempStrip->insertHole(currentStrip[i-1], currentStrip[i]);
					numblocks++;
				}
			}
			if (modelhole == false)
			{
				solidStrip = new strip(tempStrip->bottom, tempStrip->top);
				if (tempStrip->holes.size() < 1)
				{
					solidStrip->holes.push_back(new block(tempStrip->bottom, tempStrip->top));
					dataModel.push_back(solidStrip);
				}
				else
				{
					if ((tempStrip->holes[0]->lowerbound - tempStrip->bottom) >= stripwidths[0])
						solidStrip->holes.push_back(new block(tempStrip->bottom, tempStrip->holes[0]->lowerbound));

					for (int i = 1; i < tempStrip->holes.size(); i++)
					{
						if ((tempStrip->holes[i]->lowerbound - tempStrip->holes[i-1]->upperbound) >= stripwidths[0])
								solidStrip->holes.push_back(new block(tempStrip->holes[i-1]->upperbound, tempStrip->holes[i]->lowerbound));
					}
					if ((tempStrip->top - tempStrip->holes[tempStrip->holes.size()-1]->upperbound) >= stripwidths[0])
						solidStrip->holes.push_back(new block(tempStrip->holes[tempStrip->holes.size()-1]->upperbound, tempStrip->top));

					delete tempStrip;
					dataModel.push_back(solidStrip);
				}
			}
			else
				dataModel.push_back(tempStrip);
		}
		stripWidthStart = stripWidthEnd;
		stripWidthEnd = stripWidthStart + stripWidth;
		currentStrip.clear();
	}
	dataModel.push_back(new strip());

	/*cout << "reading off strips\n";
		for (int i = 0; i < dataModel.size(); i++)
		{
			cout << "strip# " << i << " has range " << dataModel[i]->bottom << "to"
			<< dataModel[i]->top << "\n";
			for (int j = 0; j < dataModel[i]->holes.size(); j++)
			{
				cout << "\t strip# " << i <<" has hole# "  << j << "range " << dataModel[i]->holes[j]->lowerbound << " to "
			<< dataModel[i]->holes[j]->upperbound << "\n";
			}
	}
	*/
}
int count(vector<d2point> &pointcloud, vector<double> &stripwidths, bool modelhole, math_vector &direction)
{

	float stripWidth = stripwidths[1]; // width for 2d
	// model data
	vector<strip*> dataModel;
	modelData(dataModel, pointcloud, stripwidths, modelhole, direction);
	vector<block*> result;
	int numedges = 0;
	int overlapIndex;

	for (int i = 1; i < dataModel.size() - 1; i++)
	{
		//cout << "dataModel # " << i << "\n";
		for (int j = 0; j < dataModel[i]->holes.size(); j++)
		{
			//cout << "dataModel # " << i << "--" << j <<"th hole\n";
			float holetop = dataModel[i]->holes[j]->upperbound;
			float holebottom = dataModel[i]->holes[j]->lowerbound;

			// check if current hole is adjacent to infinte range
			if (modelhole && (holetop > dataModel[i-1]->top || holebottom > dataModel[i-1]->top
				|| holetop < dataModel[i-1]->bottom || holebottom < dataModel[i-1]->bottom
				|| holetop > dataModel[i + 1]->top || holebottom > dataModel[i+1]->top
				|| holetop < dataModel[i+1]->bottom || holebottom < dataModel[i+1]->bottom))
			{
				//cout << "found infinite on " << i << " \n";
				dataModel[i]->holes[j]->isNull = true;
			}
			//cout << "passed\n";

			overlapIndex = dataModel[i-1]->searchAdjacent(holebottom, holetop);

		//	cout << "index is " << overlapIndex << "\n";
			if (overlapIndex == -1) // no adjacent pices, new hole
			{
				//cout << "new hole on" << i << " \n";
				result.push_back(dataModel[i]->holes[j]);
			}
			else
			{
				dataModel[i-1]->holes[overlapIndex]->adjacencyList.push_back(dataModel[i]->holes[j]);
				dataModel[i]->holes[j]->adjacencyList.push_back(dataModel[i-1]->holes[overlapIndex]);
				numedges = numedges +2;
				int up = overlapIndex +1;
				int down = overlapIndex -1;

				//check above
				while ( up < dataModel[i-1]->holes.size() && up >= 0 &&
					overlap(dataModel[i-1]->holes[up]->lowerbound, dataModel[i-1]->holes[up]->upperbound, holebottom, holetop) == true)
				{
					numedges = numedges +2;
					dataModel[i-1]->holes[up]->adjacencyList.push_back(dataModel[i]->holes[j]);
					dataModel[i]->holes[j]->adjacencyList.push_back(dataModel[i-1]->holes[up]);
					up++;
				}
				//check below
				while ( down < dataModel[i-1]->holes.size() && down >= 0 &&
					overlap(dataModel[i-1]->holes[down]->lowerbound, dataModel[i-1]->holes[down]->upperbound, holebottom, holetop) == true)
				{
			    	numedges = numedges +2;
					dataModel[i-1]->holes[down]->adjacencyList.push_back(dataModel[i]->holes[j]);
					dataModel[i]->holes[j]->adjacencyList.push_back(dataModel[i-1]->holes[down]);
					down--;
				}
			}
		}
	}
	//cout << "num of edges= " << numedges << endl;
	//cout << "->>result size " << result.size() << endl;
	int answer = processGraph(result);
	//cout << " answer is " << answer << endl;


	//reclaim memory
	for (int i = 0; i < dataModel.size(); i++)
		delete dataModel[i];


	return answer;
}

//This algorirandom directioon and pre-defined direction, but this algorithm was designed with the intent on working across
//all directions.
int Euler2dreeb (vector<d2point>& pointcloud, vector<double> &stripwidths, int numdirections2d, vector<math_vector> &directions2d)
{
	if (pointcloud.size() == 0)
		return 0;

	int sumOfEuler = 0;
	int directionCounter = numdirections2d;
	bool isRandom = true;

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

	math_vector direction(2);

	for (int i = 0; i < directionCounter; i++)
	{
		if (isRandom)
		{
			direction = direction.getRandomUnitVector(2);
			if (direction[1] < 0) // we only want vectors pointing upwards
				direction = direction*-1; //flip directions

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

		sumOfEuler += (count(pointcloud, stripwidths, false, direction) - count(pointcloud, stripwidths, true, direction));
	}
	float result =  (float) sumOfEuler/directionCounter;
	if ((ceil(result) - result) <= .5)
		return ((int)ceil(result));
	else
		return ((int)floor(result));
}
