/*
 * OpenClonk, http://www.openclonk.org
 *
 * Copyright (c) 1998-2000, 2005, 2007  Matthes Bender
 * Copyright (c) 2001-2007  Sven Eberhardt
 * Copyright (c) 2003, 2005-2007  Peter Wortmann
 * Copyright (c) 2006-2009, 2012  Günther Brammer
 * Copyright (c) 2012  Nicolas Hake
 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
 *
 * Portions might be copyrighted by other authors who have contributed
 * to OpenClonk.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 * See isc_license.txt for full license and disclaimer.
 *
 * "Clonk" is a registered trademark of Matthes Bender.
 * See clonk_trademark_license.txt for full license.
 */

/* Basic classes for rectangles and vertex outlines */

#include <C4Include.h>
#include <C4Shape.h>

#include <C4Physics.h>
#include <C4Material.h>
#include <C4Landscape.h>
#include <C4Record.h>

bool C4Shape::AddVertex(int32_t iX, int32_t iY)
{
	if (VtxNum>=C4D_MaxVertex) return false;
	VtxX[VtxNum]=iX; VtxY[VtxNum]=iY;
	VtxNum++;
	return true;
}

void C4Shape::Default()
{
	ZeroMem(this,sizeof (C4Shape));
	AttachMat=MNone;
	ContactDensity=C4M_Solid;
}

C4Shape::C4Shape()
{
	Default();
}

void C4Shape::Clear()
{
	ZeroMem(this, sizeof (C4Shape));
}

void C4Shape::Rotate(int32_t iAngle, bool bUpdateVertices)
{
#ifdef DEBUGREC
	C4RCRotVtx rc;
	rc.x=x; rc.y=y; rc.wdt=Wdt; rc.hgt=Hgt; rc.r=iAngle;
	int32_t i = 0;
	for (; i<4; ++i)
		{ rc.VtxX[i]=VtxX[i]; rc.VtxY[i]=VtxY[i]; }
	AddDbgRec(RCT_RotVtx1, &rc, sizeof(rc));
#endif
	int32_t cnt,nvtx,nvty,rdia;

	//int32_t *vtx=VtxX;
	//int32_t *vty=VtxY;
	C4Real mtx[4];
	C4Real fAngle = itofix(iAngle);

	if (bUpdateVertices)
	{

		// Calculate rotation matrix
		mtx[0]=Cos(fAngle); mtx[1]=-Sin(fAngle);
		mtx[2]=-mtx[1]; mtx[3]= mtx[0];
		// Rotate vertices
		for (cnt=0; cnt<VtxNum; cnt++)
		{
			//nvtx= (int32_t) ( mtx[0]*vtx[cnt] + mtx[1]*vty[cnt] );
			//nvty= (int32_t) ( mtx[2]*vtx[cnt] + mtx[3]*vty[cnt] );
			nvtx = fixtoi(mtx[0]*VtxX[cnt] + mtx[1]*VtxY[cnt]);
			nvty = fixtoi(mtx[2]*VtxX[cnt] + mtx[3]*VtxY[cnt]);
			VtxX[cnt]=nvtx; VtxY[cnt]=nvty;
		}

		/* This is freaking nuts. I used the int32_t* to shortcut the
		  two int32_t arrays Shape.Vtx_[]. Without modifications to
		  this code, after rotation the x-values of vertex 2 and 4
		  are screwed to that of vertex 0. Direct use of the array
		  variables instead of the pointers helped. Later in
		  development, again without modification to this code, the
		  same error occured again. I moved back to pointer array
		  shortcut and it worked again. ?!

		  The error occurs after the C4DefCore structure has
		  changed. It must have something to do with struct
		  member alignment. But why does pointer usage vs. array
		  index make a difference?
		*/

	}

	// Enlarge Rect
	rdia= (int32_t) sqrt(double(x*x+y*y)) + 2;
	x=-rdia;
	y=-rdia;
	Wdt=2*rdia;
	Hgt=2*rdia;
#ifdef DEBUGREC
	rc.x=x; rc.y=y; rc.wdt=Wdt; rc.hgt=Hgt;
	for (i=0; i<4; ++i)
		{ rc.VtxX[i]=VtxX[i]; rc.VtxY[i]=VtxY[i]; }
	AddDbgRec(RCT_RotVtx2, &rc, sizeof(rc));
#endif
}

