/*
** p_slopes.cpp
** Slope creation
**
**---------------------------------------------------------------------------
** Copyright 1998-2008 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/

#include "level.h"

#include <map>

#define EQUAL_EPSILON (1/65536.)

// convert from fixed point(FRACUNIT) to floating point
#define F(x)  (((float)(x))/65536.0f)

inline int P_PointOnLineSidePrecise(const vec2& p, const vec2& v1, const vec2& v2)
{
	return (p.y - v1.y) * (v2.x - v1.x) + (v1.x - p.x) * (v2.y - v1.y) > EQUAL_EPSILON;
}

inline int P_PointOnLineSidePrecise(const vec2& p, const FloatVertex& v1, const FloatVertex& v2)
{
	return P_PointOnLineSidePrecise(p, vec2(v1.x, v1.y), vec2(v2.x, v2.y));
}

enum
{
	// Thing numbers used to define slopes
	SMT_VavoomFloor = 1500,
	SMT_VavoomCeiling = 1501,
	SMT_VertexFloorZ = 1504,
	SMT_VertexCeilingZ = 1505,
	SMT_SlopeFloorPointLine = 9500,
	SMT_SlopeCeilingPointLine = 9501,
	SMT_SetFloorSlope = 9502,
	SMT_SetCeilingSlope = 9503,
	SMT_CopyFloorPlane = 9510,
	SMT_CopyCeilingPlane = 9511,
};

void FProcessor::SetSlopesFromVertexHeights(IntThing* firstmt, IntThing* lastmt, const int* oldvertextable)
{
	TMap<int, double> vt_heights[2];
	IntThing* mt;
	bool vt_found = false;

	for (mt = firstmt; mt < lastmt; ++mt)
	{
		if (mt->type == SMT_VertexFloorZ || mt->type == SMT_VertexCeilingZ)
		{
			for (int i = 0; i < Level.NumVertices; i++)
			{
				auto vertex = Level.GetSegVertex(i);

				if(F(mt->x) == vertex.x && F(mt->y) == vertex.y)
				{
					if (mt->type == SMT_VertexFloorZ)
					{
						vt_heights[0][i] = F(mt->z);
					}
					else
					{
						vt_heights[1][i] = F(mt->z);
					}
					vt_found = true;
				}
			}
			mt->type = 0;
		}
	}

	for (unsigned i = 0; i < Level.VertexProps.Size(); i++)
	{
		auto& vertex = Level.VertexProps[i];

		int ii = oldvertextable == NULL ? i : oldvertextable[i];

		if (vertex.HasZCeiling())
		{
			vt_heights[1][ii] = vertex.zceiling;
			vt_found = true;
		}

		if (vertex.HasZFloor())
		{
			vt_heights[0][ii] = vertex.zfloor;
			vt_found = true;
		}
	}

	if (vt_found)
	{
		for (auto& sec : Level.Sectors)
		{
			if (sec.lines.Size() != 3) continue;	// only works with triangular sectors

			dvec3 vt1, vt2, vt3;
			dvec3 vec1, vec2;
			int vi1, vi2, vi3;

			vi1 = sec.lines[0]->v1;
			vi2 = sec.lines[0]->v2;
			vi3 = (sec.lines[1]->v1 == sec.lines[0]->v1 || sec.lines[1]->v1 == sec.lines[0]->v2) ?
				sec.lines[1]->v2 : sec.lines[1]->v1;

			const auto sv1 = Level.GetSegVertex(vi1);
			const auto sv2 = Level.GetSegVertex(vi2);
			const auto sv3 = Level.GetSegVertex(vi3);

			vt1 = dvec3(sv1.x, sv1.y, 0);
			vt2 = dvec3(sv2.x, sv2.y, 0);
			vt3 = dvec3(sv3.x, sv3.y, 0);

			for (int j = 0; j < 2; j++)
			{
				double* h1 = vt_heights[j].CheckKey(vi1);
				double* h2 = vt_heights[j].CheckKey(vi2);
				double* h3 = vt_heights[j].CheckKey(vi3);

				if (h1 == NULL && h2 == NULL && h3 == NULL) continue;

				vt1.z = h1 ? *h1 : j == 0 ? sec.data.floorheight : sec.data.ceilingheight;
				vt2.z = h2 ? *h2 : j == 0 ? sec.data.floorheight : sec.data.ceilingheight;
				vt3.z = h3 ? *h3 : j == 0 ? sec.data.floorheight : sec.data.ceilingheight;

				if (P_PointOnLineSidePrecise(::vec2(vt3.xy()), Level.GetSegVertex(sec.lines[0]->v1), Level.GetSegVertex(sec.lines[0]->v2)) == 0)
				{
					vec1 = vt2 - vt3;
					vec2 = vt1 - vt3;
				}
				else
				{
					vec1 = vt1 - vt3;
					vec2 = vt2 - vt3;
				}

				dvec3 cross = ::cross(vec1, vec2);

				double len = length(cross);
				if (len == 0)
				{
					// Only happens when all vertices in this sector are on the same line.
					// Let's just ignore this case.
					continue;
				}
				cross /= len;

				// Fix backward normals
				if ((cross.z < 0 && j == 0) || (cross.z > 0 && j == 1))
				{
					cross = -cross;
				}

				Plane* plane = j == 0 ? &sec.floorplane : &sec.ceilingplane;

				double dist = -cross[0] * vt3.x - cross[1] * vt3.y - cross[2] * vt3.z;
				plane->Set(float(cross[0]), float(cross[1]), float(cross[2]), float(-dist));
			}
		}
	}
}

void FProcessor::SpawnSlopeMakers(IntThing* firstmt, IntThing* lastmt, const int* oldvertextable)
{
	IntThing* mt;

	for (mt = firstmt; mt < lastmt; ++mt)
	{
		if (mt->type >= SMT_SlopeFloorPointLine && mt->type <= SMT_VavoomCeiling)
		{
			dvec3 pos = dvec3(F(mt->x), F(mt->y), F(mt->z));
			Plane* refplane;
			IntSector* sec;
			bool ceiling;

			sec = Level.PointInSector(pos.xy());
			if (mt->type == SMT_SlopeCeilingPointLine || mt->type == SMT_VavoomCeiling || mt->type == SMT_SetCeilingSlope)
			{
				refplane = &sec->ceilingplane;
				ceiling = true;
			}
			else
			{
				refplane = &sec->floorplane;
				ceiling = false;
			}

			pos.z = double(refplane->zAt(float(pos.x), float(pos.y))) + pos.z;

			/*if (mt->type <= SMT_SlopeCeilingPointLine)
			{ // SlopeFloorPointLine and SlopCeilingPointLine
				SlopeLineToPoint(mt->args[0], pos, ceiling);
			}
			else if (mt->type <= SMT_SetCeilingSlope)
			{ // SetFloorSlope and SetCeilingSlope
				SetSlope(refplane, ceiling, mt->angle, mt->args[0], pos);
			}
			else
			{ // VavoomFloor and VavoomCeiling (these do not perform any sector height adjustment - z is absolute)
				VavoomSlope(sec, mt->thingid, mt->pos, ceiling);
			}*/
			mt->type = 0;
		}
	}
	SetSlopesFromVertexHeights(firstmt, lastmt, oldvertextable);

	for (mt = firstmt; mt < lastmt; ++mt)
	{
		if (mt->type == SMT_CopyFloorPlane || mt->type == SMT_CopyCeilingPlane)
		{
			CopyPlane(mt->args[0], dvec2(F(mt->x), F(mt->y)), mt->type == SMT_CopyCeilingPlane);
			mt->type = 0;
		}
	}
	
}

