#pragma warning (disable:4786)
#include <utils\utils.h>
#include "map.hpp"
#include "move.hpp"
#include "movezone.hpp"
#include <map>

CMapCell g_MapCells[MAPCELLCOUNT][MAPCELLCOUNT];

std::map<int,float> pathmapzone[MAPCELLCOUNT][MAPCELLCOUNT][2];		// index is for the topleft cell of each src,dest pair  the 2 is for the offset table-2

bool SetWeight (int sx,int sy,int offset,int areaentry,int areaexit,float newweight)
{
	int ix,iy,io;
	int index;
	int temp;
  std::map<int,float>::iterator iter;

	ix = sx;
	iy = sy;
	io = offset;

	switch (offsetzone[offset][2])
	{
		case 0:
			ix--;
			temp = areaentry;
			areaentry = areaexit;
			areaexit = temp;
		break;
		case 1:
			iy--;
			temp = areaentry;
			areaentry = areaexit;
			areaexit = temp;
		break;
		case 2:
			io-=2;
		break;
		case 3:
			io-=2;
		break;
	}
	assert (ix>=0);
	assert (ix<MAPCELLCOUNT);
	assert (iy>=0);
	assert (iy<MAPCELLCOUNT);
	assert (io>=0);
	assert (io<2);

	index = areaentry<<4;
	index += areaexit;

	iter = pathmapzone[ix][iy][io].find (index);
	if (iter == pathmapzone[ix][iy][io].end ())
	{
		pathmapzone[ix][iy][io][index] = newweight;
		return true;
	}
	if ((*iter).second > newweight)
	{
		pathmapzone[ix][iy][io][index] = newweight;
		return true;
	}
	return false;
}

float GetWeight (int sx,int sy,int dx,int dy,int areaentry,int areaexit)
{
	if (abs (sx-dx))
	{
		if (sx > dx)
			return GetWeight (sx,sy,0,areaentry,areaexit);
		else
			return GetWeight (sx,sy,2,areaentry,areaexit);
	}
	else if (abs(sy-dy))
	{
		if (sy > dy)
			return GetWeight (sx,sy,1,areaentry,areaexit);
		else
			return GetWeight (sx,sy,3,areaentry,areaexit);
	}
	assert (false);
	return -1.0;
}

float GetWeight (int sx,int sy,int offset,int areaentry,int areaexit)
{
	int ix,iy,io;
	int index;
	int temp;
  std::map<int,float>::iterator iter;

	ix = sx;
	iy = sy;
	io = offset;

	switch (offsetzone[offset][2])
	{
		case 0:
			ix--;
			temp = areaentry;
			areaentry = areaexit;
			areaexit = temp;
		break;
		case 1:
			iy--;
			temp = areaentry;
			areaentry = areaexit;
			areaexit = temp;
		break;
		case 2:
			io-=2;
		break;
		case 3:
			io-=2;
		break;
	}
	if (ix<0)
		return -1;
	if (ix>=MAPCELLCOUNT)
		return -1;
	if (iy<0)
		return -1;
	if (iy>=MAPCELLCOUNT)
		return -1;
	assert (io>=0);
	assert (io<2);
	index = areaentry<<4;
	index += areaexit;
	iter = pathmapzone[ix][iy][io].find (index);
	if (iter == pathmapzone[ix][iy][io].end ())
	{
		return -1.0;
	}
	return (*iter).second;
}

void InitMoveZoneData (void)
{
	int curx,cury;
	int x,y;
	for (y=0,cury = 0;y<MAPCELLCOUNT;y++,cury+=(MAPCELLSIZE-1))
		for (x=0,curx=0;x<MAPCELLCOUNT;x++,curx+=(MAPCELLSIZE-1))
		{
			int left,right,top,bottom;

			left = clamp<int> (0,curx,MAPSIZE-1);
			right = clamp<int> (0,left+MAPCELLSIZE-1,MAPSIZE-1);
			top = clamp<int> (0,cury,MAPSIZE-1);
			bottom = clamp<int> (0,top+MAPCELLSIZE-1,MAPSIZE-1);

			g_MapCells[x][y].SetSize (left,top,right,bottom);
		}
	for (y=0;y<MAPCELLCOUNT;y++)
		for (x=0;x<MAPCELLCOUNT;x++)
		{
			g_MapCells[x][y].CalcWeight ();
			g_MapCells[x][y].CalcInterior ();
			g_MapCells[x][y].CalcEdges ();
		}
}