void C4Shape::Stretch(int32_t iCon, bool bUpdateVertices)
{
	int32_t cnt;
	x=x*iCon/FullCon;
	y=y*iCon/FullCon;
	Wdt=Wdt*iCon/FullCon;
	Hgt=Hgt*iCon/FullCon;
	FireTop=FireTop*iCon/FullCon;
	if (bUpdateVertices)
		for (cnt=0; cnt<VtxNum; cnt++)
		{
			VtxX[cnt]=VtxX[cnt]*iCon/FullCon;
			VtxY[cnt]=VtxY[cnt]*iCon/FullCon;
		}
}

void C4Shape::Jolt(int32_t iCon, bool bUpdateVertices)
{
	int32_t cnt;
	y=y*iCon/FullCon;
	Hgt=Hgt*iCon/FullCon;
	FireTop=FireTop*iCon/FullCon;
	if (bUpdateVertices)
		for (cnt=0; cnt<VtxNum; cnt++)
			VtxY[cnt]=VtxY[cnt]*iCon/FullCon;
}

void C4Shape::GetVertexOutline(C4Rect &rRect)
{
	int32_t cnt;
	rRect.x=rRect.y=rRect.Wdt=rRect.Hgt=0;
	for (cnt=0; cnt<VtxNum; cnt++)
	{
		// Extend left
		if (VtxX[cnt]<rRect.x)
		{
			rRect.Wdt+=rRect.x-VtxX[cnt];
			rRect.x=VtxX[cnt];
		}
		// Extend right
		else if (VtxX[cnt]>rRect.x+rRect.Wdt)
			{ rRect.Wdt=VtxX[cnt]-rRect.x; }

		// Extend up
		if (VtxY[cnt]<rRect.y)
		{
			rRect.Hgt+=rRect.y-VtxY[cnt];
			rRect.y=VtxY[cnt];
		}
		// Extend down
		else if (VtxY[cnt]>rRect.y+rRect.Hgt)
			{ rRect.Hgt=VtxY[cnt]-rRect.y; }
	}

	rRect.Hgt+=rRect.y-y;
	rRect.y=y;

}

// Adjust given position to one pixel before contact
// at vertices matching CNAT request.
bool C4Shape::Attach(int32_t &cx, int32_t &cy, BYTE cnat_pos)
{
	// reset attached material
	AttachMat=MNone;
	int xcd = 0;
	int ycd = 0;
	// determine attachment direction
	switch (cnat_pos & (~CNAT_Flags))
	{
	case CNAT_Top:    ycd=-1; break;
	case CNAT_Bottom: ycd=+1; break;
	case CNAT_Left:   xcd=-1; break;
	case CNAT_Right:  xcd=+1; break;
	default: return false;
	}
	int testx = cx;
	int testy = cy;
	bool increase_distance = true;
	bool any_contact = false;
	// Find the nearest position that has at least one vertex adjacent to dense material
	// and no vertices in dense materials
	while (Abs(testx - cx) < AttachRange && Abs(testy - cy) < AttachRange)
	{
		bool found = false;
		for (int i = 0; i < VtxNum; ++i)
		{
			if (VtxCNAT[i] & cnat_pos)
			{
				// get new vertex pos
				int32_t ax = testx + VtxX[i], ay = testy + VtxY[i];
				if (GBackDensity(ax, ay) >= ContactDensity)
				{
					found = false;
					break;
				}
				// can attach here?
				if (GBackDensity(ax + xcd, ay + ycd) >= ContactDensity)
				{
					found = true;
					any_contact = true;
					// store attachment material
					AttachMat = GBackMat(ax + xcd, ay + ycd);
					// store absolute attachment position
					iAttachX = ax + xcd; iAttachY = ay + ycd;
					iAttachVtx = i;
				}

			}
		}
		if (found)
		{
			cx = testx;
			cy = testy;
			return true;
		}
		// Try positions in order of distance from the origin,
		// and alternating the direction
		testx = cx - (testx - cx);
		testy = cy - (testy - cy);
		if (increase_distance)
		{
			testx += xcd;
			testy += ycd;
		}
		increase_distance = !increase_distance;
	}
	return any_contact;
}


