//
// Tools_Multiple_Node.cpp
//
// Created by Kevin Lea - 05/10/2001 16:51:03
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

#include "Texturing/Tools_Multiple_Node.h"

#include "Core/Asura_Material_System.h"
#include "Debugging/Asura_Assert.h"
#include "Mesh/Asura_Mesh_System.h"
#include "Rendering/Asura_Module.h"

#include "Misc/Tools_Polygon_Selection.h"
#include "Texturing/Tools_Mode_Texturing.h"
#include "Texturing/Tools_Texturing_Texture.h"

///////////////////////////////////////////////////////////////////////
//static member variables
List<TexturingNode*> Texturing_Multiple_Nodes::NodeList;

///////////////////////////////////////////////////////////////////////
/*--------------------------**
** Texturing_Multiple_Nodes **
**--------------------------*/

void Texturing_Multiple_Nodes::Initialise()
{
}

void Texturing_Multiple_Nodes::Shutdown()
{
	while(NodeList.size())
	{
		delete NodeList.first_entry();
		NodeList.delete_first_entry();
	}
}


bool Texturing_Multiple_Nodes::DetermineNodesForSelection(Tools_UV_Editor* pxTextureEdit)
{
	Asura_Assert(!NodeList.size(),"Texturing nodes should have been cleared up");

	//Generate a temporary instance of the undo system so that we can reverse the changes to the UV coordinates should
	//they be scrwed up by the attempt to sort out the tiling.
	VariableSizeUndo xTempUndo(1000);
	xTempUndo.Initialise();


	/*
	First stage is to check each vertex of each selected polygon and either create 
	a new node for it , or add to an existing node
	*/

	PolygonID polygon;
	Tools_Polygon_Selection::StartIteration();
	while(Tools_Polygon_Selection::GetNextSelected(&polygon))
	{
		Tools_Texturing_UndoData_TextureInfo::Create(polygon,&xTempUndo);
		
		//get the mesh data
		Asura_Mesh* mesh = Asura_Module_System::GetMeshForModule(polygon.ModuleIndex);
		
		//and the modules location
		Asura_Vector_3  module_loc;
		Asura_Module_System::GetPositionOfModule(polygon.ModuleIndex,module_loc);
		
		//get the polygon
		Asura_Mesh_Polygon* poly = &mesh->PolygonDataPtr[polygon.PolyIndex];
	
		TexturingNode* nodes[4] = {0,0,0,0};//array of nodes for this polygon
		
		//and check each vertex
		int num_verts = poly->NumberOfVertices();
		int i;
		for(i=0;i<num_verts;i++)
		{
			//get the world coordinates for the vertex
			Asura_Vector_3 pos = module_loc + mesh->VertexDataPtr[poly->VertexID[i]].Position;

			TexturingNode* node = FindNodeAtLocation(pos);
			if(!node)
			{
				//create a new node then
				node = new TexturingNode(pos,NodeList.size());
				//and add to the list
				NodeList.add_entry(node);
			}
			//add this vertex to the node
			node->AddVertex(polygon.ModuleIndex,polygon.PolyIndex,i);

			nodes[i] = node;
		}

		//add the connections for the nodes for this polygon
		for(i=0;i<num_verts;i++)
		{
			nodes[i]->AddConnection(nodes[(i+1)%num_verts]);
		}


	}

	//tell the Tools_UV_Editor about the new number of nodes
	pxTextureEdit->SetNumNodes(NodeList.size());

	if(!SortOutNodeTiling(pxTextureEdit))
	{
		//reverse the UV changse that were done by SortOutNodeTiling()
		xTempUndo.Undo();

		ClearupNodes(pxTextureEdit);
		return false;
	}

	return true;
}