void FProcessor::AlignPlane(IntSector* sec, IntLineDef* line, int which)
{
	IntSector* refsec;
	double bestdist;
	FloatVertex refvert = Level.GetSegVertex(sec->lines[0]->v1);

	if (line->backsector == NULL)
		return;

	auto lv1 = Level.GetSegVertex(line->v1);
	auto lv2 = Level.GetSegVertex(line->v2);

	// Find furthest vertex from the reference line. It, along with the two ends
	// of the line, will define the plane.
	bestdist = 0;
	for (auto ln : sec->lines)
	{
		for (int i = 0; i < 2; i++)
		{
			double dist;
			FloatVertex vert;

			vert = Level.GetSegVertex(i == 0 ? ln->v1 : ln->v2);

			dist = fabs((lv1.y - vert.y) * (lv2.x - lv1.x) -
				(lv1.x - vert.x) * (lv2.y - lv1.y));

			if (dist > bestdist)
			{
				bestdist = dist;
				refvert = vert;
			}
		}
	}

	refsec = line->frontsector == sec ? line->backsector : line->frontsector;

	dvec3 p, v1, v2, cross;

	Plane* srcplane;
	double srcheight, destheight;

	srcplane = (which == 0) ? &sec->floorplane : &sec->ceilingplane;
	srcheight = (which == 0) ? sec->data.floorheight : sec->data.ceilingheight;
	destheight = (which == 0) ? refsec->data.floorheight : refsec->data.ceilingheight;

	p[0] = lv1.x;
	p[1] = lv1.y;
	p[2] = destheight;
	v1[0] = double(lv2.x) - double(lv1.x);
	v1[1] = double(lv2.y) - double(lv1.y);
	v1[2] = 0;
	v2[0] = double(refvert.x) - double(lv1.x);
	v2[1] = double(refvert.y) - double(lv1.y);
	v2[2] = srcheight - destheight;

	cross = normalize(::cross(v1, v2));

	// Fix backward normals
	if ((cross.z < 0 && which == 0) || (cross.z > 0 && which == 1))
	{
		cross = -cross;
	}

	double dist = -cross[0] * lv1.x - cross[1] * lv1.y - cross[2] * destheight;
	srcplane->Set(float(cross[0]), float(cross[1]), float(cross[2]), float(-dist));
}