bool C4Shape::LineConnect(int32_t tx, int32_t ty, int32_t cvtx, int32_t ld, int32_t oldx, int32_t oldy)
{

	if (VtxNum<2) return false;

	// No modification
	if ((VtxX[cvtx]==tx) && (VtxY[cvtx]==ty)) return true;

	// Check new path
	int32_t ix,iy;
	if (PathFree(tx,ty,VtxX[cvtx+ld],VtxY[cvtx+ld],&ix,&iy))
	{
		// Okay, set vertex
		VtxX[cvtx]=tx; VtxY[cvtx]=ty;
		return true;
	}
	else
	{
		// Intersected, find bend vertex
		bool found = false;
		int32_t cix;
		int32_t ciy;
		for (int irange = 4; irange <= 12; irange += 4)
			for (cix = ix - irange / 2; cix <= ix + irange; cix += irange)
				for (ciy = iy - irange / 2; ciy <= iy + irange; ciy += irange)
				{
					if (PathFree(cix,ciy,tx,ty) && PathFree(cix,ciy,VtxX[cvtx+ld],VtxY[cvtx+ld]))
					{
						found = true;
						goto out;
					}
				}
out:
		if (!found)
		{
			// try bending directly at path the line took
			// allow going through vehicle in this case to allow lines through castles and elevator shafts
			cix = oldx;
			ciy = oldy;
			if (!PathFreeIgnoreVehicle(cix,ciy,tx,ty) || !PathFreeIgnoreVehicle(cix,ciy,VtxX[cvtx+ld],VtxY[cvtx+ld]))
				if (!PathFreeIgnoreVehicle(cix,ciy,tx,ty) || !PathFreeIgnoreVehicle(cix,ciy,VtxX[cvtx+ld],VtxY[cvtx+ld]))
					return false; // Found no bend vertex
		}
		// Insert bend vertex
		if (ld>0)
		{
			if (!InsertVertex(cvtx+1,cix,ciy)) return false;
		}
		else
		{
			if (!InsertVertex(cvtx,cix,ciy)) return false;
			cvtx++;
		}
		// Okay, set vertex
		VtxX[cvtx]=tx; VtxY[cvtx]=ty;
		return true;
	}

	return false;
}

bool C4Shape::InsertVertex(int32_t iPos, int32_t tx, int32_t ty)
{
	if (VtxNum+1>C4D_MaxVertex) return false;
	// Insert vertex before iPos
	for (int32_t cnt=VtxNum; cnt>iPos; cnt--)
		{ VtxX[cnt]=VtxX[cnt-1]; VtxY[cnt]=VtxY[cnt-1]; }
	VtxX[iPos]=tx; VtxY[iPos]=ty;
	VtxNum++;
	return true;
}

bool C4Shape::RemoveVertex(int32_t iPos)
{
	if (!Inside<int32_t>(iPos,0,VtxNum-1)) return false;
	for (int32_t cnt=iPos; cnt+1<VtxNum; cnt++)
		{ VtxX[cnt]=VtxX[cnt+1]; VtxY[cnt]=VtxY[cnt+1]; }
	VtxNum--;
	return true;
}

bool C4Shape::CheckContact(int32_t cx, int32_t cy)
{
	// Check all vertices at given object position.
	// Return true on any contact.


	for (int32_t cvtx=0; cvtx<VtxNum; cvtx++)
		if (!(VtxCNAT[cvtx] & CNAT_NoCollision))
			if (GBackDensity(cx+VtxX[cvtx],cy+VtxY[cvtx]) >= ContactDensity)
				return true;


	return false;
}

bool C4Shape::ContactCheck(int32_t cx, int32_t cy, uint32_t *border_hack_contacts)
{
	// Check all vertices at given object position.
	// Set ContactCNAT and ContactCount.
	// Set VtxContactCNAT and VtxContactMat.
	// Return true on any contact.


	ContactCNAT=CNAT_None;
	ContactCount=0;

	for (int32_t cvtx=0; cvtx<VtxNum; cvtx++)

		// Ignore vertex if collision has been flagged out
		if (!(VtxCNAT[cvtx] & CNAT_NoCollision))

		{
			VtxContactCNAT[cvtx]=CNAT_None;
			int32_t x = cx+VtxX[cvtx];
			int32_t y = cy+VtxY[cvtx];
			VtxContactMat[cvtx]=GBackMat(x,y);

			if (GBackDensity(x,y) >= ContactDensity)
			{
				ContactCNAT |= VtxCNAT[cvtx];
				VtxContactCNAT[cvtx]|=CNAT_Center;
				ContactCount++;
				// Vertex center contact, now check top,bottom,left,right
				if (GBackDensity(x,y-1) >= ContactDensity)
					VtxContactCNAT[cvtx]|=CNAT_Top;
				if (GBackDensity(x,y+1) >= ContactDensity)
					VtxContactCNAT[cvtx]|=CNAT_Bottom;
				if (GBackDensity(x-1,y) >= ContactDensity)
					VtxContactCNAT[cvtx]|=CNAT_Left;
				if (GBackDensity(x+1,y) >= ContactDensity)
					VtxContactCNAT[cvtx]|=CNAT_Right;
			}
			if (border_hack_contacts)
			{
				if (x == 0 && GBackDensity(x-1, y) >= ContactDensity) *border_hack_contacts |= CNAT_Left;
				else if (x == ::Landscape.Width && GBackDensity(x+1, y) >= ContactDensity) *border_hack_contacts |= CNAT_Right;
			}
		}


	return !!ContactCount;
}

