//------------------------------------------------------------
// Tony Hyun Kim
// Spring 2007
// 18.354 Project: Lattice gas
// NODE MANAGER IMPLEMENTATION
//------------------------------------------------------------

#include "node_manager.h"
#include <time.h>

NodeManager	myNodes;

NodeManager::NodeManager()
{ 
	srand( time(NULL) );
	fTolerance = 0.2f * g_spacing;  
}

const list<NODE>& NodeManager::GetNodeData()
{
	return node_data;
}

void NodeManager::RandomlyFillParticles()
{
	list<NODE>::iterator iter;
	BYTE layout;

	for(iter = node_data.begin(); iter != node_data.end(); iter++)
	{
		if( !(rand()%10 == 0) )
		{
			layout = g_PosFlags[rand()%6];
			(*iter).current.occ = layout;
			if( rand()%2 == 0 )
				(*iter).current.type = layout;
		}
		else
			(*iter).current.occ = POS_ALL_OFF;
	}
}

void NodeManager::ClearAllParticles()
{
	list<NODE>::iterator iter;

	for(iter = node_data.begin(); iter != node_data.end(); iter++)
	{
		(*iter).current.occ = POS_ALL_OFF;
	}
}

void NodeManager::Tick()
{
	static list<NODE>::iterator iter;

	for(iter = node_data.begin(); iter != node_data.end(); iter++)
	{
		(*iter).CalculateNext();
	}

	for(iter = node_data.begin(); iter != node_data.end(); iter++)
	{
		(*iter).Update();
	}
}

void NodeManager::ReorderNPx(NODE* target)
{
	static vector<NODE*>::iterator iter;

	for(iter = np_x.begin(); iter != np_x.end(); iter++)
	{
		if(*iter == target)
		{
			np_x.erase(iter);
			break;
		}
	}

	iter = lower_bound(np_x.begin(),np_x.end(),target,SortByX());
	np_x.insert(iter,target);
}

void NodeManager::ReorderNPy(NODE* target)
{
	static vector<NODE*>::iterator iter;

	for(iter = np_y.begin(); iter != np_y.end(); iter++)
	{
		if(*iter == target)
		{
			np_y.erase(iter);
			break;
		}
	}

	iter = lower_bound(np_y.begin(),np_y.end(),target,SortByY());
	np_y.insert(iter,target);
}

void NodeManager::AddNode(NODE& n)
{
	static vector<NODE*>::iterator location;
	static NODE* pt;

	// Add node into data
	node_data.push_back(n);

	// We add the new data pointer into the appropriate location
	pt = &node_data.back();

	location = lower_bound(np_x.begin(),np_x.end(),pt,SortByX());
	np_x.insert(location,pt);

	location = lower_bound(np_y.begin(),np_y.end(),pt,SortByY());
	np_y.insert(location,pt);

	UpdateNode(pt);
}

void NodeManager::UpdateNode(NODE* np)
{
	static node* oldNeighbors[6];
	static int i;

	for(i=0; i<6;i++)
		oldNeighbors[i] = np->neighbors[i];
	
	NodeFindNeighbors(np);
	for(i=0; i<6; i++)
	{
		if(oldNeighbors[i] != np->neighbors[i])
		{		
			if(oldNeighbors[i])
				NodeFindNeighbors(oldNeighbors[i]);
			if(np->neighbors[i])
				NodeFindNeighbors(np->neighbors[i]);
		}
	}
}

//------------------------------------------------------------
// Notice that we must be careful in managing the nodes. 
//	1) We need to identify the location in the data vector
//	2) We need to remove the references from the pointer vectors
//------------------------------------------------------------
void NodeManager::DelNode(NODE* np)
{
	static vector<NODE*>::iterator iter_np;
	static list<NODE>::iterator    iter_node;

	if( np == 0 )
		return;

	iter_np = find(np_x.begin(),np_x.end(),np);

	if( iter_np != np_x.end() )
		np_x.erase(iter_np);

	iter_np = find(np_y.begin(),np_y.end(),np);
	if( iter_np != np_y.end() )
		np_y.erase(iter_np);

	for(iter_node=node_data.begin(); iter_node != node_data.end(); iter_node++)
	{
		if( &(*iter_node) == np )
		{
			node_data.erase(iter_node);
			break;
		}
	}

	// Now we ask the neighbors of the deleted node to reconfigure their neighbors
	for(int i=0; i<6; i++)
	{
		if( np->neighbors[i] )
			NodeFindNeighbors(np->neighbors[i]);
	}
	
}