CMapCell::CMapCell (void)
{
	m_WeightAverage = 0;
	m_PassAverage = 0;
}


CMapCell::~CMapCell (void)
{
}

void CMapCell::CalcWeight (void)
{
	int midx,midy,count;

	m_PassAverage = 0;
	m_WeightAverage = 0;
	count = 0;
	for (midx = m_Left;midx<=m_Right;midx++)
	{
		for (midy=m_Top;midy<=m_Bottom;midy++)
		{
			count++;
			m_WeightAverage+=g_Map[midx][midy].weight;
			if (g_Map[midx][midy].passable)
			{
				m_PassAverage++;
			}
		}
	}
	m_PassAverage /= count;
	m_WeightAverage /= count;
}

void CMapCell::CalcEdges (void)
{
	int x,y;
	for (x=m_Left;x<=m_Right;x++)
	{
		m_AreaData[m_Areas[x-m_Left][0]].flags |= TOPEDGEMASK;
		m_AreaData[m_Areas[x-m_Left][MAPCELLSIZE-1]].flags |= BOTTOMEDGEMASK;
	}
	for (y=m_Top;y<=m_Bottom;y++)
	{
		m_AreaData[m_Areas[0][y-m_Top]].flags |= LEFTEDGEMASK;
		m_AreaData[m_Areas[MAPCELLSIZE-1][y-m_Top]].flags |= RIGHTEDGEMASK;
	}
}

void CMapCell::CalcArea (int sx,int sy,int id,int info)
{
	if (sx < 0)
		return;
	if (sy<0)
		return;
	if (sx>=MAPCELLSIZE)
		return;
	if (sy>=MAPCELLSIZE)
		return;

	if ((m_Areas[sx][sy] == 0) && (SquareInfo (sx+m_Left,sy+m_Top)==info))
	{
		m_Areas[sx][sy] = id;
		CalcArea (sx-1,sy,id,info);
		CalcArea (sx+1,sy,id,info);
		CalcArea (sx,sy-1,id,info);
		CalcArea (sx,sy+1,id,info);
	}
}

void CMapCell::CalcInterior (void)
{
	int x,y,id;
	float count[MAXAREAS];

	for (x=0;x<MAPCELLSIZE;x++)
	{
		for (y=0;y<MAPCELLSIZE;y++)
		{
			m_Areas[x][y] = 0;
		}
	}
	id = 1;
	for (x=0;x<MAPCELLSIZE;x++)
	{
		for (y=0;y<MAPCELLSIZE;y++)
		{
			if (m_Areas[x][y])
				continue;
			assert (id<MAXAREAS);
			m_AreaData[id].info = SquareInfo (x+m_Left,y+m_Top);
			if (Passable (x+m_Left,y+m_Top))
				m_AreaData[id].passable = 1;
			else
				m_AreaData[id].passable = 0;
			CalcArea (x,y,id++,SquareInfo (x+m_Left,y+m_Top));
		}
	}
	memset (count,0,sizeof(int)*MAXAREAS);
	for (id=0;id<MAXAREAS;id++)
		m_AreaData[id].weight = 0;
	for (x=0;x<MAPCELLSIZE;x++)
	{
		for (y=0;y<MAPCELLSIZE;y++)
		{
			m_AreaData[m_Areas[x][y]].weight += g_Map[x+m_Left][y+m_Top].weight;
			count[m_Areas[x][y]]++;
		}
	}
	for (id=1;id<MAXAREAS;id++)
	{
		if (count[id] != 0)
			m_AreaData[id].weight /= count[id];
	}
}