bool C4Shape::CheckScaleToWalk(int x, int y)
{
	for (int32_t i = 0; i < VtxNum; i++)
	{
		if (VtxCNAT[i] & CNAT_NoCollision)
			continue;
		if (VtxCNAT[i] & CNAT_Bottom)
		{
			// no ground under the feet?
			if (GBackDensity(x + VtxX[i], y + VtxY[i] + 1) < ContactDensity)
				return false;
		}
		else
		{
			// can climb with hands?
			if (GBackDensity(x + VtxX[i] - 1, y + VtxY[i]) >= ContactDensity)
				return false;
			if (GBackDensity(x + VtxX[i] + 1, y + VtxY[i]) >= ContactDensity)
				return false;
		}
	}
	return true;
}

int32_t C4Shape::GetVertexX(int32_t iVertex)
{
	if (!Inside<int32_t>(iVertex,0,VtxNum-1)) return 0;
	return VtxX[iVertex];
}

int32_t C4Shape::GetVertexY(int32_t iVertex)
{
	if (!Inside<int32_t>(iVertex,0,VtxNum-1)) return 0;
	return VtxY[iVertex];
}

void C4Shape::CopyFrom(C4Shape rFrom, bool bCpyVertices, bool fCopyVerticesFromSelf)
{
	if (bCpyVertices)
	{
		// truncate / copy vertex count
		VtxNum = (fCopyVerticesFromSelf ? Min<int32_t>(VtxNum, C4D_VertexCpyPos) : rFrom.VtxNum);
		// restore vertices from back of own buffer (retaining count)
		int32_t iCopyPos = (fCopyVerticesFromSelf ? C4D_VertexCpyPos : 0);
		C4Shape &rVtxFrom = (fCopyVerticesFromSelf ? *this : rFrom);
		memcpy(VtxX, rVtxFrom.VtxX+iCopyPos, VtxNum*sizeof(*VtxX));
		memcpy(VtxY, rVtxFrom.VtxY+iCopyPos, VtxNum*sizeof(*VtxY));
		memcpy(VtxCNAT, rVtxFrom.VtxCNAT+iCopyPos, VtxNum*sizeof(*VtxCNAT));
		memcpy(VtxFriction, rVtxFrom.VtxFriction+iCopyPos, VtxNum*sizeof(*VtxFriction));
		memcpy(VtxContactCNAT, rVtxFrom.VtxContactCNAT+iCopyPos, VtxNum*sizeof(*VtxContactCNAT));
		memcpy(VtxContactMat, rVtxFrom.VtxContactMat+iCopyPos, VtxNum*sizeof(*VtxContactMat));
		// continue: copies other members
	}
	*((C4Rect *) this) = rFrom;
	AttachMat=rFrom.AttachMat;
	ContactCNAT=rFrom.ContactCNAT;
	ContactCount=rFrom.ContactCount;
	FireTop=rFrom.FireTop;
}

int32_t C4Shape::GetBottomVertex()
{
	// return bottom-most vertex
	int32_t iMax = -1;
	for (int32_t i = 0; i < VtxNum; i++)
		if (VtxCNAT[i] & CNAT_Bottom)
			if (iMax == -1 || VtxY[i] < VtxY[iMax])
				iMax = i;
	return iMax;
}

int C4Shape::GetBottom()
{
	int b = INT_MIN;
	for (int32_t i = 0; i < VtxNum; i++)
		if (~VtxCNAT[i] & CNAT_NoCollision)
			if (VtxY[i] > b)
				b = VtxY[i];
	if (b == INT_MIN)
		return y + Hgt;
	return b;
}

C4DensityProvider DefaultDensityProvider;