float NodeManager::Distance(const D3DXVECTOR2& v1, const D3DXVECTOR2& v2)
{
	static D3DXVECTOR2 diff;
	diff = v1 - v2;
	return sqrt(pow(diff.x,2)+pow(diff.y,2));
}

bool NodeManager::CloseEnough(float distance)
{	return (distance < fTolerance);	}

NODE* NodeManager::GetNodeByPos(const D3DXVECTOR2& target)
{
	static vector<NODE*>::iterator x_lower, x_upper;
	static vector<NODE*>::iterator y_lower, y_upper;
	static vector<NODE*>::iterator iter, min_iter, end;
	
	static vector<NODE*> x_result;
	static vector<NODE*> y_result;
	static vector<NODE*> result;

	static int xSize, ySize;
	static float distance, min_distance;

	static NODE x_min_tol, x_max_tol;
	static NODE y_min_tol, y_max_tol;

	//------------------------------------------------------------
	// Set the bounds
	//------------------------------------------------------------
	x_min_tol.pos.x = target.x - fTolerance;
	x_max_tol.pos.x = target.x + fTolerance;
	y_min_tol.pos.y = target.y - fTolerance;
	y_max_tol.pos.y = target.y + fTolerance;

	//------------------------------------------------------------
	// Scan for lower bound;
	// Terminate if we do not obtain a hit
	//------------------------------------------------------------
	x_lower = lower_bound(np_x.begin(),np_x.end(),&x_min_tol,SortByX());
	if( x_lower == np_x.end() )
		return NULL;
	y_lower = lower_bound(np_y.begin(),np_y.end(),&y_min_tol,SortByY());
	if( y_lower == np_y.end() )
		return NULL;

	x_upper = upper_bound(x_lower,np_x.end(),&x_max_tol,SortByX());	
	y_upper = lower_bound(y_lower,np_y.end(),&y_max_tol,SortByY());

	xSize = static_cast<int>(x_upper-x_lower);
	ySize = static_cast<int>(y_upper-y_lower);

	x_result.resize(xSize);
	y_result.resize(ySize);
	result.resize((xSize<ySize)?xSize:ySize);

	copy(x_lower,x_upper,x_result.begin());
	sort(x_result.begin(),x_result.end());

	copy(y_lower,y_upper,y_result.begin());
	sort(y_result.begin(),y_result.end());
	
	end = set_intersection(x_result.begin(),x_result.end(),
					 y_result.begin(),y_result.end(),
					 result.begin());
	
	min_iter	 = result.begin();
	if( min_iter == end )
		return 0;
	min_distance = Distance((*min_iter)->pos,target);

	for(iter=min_iter+1; iter != end; ++iter)
	{
		distance = Distance((*iter)->pos,target);
		if( distance < min_distance )
		{
			min_iter	 = iter;
			min_distance = distance;
		}
	}
	
	//------------------------------------------------------------
	// Are we within the tolerance level?
	//------------------------------------------------------------
	if(CloseEnough(min_distance))
		return *min_iter;
	else
		return NULL;
}

void NodeManager::NodeFindNeighbors(NODE* np)
{
	for(int i=0; i<6; i++)
	{
		np->neighbors[i] = GetNodeByPos(np->pos+g_dir[i]);
	}
}

void NodeManager::ConnectAllNeighbors()
{
	static list<NODE>::iterator iter;
	for(iter=node_data.begin(); iter != node_data.end(); iter++)
		NodeFindNeighbors(&(*iter));
}