void CMapCell::RenderEdges (CDIBSurface& dest,int dx)
{
	int x,y;

	for (x=m_Left;x<=m_Right;x++)
	{
		dest.Fill (dx+x*DRAWCELLSIZE,m_Top*DRAWCELLSIZE,dx+(x+1)*DRAWCELLSIZE,(m_Top+1)*DRAWCELLSIZE,RGB (0,255,0));
		dest.Fill (dx+x*DRAWCELLSIZE,m_Bottom*DRAWCELLSIZE,dx+(x+1)*DRAWCELLSIZE,(m_Bottom+1)*DRAWCELLSIZE,RGB (0,255,0));
	}
	for (y=m_Top;y<=m_Bottom;y++)
	{
		dest.Fill (dx+m_Left*DRAWCELLSIZE,y*DRAWCELLSIZE,dx+(m_Left+1)*DRAWCELLSIZE,(y+1)*DRAWCELLSIZE,RGB (0,255,0));
		dest.Fill (dx+m_Right*DRAWCELLSIZE,y*DRAWCELLSIZE,dx+(m_Right+1)*DRAWCELLSIZE,(y+1)*DRAWCELLSIZE,RGB (0,255,0));
	}
}

FixedHeap<CALCZONEELEMENT> CALCZONEELEMENT::m_EmptyCalcZoneElement(1);

void CALCZONEELEMENT::operator =(FibHeapNode& RHS)
{
	CALCZONEELEMENT& fred = (CALCZONEELEMENT&) RHS;

	sx = fred.sx;
	sy = fred.sy;
	dx = fred.dx;
	dy = fred.dy;
	sarea = fred.sarea;
	darea = fred.darea;
	dir = fred.dir;
}

bool CALCZONEELEMENT::operator ==(FibHeapNode& RHS)
{
	CALCZONEELEMENT& fred = (CALCZONEELEMENT&) RHS;
	if (sx != fred.sx)
		return false;
	if (sy != fred.sy)
		return false;

	if (dx != fred.dx)
		return false;
	if (dy != fred.dy)
		return false;

	if (sarea != fred.sarea)
		return false;
	if (darea != fred.darea)
		return false;
	if (dir != fred.dir)
		return false;
	return true;
}

bool CALCZONEELEMENT::operator <(FibHeapNode& RHS)
{
	CALCZONEELEMENT& fred = (CALCZONEELEMENT&) RHS;

	float sw,dw;

	sw = GetWeight (sx,sy,dx,dy,sarea,darea);
	dw = GetWeight (fred.sx,fred.sy,fred.dx,fred.dy,fred.sarea,fred.darea);
	if (sw < dw)
		return true;
	if (dw < sw)
		return false;

	if (fred.sx < sx)
		return false;
	if (sx < fred.sx)
		return true;
	if (fred.dx < dx)
		return false;
	if (dx < fred.dx)
		return true;

	if (fred.sy < sy)
		return false;
	if (sy < fred.sy)
		return true;
	if (fred.dy < dy)
		return false;
	if (dy < fred.dy)
		return true;

	if (sarea < fred.sarea)
		return true;
	if (darea < fred.darea)
		return true;

	if (dir < fred.dir)
		return true;

	return true;
}

float GetOneCostZone (int sx,int sy,int offset,int areaentry,int areaexit,int newarea)
{
	float toret;
	int dx,dy;

	dx = sx + offsetzone[offset][0];
	dy = sy + offsetzone[offset][1];

	if (dx<0)
		return -1.0;
	if (dy<0)
		return -1.0;
	if (dx>=MAPCELLCOUNT)
		return -1.0;
	if (dy>=MAPCELLCOUNT)
		return -1.0;

	if (areaentry == areaexit)
	{
		if (!g_MapCells[sx][sy].AreaData (areaentry).passable)
			return -1.0;
		toret = g_MapCells[sx][sy].AreaData (areaentry).weight;
	}
	else
	{
		if (!g_MapCells[sx][sy].AreaData (areaentry).passable)
			return -1.0;
		if (!g_MapCells[sx][sy].AreaData (areaexit).passable)
			return -1.0;
		toret = g_MapCells[sx][sy].AreaData (areaentry).weight/2.0 + g_MapCells[sx][sy].AreaData (areaexit).weight/2.0;
	}
//TODO: add support for connection of areas within the zone
#if 0
	int x,y;
	bool flag;
	switch (offsetzone[offset][2])
	{
		case 0:
			for (y=0;y<MAPCELLSIZE;y++)
			{
				if (g_Map)
				{
				}
			}
		break;
	case 1:
		break;
	case 2:
		break;
	case 3:
		break;
	}
#endif
	
	if (offsetzone[offset][3] != 4)			// just and area change, no new zone
	{
		if (!g_MapCells[dx][dy].AreaData (newarea).passable)
			return -1.0;
		toret += g_MapCells[dx][dy].AreaData (newarea).weight;
		toret *= 0.5;
	}


	return toret;
}