int32_t C4DensityProvider::GetDensity(int32_t x, int32_t y) const
{
	// default density provider checks the landscape
	return GBackDensity(x,y);
}

int32_t C4Shape::GetVertexContact(int32_t iVtx, DWORD dwCheckMask, int32_t tx, int32_t ty, const C4DensityProvider &rDensityProvider)
{
	// default check mask
	if (!dwCheckMask) dwCheckMask = VtxCNAT[iVtx];
	// check vertex positions (vtx num not range-checked!)
	tx += VtxX[iVtx]; ty += VtxY[iVtx];
	int32_t iContact = 0;
	// check all directions for solid mat
	if (~VtxCNAT[iVtx] & CNAT_NoCollision)
	{
		if (dwCheckMask & CNAT_Center) if (rDensityProvider.GetDensity(tx, ty) >= ContactDensity)   iContact |= CNAT_Center;
		if (dwCheckMask & CNAT_Left)   if (rDensityProvider.GetDensity(tx-1, ty) >= ContactDensity) iContact |= CNAT_Left;
		if (dwCheckMask & CNAT_Right)  if (rDensityProvider.GetDensity(tx+1, ty) >= ContactDensity) iContact |= CNAT_Right;
		if (dwCheckMask & CNAT_Top)    if (rDensityProvider.GetDensity(tx, ty-1) >= ContactDensity) iContact |= CNAT_Top;
		if (dwCheckMask & CNAT_Bottom) if (rDensityProvider.GetDensity(tx, ty+1) >= ContactDensity) iContact |= CNAT_Bottom;
	}
	// return resulting bitmask
	return iContact;
}

void C4Shape::CreateOwnOriginalCopy(C4Shape &rFrom)
{
	// copy vertices from original buffer, including count
	VtxNum = Min<int32_t>(rFrom.VtxNum, C4D_VertexCpyPos);
	memcpy(VtxX+C4D_VertexCpyPos, rFrom.VtxX, VtxNum*sizeof(*VtxX));
	memcpy(VtxY+C4D_VertexCpyPos, rFrom.VtxY, VtxNum*sizeof(*VtxY));
	memcpy(VtxCNAT+C4D_VertexCpyPos, rFrom.VtxCNAT, VtxNum*sizeof(*VtxCNAT));
	memcpy(VtxFriction+C4D_VertexCpyPos, rFrom.VtxFriction, VtxNum*sizeof(*VtxFriction));
	memcpy(VtxContactCNAT+C4D_VertexCpyPos, rFrom.VtxContactCNAT, VtxNum*sizeof(*VtxContactCNAT));
	memcpy(VtxContactMat+C4D_VertexCpyPos, rFrom.VtxContactMat, VtxNum*sizeof(*VtxContactMat));
}

void C4Shape::CompileFunc(StdCompiler *pComp, bool fRuntime)
{
	// Note: Compiled directly into "Object" and "DefCore"-categories, so beware of name clashes
	// (see C4Object::CompileFunc and C4Def::CompileFunc)
	pComp->Value(mkNamingAdapt( Wdt,                        "Width",              0                 ));
	pComp->Value(mkNamingAdapt( Hgt,                        "Height",             0                 ));
	pComp->Value(mkNamingAdapt( mkArrayAdapt(&x,2,0),       "Offset"                                ));
	pComp->Value(mkNamingAdapt( VtxNum,                     "Vertices",           0                 ));
	pComp->Value(mkNamingAdapt( toC4CArr(VtxX),             "VertexX"                               ));
	pComp->Value(mkNamingAdapt( toC4CArr(VtxY),             "VertexY"                               ));
	pComp->Value(mkNamingAdapt( toC4CArr(VtxCNAT),          "VertexCNAT"                            ));
	pComp->Value(mkNamingAdapt( toC4CArr(VtxFriction),      "VertexFriction"                        ));
	pComp->Value(mkNamingAdapt( ContactDensity,             "ContactDensity",     C4M_Solid         ));
	pComp->Value(mkNamingAdapt( FireTop,                    "FireTop",            0                 ));
	if (fRuntime)
	{
		pComp->Value(mkNamingAdapt( iAttachX,                   "AttachX",            0                 ));
		pComp->Value(mkNamingAdapt( iAttachY,                   "AttachY",            0                 ));
		pComp->Value(mkNamingAdapt( iAttachVtx,                 "AttachVtx",          0                 ));
	}
}