void FProcessor::SetSlopes()
{
	int s;

	for (auto& line : Level.Lines)
	{
		if (line.special == Plane_Align)
		{
			line.special = 0;
			if (line.backsector != nullptr)
			{
				// args[0] is for floor, args[1] is for ceiling
				//
				// As a special case, if args[1] is 0,
				// then args[0], bits 2-3 are for ceiling.
				for (s = 0; s < 2; s++)
				{
					int bits = line.args[s] & 3;

					if (s == 1 && bits == 0)
						bits = (line.args[0] >> 2) & 3;

					if (bits == 1)			// align front side to back
						AlignPlane(line.frontsector, &line, s);
					else if (bits == 2)		// align back side to front
						AlignPlane(line.backsector, &line, s);
				}
			}
		}
	}
}

void FProcessor::CopySlopes()
{
	for (auto& line : Level.Lines)
	{
		if (line.special == Plane_Copy)
		{
			// The args are used for the tags of sectors to copy:
			// args[0]: front floor
			// args[1]: front ceiling
			// args[2]: back floor
			// args[3]: back ceiling
			// args[4]: copy slopes from one side of the line to the other.
			line.special = 0;
			for (int s = 0; s < (line.backsector ? 4 : 2); s++)
			{
				if (line.args[s])
					CopyPlane(line.args[s], (s & 2 ? line.backsector : line.frontsector), s & 1);
			}

			if (line.backsector != NULL)
			{
				if ((line.args[4] & 3) == 1)
				{
					line.backsector->floorplane = line.frontsector->floorplane;
				}
				else if ((line.args[4] & 3) == 2)
				{
					line.frontsector->floorplane = line.backsector->floorplane;
				}
				if ((line.args[4] & 12) == 4)
				{
					line.backsector->ceilingplane = line.frontsector->ceilingplane;
				}
				else if ((line.args[4] & 12) == 8)
				{
					line.frontsector->ceilingplane = line.backsector->ceilingplane;
				}
			}
		}
	}
}

void FProcessor::CopyPlane(int tag, IntSector* dest, bool copyCeil)
{
	IntSector* source;
	int secnum;

	secnum = Level.FindFirstSectorFromTag(tag);
	if (secnum == -1)
	{
		return;
	}

	source = &Level.Sectors[secnum];

	if (copyCeil)
	{
		dest->ceilingplane = source->ceilingplane;
	}
	else
	{
		dest->floorplane = source->floorplane;
	}
}

void FProcessor::CopyPlane(int tag, const dvec2& pos, bool copyCeil)
{
	CopyPlane(tag, Level.PointInSector(pos), copyCeil);
}