bool Texturing_Multiple_Nodes::SortOutNodeTiling(Tools_UV_Editor* pxTextureEdit)
{
	/*
	The aim here is to find find nodes that have vertices with uv coordinates that are different
	but are the same modulo the width/height (or rather where the fractional part is the same)
	In such cases all the uv cooridnates in the polygon with the lower value will be shifted up
	so that the coordinates for all the vertices using the node match.
	This should mean that if we have 2 quads next to each with one full copy of the texture , then
	the one on the right would be adjusted to have u coordinates going from 1 to 2 instead of 0 to 1.
	Well , that wasn't very clear , oh well....
	*/

	//list of nodes that we need to check.. start off needing to do them all
	List<TexturingNode*> NodesToDo = NodeList;

	int counter = NodesToDo.size();
	int nodes_in_list = NodesToDo.size();
	
	int iChan = Tools_Mode_Texturing::GetPrimaryEditedChannel();
	
	while(NodesToDo.size())
	{
		TexturingNode* node = NodesToDo.first_entry();
		//get the highest uvs for this node
		Asura_Vector_2 max_uv(-10000,-10000);
		LIF<TexturingNodeVertex*> vlif(&node->VertexList);
		for(;!vlif.done();vlif.next())
		{
			//locate the uv coordinates for this vertex
			Asura_Mesh* mesh = Asura_Module_System::GetMeshForModule(vlif()->ModuleIndex);
			Asura_Assert(mesh->TextureDataPtr,"Texture data hasn't been allocated for the mesh");

			//get the texturing info
			Asura_Mesh_TextureInfo* tinfo = &mesh->TextureDataPtr[vlif()->PolyIndex];
			
			max_uv.x = max(max_uv.x,tinfo->U[iChan][vlif()->VertIndex]);
			max_uv.y = max(max_uv.y,tinfo->V[iChan][vlif()->VertIndex]);
		}

		if(node->VertexList.size()>1)
		{
			for(vlif.restart();!vlif.done();vlif.next())
			{
				//see if the coordinates for this vertex need to be shifted
				//locate the uv coordinates for this vertex
				Asura_Mesh* mesh = Asura_Module_System::GetMeshForModule(vlif()->ModuleIndex);
	   			//get the texturing info
				Asura_Mesh_TextureInfo* tinfo = &mesh->TextureDataPtr[vlif()->PolyIndex];

				//work out the difference in coordinates
				Asura_Vector_2 uv_diff;
				uv_diff.x = max_uv.x - tinfo->U[iChan][vlif()->VertIndex];
				uv_diff.y = max_uv.y - tinfo->V[iChan][vlif()->VertIndex];

				//get the difference rounded to the nearest integer
				Asura_Vector_2 uv_diff_int;
				uv_diff_int.x = (float)(int)(uv_diff.x + (uv_diff.x>=0 ? 0.5f : -0.5f));
				uv_diff_int.y = (float)(int)(uv_diff.y + (uv_diff.y>=0 ? 0.5f : -0.5f));

				//if uv_diff and uv_diff_int are close enough , then the uv coordinates are the same , but
				//shifted along to another tile of the texture.
				if(uv_diff_int.Magnitude() && (uv_diff-uv_diff_int).Magnitude()<0.001f)
				{
					//apply the shift to all vertices in the polygon
					Asura_Mesh_Polygon* poly = &mesh->PolygonDataPtr[vlif()->PolyIndex];
					for(int i=0; i<poly->NumberOfVertices(); i++)
					{
						tinfo->U[iChan][i] += uv_diff.x;
						tinfo->V[iChan][i] += uv_diff.y;

						//locate the node for this vertex , and add it back into the list of
						//nodes that we need to check
						TexturingNode* altered_node = FindNodeForVertex(vlif()->ModuleIndex,vlif()->PolyIndex,i);
						Asura_Assert(altered_node,"Definitely ought to be finding a node for this vertex");
						if(!NodesToDo.contains(altered_node))
						{
							NodesToDo.add_entry(altered_node);
						}
					}
				}
			}
		}
		
		pxTextureEdit->Nodes[node->Index] = max_uv;

		//remove this node from the list of nodes to check
		NodesToDo.delete_first_entry();

		counter--;
		if(!counter)
		{
			//we have done each node in the list , make sure that the list
			//is actually getting smaller
			if(NodesToDo.size()>nodes_in_list)
			{
				//not making any progress ,probably in an endless loop
				return false;
			}

			//list has reduced in size since our last check , set the counter so
			//that we check again after looking at each node once more
			counter = NodesToDo.size();
			nodes_in_list = NodesToDo.size();
		}
	}
	pxTextureEdit->FitUsedAreaToNodes();

	return true;
}


void Texturing_Multiple_Nodes::ClearupNodes(Tools_UV_Editor* pxTextureEdit)
{
	while(NodeList.size())
	{
		delete NodeList.first_entry();
		NodeList.delete_first_entry();
	}

	//reset to 4 nodes
	pxTextureEdit->SetNumNodes(4);
}

TexturingNode* Texturing_Multiple_Nodes::FindNodeAtLocation(const Asura_Vector_3& pos)
{
	Asura_Vector_3 diff;
	#define tolerance .004f //4 mm
	/*
	Search through the existing nodes for one at the given location (within tolerance)
	*/

	for(LIF<TexturingNode*> nlif(&NodeList);!nlif.done();nlif.next())
	{
		diff = pos - nlif()->Position;
		if(diff.x>-tolerance && diff.x< tolerance &&
		   diff.y>-tolerance && diff.y< tolerance &&
		   diff.z>-tolerance && diff.z< tolerance)
		{
			//close enough
			return nlif();
		}
	}
	//no existing node is close enough
	return NULL;
	
}
TexturingNode* Texturing_Multiple_Nodes::FindNodeForVertex(int module,int poly,int vertex)
{
	//search through the nodes for one that this vertex is attached to
	for(LIF<TexturingNode*> nlif(&NodeList);!nlif.done();nlif.next())
	{
		for(LIF<TexturingNodeVertex*>vlif(&nlif()->VertexList);!vlif.done();vlif.next())
		{
			if(vlif()->ModuleIndex == module && vlif()->PolyIndex == poly && vlif()->VertIndex == vertex)
			{
				return nlif();
			}
		}
	}
	//failed to find the node
	return NULL;
}