float PathCostGuessZone (int sx,int sy,int dx,int dy)
{
	int t1,t2;

	t1 = dx - sx;
	t2 = dy - dx;

	__asm
	{
		mov eax,t1
		cdq
		xor eax,edx
		sub eax,edx
		mov t1,eax
		mov eax,t2
		cdq
		xor eax,edx
		sub eax,edx
		mov t2,eax
	}
//		return t1+t2;

	return t2 < t1 ? (t1+(t2>>1)) : (t2+(t1>>1));
}

CPoint GlobalToCellIndex (CPoint src)
{
	int i;
	CPoint toret(-1,-1);

	for (i=0;i<MAPCELLCOUNT;i++)
	{
		if (src.x >= g_MapCells[i][0].Left () && src.x <= g_MapCells[i][0].Right())
		{
			toret.x = i;
			break;
		}
	}
	for (i=0;i<MAPCELLCOUNT;i++)
	{
		if (src.y >= g_MapCells[0][i].Top () && src.y <= g_MapCells[0][i].Bottom())
		{
			toret.y = i;
			break;
		}
	}
	return toret;
}

void 	FindLowerEdge (int dx,int dy,int endarea,int& sx,int& sy,int& curarea,int& dir,int startx,int starty,int startarea)
{
	int i,j;
	float cost;
	float minvalue;

	minvalue = FLT_MAX;

	for (j=1;j<MAXAREAS;j++)
	{
		for (i=0;i<OFFSIZEZONE;i++)
		{
			cost = GetWeight (dx,dy,i,j,endarea);
			if (cost != -1)
			{
				if ((startx == dx+offsetzone[i][0]) && (starty == dy+offsetzone[i][1]) && (startarea == j))
				{
					sx = startx;
					sy = starty;
					curarea = j;
					dir = (offsetzone[i][2]+2)& 0x03;
					return;
				}
				if (cost<minvalue)
				{
					minvalue = cost;
					sx = dx + offsetzone[i][0];
					sy = dy + offsetzone[i][1];
					curarea = j;
					dir = (offsetzone[i][2]+2) & 0x03;
				}
			}
		}
	}
}

bool AddCalcElements (FibHeap& calc,int sx,int sy,int area,float startweight)
{
	int i,j,temparea;
	int x,y;
	float cost;
  CALCZONEELEMENT *temp;

	AREADATA& tempdata = g_MapCells[sx][sy].AreaData (area);
	if (tempdata.left_connect)
	{
		i = sx-1;
		j = sy;
		if (i>=0)
		{
			for (y=0;y<MAPCELLSIZE;y++)
			{
				if (g_MapCells[sx][sy].m_Areas[0][y] == area)
				{
					temparea = g_MapCells[i][j].m_Areas[MAPCELLSIZE-1][y];
					cost = GetOneCostZone (sx,sy,0,area,area,temparea);
					assert (cost != -1);
					cost += startweight;
					if (SetWeight (sx,sy,0,area,temparea,cost))
					{
						temp = new CALCZONEELEMENT (sx,sy,i,j,area,temparea,LEFTEDGE);
						calc.Insert (temp);
					}
				}
			}
		}
	}
	if (tempdata.top_connect)
	{
		i = sx;
		j = sy-1;
		if (j>=0)
		{
			for (x=0;x<MAPCELLSIZE;x++)
			{
				if (g_MapCells[sx][sy].m_Areas[x][0] == area)
				{
					temparea = g_MapCells[i][j].m_Areas[x][MAPCELLSIZE-1];
					cost = GetOneCostZone (sx,sy,1,area,area,temparea);
					assert (cost != -1);
					cost += startweight;
					if (SetWeight (sx,sy,1,area,temparea,cost))
					{
						temp = new CALCZONEELEMENT (sx,sy,i,j,area,temparea,TOPEDGE);
						calc.Insert (temp);
					}
				}
			}
		}
	}

	if (tempdata.right_connect)
	{
		i = sx+1;
		j = sy;
		if (i<MAPCELLCOUNT)
		{
			for (y=0;y<MAPCELLSIZE;y++)
			{
				if (g_MapCells[sx][sy].m_Areas[MAPCELLSIZE-1][y] == area)
				{
					temparea = g_MapCells[i][j].m_Areas[0][y];
					cost = GetOneCostZone (sx,sy,2,area,area,temparea);
					assert (cost != -1);
					cost += startweight;
					if (SetWeight (sx,sy,2,area,temparea,cost))
					{
						temp = new CALCZONEELEMENT (sx,sy,i,j,area,temparea,RIGHTEDGE);
						calc.Insert (temp);
					}
				}
			}
		}
	}
	if (tempdata.bottom_connect)
	{
		i = sx;
		j = sy+1;
		if (j<MAPCELLCOUNT)
		{
			for (x=0;x<MAPCELLSIZE;x++)
			{
				if (g_MapCells[sx][sy].m_Areas[x][MAPCELLSIZE-1] == area)
				{
					temparea = g_MapCells[i][j].m_Areas[x][0];
					cost = GetOneCostZone (sx,sy,3,area,area,temparea);
					assert (cost != -1);
					cost += startweight;
					if (SetWeight (sx,sy,3,area,temparea,cost))
					{
						temp = new CALCZONEELEMENT (sx,sy,i,j,area,temparea,BOTTOMEDGE);
						calc.Insert (temp);
					}
				}
			}
		}
	}
	return true;
}

