/*
** nodebuild_extract.cpp
**
** Converts the nodes, segs, and subsectors from the node builder's
** internal format to the format used by the rest of the game.
**
**---------------------------------------------------------------------------
** Copyright 2002-2006 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.
** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be
**    covered by the terms of the GNU General Public License as published by
**    the Free Software Foundation; either version 2 of the License, or (at
**    your option) any later version.
**
** 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 <string.h>
#include <float.h>

#include "nodebuild.h"

#if 0
#define D(x) x
#define DD 1
#else
#define D(x) do{}while(0)
#undef DD
#endif


void FNodeBuilder::Extract (FLevelLocals &theLevel)
{
	int i;

	auto &outVerts = theLevel.vertexes; 
	int vertCount = Vertices.Size ();
	outVerts.Alloc(vertCount);

	for (i = 0; i < vertCount; ++i)
	{
		outVerts[i].set(Vertices[i].x, Vertices[i].y);
	}

	auto &outSubs = theLevel.subsectors;
	auto subCount = Subsectors.Size();
	outSubs.Alloc(subCount);
	memset(&outSubs[0], 0, subCount * sizeof(subsector_t));

	auto &outNodes = theLevel.nodes;
	auto nodeCount = Nodes.Size ();
	outNodes.Alloc(nodeCount);

	memcpy (&outNodes[0], &Nodes[0], nodeCount*sizeof(node_t));
	for (unsigned i = 0; i < nodeCount; ++i)
	{
		D(Printf(PRINT_LOG, "Node %d:  Splitter[%08x,%08x] [%08x,%08x]\n", i,
			outNodes[i].x, outNodes[i].y, outNodes[i].dx, outNodes[i].dy));
		// Go backwards because on 64-bit systems, both of the intchildren are
		// inside the first in-game child.
		for (int j = 1; j >= 0; --j)
		{
			if (outNodes[i].intchildren[j] & 0x80000000)
			{
				D(Printf(PRINT_LOG, "  subsector %d\n", outNodes[i].intchildren[j] & 0x7FFFFFFF));
				outNodes[i].children[j] = (uint8_t *)(&outSubs[(outNodes[i].intchildren[j] & 0x7fffffff)]) + 1;
			}
			else
			{
				D(Printf(PRINT_LOG, "  node %d\n", outNodes[i].intchildren[j]));
				outNodes[i].children[j] = &outNodes[outNodes[i].intchildren[j]];
			}
		}
		for (int j = 0; j < 2; ++j)
		{
			for (int k = 0; k < 4; ++k)
			{
				outNodes[i].bbox[j][k] = FIXED2FLOAT(outNodes[i].nb_bbox[j][k]);
			}
		}
	}

	auto &outSegs = theLevel.segs;
	if (GLNodes)
	{
		TArray<glseg_t> segs (Segs.Size()*5/4);

		for (unsigned i = 0; i < subCount; ++i)
		{
			uint32_t numsegs = CloseSubsector (segs, i, &outVerts[0]);
			outSubs[i].numlines = numsegs;
			outSubs[i].firstline = (seg_t *)(size_t)(segs.Size() - numsegs);
		}

		auto segCount = segs.Size ();
		outSegs.Alloc(segCount);

		for (unsigned i = 0; i < segCount; ++i)
		{
			outSegs[i] = *(seg_t *)&segs[i];

			if (segs[i].Partner != UINT_MAX)
			{
				const uint32_t storedseg = Segs[segs[i].Partner].storedseg;
				outSegs[i].PartnerSeg = UINT_MAX == storedseg ? nullptr : &outSegs[storedseg];
			}
			else
			{
				outSegs[i].PartnerSeg = nullptr;
			}
		}
	}
	else
	{
		memcpy (&outSubs[0], &Subsectors[0], subCount*sizeof(subsector_t));
		auto segCount = Segs.Size ();
		outSegs.Alloc(segCount);
		for (unsigned i = 0; i < segCount; ++i)
		{
			const FPrivSeg *org = &Segs[SegList[i].SegNum];
			seg_t *out = &outSegs[i];

			D(Printf(PRINT_LOG, "Seg %d: v1(%d) -> v2(%d)\n", i, org->v1, org->v2));

			out->v1 = &outVerts[org->v1];
			out->v2 = &outVerts[org->v2];
			out->backsector = org->backsector;
			out->frontsector = org->frontsector;
			out->linedef = Level.Lines + org->linedef;
			out->sidedef = Level.Sides + org->sidedef;
			out->PartnerSeg = nullptr;
		}
	}
	for (unsigned i = 0; i < subCount; ++i)
	{
		outSubs[i].firstline = &outSegs[(size_t)outSubs[i].firstline];
	}

	D(Printf("%i segs, %i nodes, %i subsectors\n", segCount, nodeCount, subCount));

	for (i = 0; i < Level.NumLines; ++i)
	{
		Level.Lines[i].v1 = &outVerts[(size_t)Level.Lines[i].v1];
		Level.Lines[i].v2 = &outVerts[(size_t)Level.Lines[i].v2];
	}
}

int FNodeBuilder::CloseSubsector (TArray<glseg_t> &segs, int subsector, vertex_t *outVerts)
{
	FPrivSeg *seg, *prev;
	angle_t prevAngle;
	double accumx, accumy;
	fixed_t midx, midy;
	int firstVert;
	uint32_t first, max, count, i, j;
	bool diffplanes;
	int firstplane;

	first = (uint32_t)(size_t)Subsectors[subsector].firstline;
	max = first + Subsectors[subsector].numlines;
	count = 0;

	accumx = accumy = 0.0;
	diffplanes = false;
	firstplane = Segs[SegList[first].SegNum].planenum;

	// Calculate the midpoint of the subsector and also check for degenerate subsectors.
	// A subsector is degenerate if it exists in only one dimension, which can be
	// detected when all the segs lie in the same plane. This can happen if you have
	// outward-facing lines in the void that don't point toward any sector. (Some of the
	// polyobjects in Hexen are constructed like this.)
	for (i = first; i < max; ++i)
	{
		seg = &Segs[SegList[i].SegNum];
		accumx += double(Vertices[seg->v1].x) + double(Vertices[seg->v2].x);
		accumy += double(Vertices[seg->v1].y) + double(Vertices[seg->v2].y);
		if (firstplane != seg->planenum)
		{
			diffplanes = true;
		}
	}

	midx = fixed_t(accumx / (max - first) / 2);
	midy = fixed_t(accumy / (max - first) / 2);

	seg = &Segs[SegList[first].SegNum];
	prevAngle = PointToAngle (Vertices[seg->v1].x - midx, Vertices[seg->v1].y - midy);
	seg->storedseg = PushGLSeg (segs, seg, outVerts);
	count = 1;
	prev = seg;
	firstVert = seg->v1;

#ifdef DD
	Printf(PRINT_LOG, "--%d--\n", subsector);
	for (j = first; j < max; ++j)
	{
		seg = &Segs[SegList[j].SegNum];
		angle_t ang = PointToAngle (Vertices[seg->v1].x - midx, Vertices[seg->v1].y - midy);
		Printf(PRINT_LOG, "%d%c %5d(%5d,%5d)->%5d(%5d,%5d) - %3.5f  %d,%d  [%08x,%08x]-[%08x,%08x]\n", j,
			seg->linedef == -1 ? '+' : ':',
			seg->v1, Vertices[seg->v1].x>>16, Vertices[seg->v1].y>>16,
			seg->v2, Vertices[seg->v2].x>>16, Vertices[seg->v2].y>>16,
			double(ang/2)*180/(1<<30),
			seg->planenum, seg->planefront,
			Vertices[seg->v1].x, Vertices[seg->v1].y,
			Vertices[seg->v2].x, Vertices[seg->v2].y);
	}
#endif

	if (diffplanes)
	{ // A well-behaved subsector. Output the segs sorted by the angle formed by connecting
	  // the subsector's center to their first vertex.

		D(Printf(PRINT_LOG, "Well behaved subsector\n"));
		for (i = first + 1; i < max; ++i)
		{
			angle_t bestdiff = ANGLE_MAX;
			FPrivSeg *bestseg = NULL;
			uint32_t bestj = UINT_MAX;
			j = first;
			do
			{
				seg = &Segs[SegList[j].SegNum];
				angle_t ang = PointToAngle (Vertices[seg->v1].x - midx, Vertices[seg->v1].y - midy);
				angle_t diff = prevAngle - ang;
				if (seg->v1 == prev->v2)
				{
					bestdiff = diff;
					bestseg = seg;
					bestj = j;
					break;
				}
				if (diff < bestdiff && diff > 0)
				{
					bestdiff = diff;
					bestseg = seg;
					bestj = j;
				}
			}
			while (++j < max);
			// Is a NULL bestseg actually okay?
			if (bestseg != NULL)
			{
				seg = bestseg;
			}
			if (prev->v2 != seg->v1)
			{
				// Add a new miniseg to connect the two segs
				PushConnectingGLSeg (subsector, segs, &outVerts[prev->v2], &outVerts[seg->v1]);
				count++;
			}
#ifdef DD
			Printf(PRINT_LOG, "+%d\n", bestj);
#endif
			prevAngle -= bestdiff;
			seg->storedseg = PushGLSeg (segs, seg, outVerts);
			count++;
			prev = seg;
			if (seg->v2 == firstVert)
			{
				prev = seg;
				break;
			}
		}
#ifdef DD
		Printf(PRINT_LOG, "\n");
#endif
	}
	else
	{ // A degenerate subsector. These are handled in three stages:
	  // Stage 1. Proceed in the same direction as the start seg until we
	  //          hit the seg furthest from it.
	  // Stage 2. Reverse direction and proceed until we hit the seg
	  //          furthest from the start seg.
	  // Stage 3. Reverse direction again and insert segs until we get
	  //          to the start seg.
	  // A dot product serves to determine distance from the start seg.

		D(Printf(PRINT_LOG, "degenerate subsector\n"));

		// Stage 1. Go forward.
		count += OutputDegenerateSubsector (segs, subsector, true, 0, prev, outVerts);

		// Stage 2. Go backward.
		count += OutputDegenerateSubsector (segs, subsector, false, DBL_MAX, prev, outVerts);

		// Stage 3. Go forward again.
		count += OutputDegenerateSubsector (segs, subsector, true, -DBL_MAX, prev, outVerts);
	}

	if (prev->v2 != firstVert)
	{
		PushConnectingGLSeg (subsector, segs, &outVerts[prev->v2], &outVerts[firstVert]);
		count++;
	}
#ifdef DD
	Printf(PRINT_LOG, "Output GL subsector %d:\n", subsector);
	for (i = segs.Size() - count; i < (int)segs.Size(); ++i)
	{
		Printf(PRINT_LOG, "  Seg %5d%c(%5d,%5d)-(%5d,%5d)  [%08x,%08x]-[%08x,%08x]\n", i,
			segs[i].linedef == NULL ? '+' : ' ',
			segs[i].v1->fixX()>>16,
			segs[i].v1->fixY()>>16,
			segs[i].v2->fixX()>>16,
			segs[i].v2->fixY()>>16,
			segs[i].v1->fixX(),
			segs[i].v1->fixY(),
			segs[i].v2->fixX(),
			segs[i].v2->fixY());
	}
#endif

	return count;
}

int FNodeBuilder::OutputDegenerateSubsector (TArray<glseg_t> &segs, int subsector, bool bForward, double lastdot, FPrivSeg *&prev, vertex_t *outVerts)
{
	static const double bestinit[2] = { -DBL_MAX, DBL_MAX };
	FPrivSeg *seg;
	int i, j, first, max, count;
	double dot, x1, y1, dx, dy, dx2, dy2;
	bool wantside;

	first = (uint32_t)(size_t)Subsectors[subsector].firstline;
	max = first + Subsectors[subsector].numlines;
	count = 0;

	seg = &Segs[SegList[first].SegNum];
	x1 = Vertices[seg->v1].x;
	y1 = Vertices[seg->v1].y;
	dx = Vertices[seg->v2].x - x1;
	dy = Vertices[seg->v2].y - y1;
	wantside = seg->planefront ^ !bForward;

	for (i = first + 1; i < max; ++i)
	{
		double bestdot = bestinit[bForward];
		FPrivSeg *bestseg = NULL;
		for (j = first + 1; j < max; ++j)
		{
			seg = &Segs[SegList[j].SegNum];
			if (seg->planefront != wantside)
			{
				continue;
			}
			dx2 = Vertices[seg->v1].x - x1;
			dy2 = Vertices[seg->v1].y - y1;
			dot = dx*dx2 + dy*dy2;

			if (bForward)
			{
				if (dot < bestdot && dot > lastdot)
				{
					bestdot = dot;
					bestseg = seg;
				}
			}
			else
			{
				if (dot > bestdot && dot < lastdot)
				{
					bestdot = dot;
					bestseg = seg;
				}
			}
		}
		if (bestseg != NULL)
		{
			if (prev->v2 != bestseg->v1)
			{
				PushConnectingGLSeg (subsector, segs, &outVerts[prev->v2], &outVerts[bestseg->v1]);
				count++;
			}
			seg->storedseg = PushGLSeg (segs, bestseg, outVerts);
			count++;
			prev = bestseg;
			lastdot = bestdot;
		}
	}
	return count;
}

uint32_t FNodeBuilder::PushGLSeg (TArray<glseg_t> &segs, const FPrivSeg *seg, vertex_t *outVerts)
{
	glseg_t newseg;

	newseg.v1 = outVerts + seg->v1;
	newseg.v2 = outVerts + seg->v2;
	newseg.backsector = seg->backsector;
	newseg.frontsector = seg->frontsector;
	if (seg->linedef != -1)
	{
		newseg.linedef = Level.Lines + seg->linedef;
		newseg.sidedef = Level.Sides + seg->sidedef;
	}
	else
	{
		newseg.linedef = NULL;
		newseg.sidedef = NULL;
	}
	newseg.Partner = seg->partner;
	return (uint32_t)segs.Push (newseg);
}

void FNodeBuilder::PushConnectingGLSeg (int subsector, TArray<glseg_t> &segs, vertex_t *v1, vertex_t *v2)
{
	glseg_t newseg;

	newseg.v1 = v1;
	newseg.v2 = v2;
	newseg.backsector = NULL;
	newseg.frontsector = NULL;
	newseg.linedef = NULL;
	newseg.sidedef = NULL;
	newseg.Partner = UINT_MAX;
	segs.Push (newseg);
}