void Texturing_Multiple_Nodes::ApplyUVCoordinates(Tools_UV_Editor* pxTextureEdit)
{
	Asura_Assert(pxTextureEdit->NumNodes == NodeList.size(),"Number of nodes is out of sync");

	//aply the current uv coordinates to the selected polygons
	for(int i=0;i<NodeList.size();i++)
	{
		NodeList[i]->ApplyUVCoordinates(pxTextureEdit->Nodes[i]);
	}	
}

int Texturing_Multiple_Nodes::GetConnectedNode(int NodeIndex,int ConnectionIndex)
{
	Asura_Assert(ConnectionIndex >=0,"");

	if(NodeList.size() == 0)
	{
		//not actually using the multiple node stuff.. so each node just has
		//a connection to the next node
		Asura_Assert(NodeIndex>=0 && NodeIndex<4,"NodeIndex out of bounds");
		if(ConnectionIndex == 0)
		{
			return (NodeIndex+1)%4;
		}
		if(ConnectionIndex == 1)
		{
			return (NodeIndex+3)%4;
		}
	}
	else
	{
		Asura_Assert(NodeIndex>=0 && NodeIndex<NodeList.size(),"NodeIndex out of bounds");
		if(ConnectionIndex<NodeList[NodeIndex]->ConnectionList.size())
		{
			return NodeList[NodeIndex]->ConnectionList[ConnectionIndex]->Index;
		}
	}
	return -1;
}

//////////////////////////////////////////////////////////////////////////////////////////
/*---------------**
** TexturingNode **
**---------------*/
TexturingNode::TexturingNode(const Asura_Vector_3& pos,int index)
{
	Position = pos;
	Index = index;
}

TexturingNode::~TexturingNode()
{
	while(VertexList.size())
	{
		delete VertexList.first_entry();
		VertexList.delete_first_entry();
	}
	
}

void TexturingNode::AddVertex(int m_index,int p_index,int v_index)
{
	//add a new TexturingNodeVertex to the list
	VertexList.add_entry(new TexturingNodeVertex(m_index,p_index,v_index));
}

void TexturingNode::AddConnection(TexturingNode* node)
{
	//add to the connection list if not already there
	if(!ConnectionList.contains(node))
	{
		ConnectionList.add_entry(node);
	}
	
	//add the reverse conenction
	if(!node->ConnectionList.contains(this))	
	{
		node->ConnectionList.add_entry(this);
	}
}

void TexturingNode::ApplyUVCoordinates(const Asura_Vector_2& uv)
{
	for(int iChan=0;iChan<ASURA_MATERIAL_MAXTEXTURES;iChan++)
	{
		if(!Tools_Mode_Texturing::IsEditingChannel(iChan)) continue;

		float fUVScale = Tools_Mode_Texturing::GetUVScaleForChannel(iChan);

		//apply this to all the vertices using this node
		for(LIF<TexturingNodeVertex*> vlif(&VertexList);!vlif.done();vlif.next())
		{
			//locate the uv coordinates for this vertex
			Asura_Mesh* mesh = Asura_Module_System::GetMeshForModule(vlif()->ModuleIndex);
			Asura_Assert(mesh->TextureDataPtr,"Texture data hasn't been allocated for the mesh");

			//get the texturing info
			Asura_Mesh_TextureInfo* tinfo = &mesh->TextureDataPtr[vlif()->PolyIndex];

			tinfo->U[iChan][vlif()->VertIndex] = uv.x * fUVScale;
			tinfo->V[iChan][vlif()->VertIndex] = uv.y * fUVScale;

			//make sure the mesh gets updated in the hardware
			Tools_UpdateTexturingOnSurfaces(mesh);
		}
	}
}

//////////////////////////////////////////////////////////////////////////////////////////
/*---------------------**
** TexturingNodeVertex **
**---------------------*/

TexturingNodeVertex::TexturingNodeVertex(int m_index,int p_index,int v_index)
{
	ModuleIndex = m_index;
	PolyIndex = p_index;
	VertIndex = v_index;
}