bool CalcPathZone (std::stack<PATHZONEELEMENT> *dest,CPoint gstart,CPoint gend)
{
	int curarea,endarea,startarea;
	int i,j,k;
	FibHeap calc;
	CALCZONEELEMENT *temp;
	CPoint start,end;

	assert (gstart.x>=0);
	assert (gstart.x<MAPSIZE);
	assert (gstart.y>=0);
	assert (gstart.y<MAPSIZE);

	start = GlobalToCellIndex (gstart);
	end = GlobalToCellIndex (gend);


	assert (start.x != -1);
	assert (start.y != -1);
	assert (end.x != -1);
	assert (end.y != -1);
	if (start == end)
	{
// TODO:  need to update if start and end zones are the same yet can't get there
		dest->push (PATHZONEELEMENT (start.x,start.y,end.x,end.y,g_MapCells[start.x][start.y].AreaGlobal (gstart.x,gstart.y),g_MapCells[end.x][end.y].AreaGlobal (gend.x,gend.y),-1));
		return true;
	}


	int x=-1,y=-1;
	bool done;

	while (!dest->empty ())
		dest->pop ();

	for (i=0;i<MAPCELLSIZE;i++)
		for (j=0;j<MAPCELLSIZE;j++)
			for (k=0;k<OFFSIZEZONE;k++)
			{
				pathmapzone[i][j][k].clear ();
			}

	endarea = g_MapCells[end.x][end.y].AreaGlobal (gend.x,gend.y);

	curarea = g_MapCells[start.x][start.y].AreaGlobal (gstart.x,gstart.y);
	startarea = curarea;
	AREADATA& tempdata = g_MapCells[start.x][start.y].AreaData (curarea);

	AddCalcElements (calc,start.x,start.y,curarea,0);

	done = false;
	while (!done)
	{
		temp = (CALCZONEELEMENT *) calc.ExtractMin ();
		if (temp == NULL)
			break;

		if ((temp->dx == end.x) && (temp->dy == end.y) && (temp->darea == endarea))
			done = true;
		else
			AddCalcElements (calc,temp->dx,temp->dy,temp->darea,GetWeight (temp->sx,temp->sy,temp->dx,temp->dy,temp->sarea,temp->darea));

		delete temp;
	}

	while (temp = (CALCZONEELEMENT *) calc.ExtractMin ())
		delete temp;

	if (!done)
		return false;

	done = false;
	int curx,cury;

	curx = end.x;
	cury = end.y;
	curarea = endarea;


	while (!done)
	{
		int dx,dy,darea,dir;
		FindLowerEdge (curx,cury,curarea,dx,dy,darea,dir,start.x,start.y,startarea);
		dest->push (PATHZONEELEMENT (dx,dy,curx,cury,darea,curarea,dir));
		curx = dx;
		cury = dy;
		curarea = darea;
		if ((curx == start.x) && (cury == start.y) && (curarea == startarea))
		{
			done = true;
		}
	}

	return true;
}


