/*
** hw_sections.cpp
** For decoupling the renderer from internal Build structures
**
**---------------------------------------------------------------------------
** Copyright 2021 Christoph Oelckers
** 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.
**---------------------------------------------------------------------------
**
** The sole reason for existence of this file is that Build's sector setup
** does not allow for easy splitting of sectors, either for having disjoint parts
** or requiring partial rendering. So we need to add a superstructure
** where we can shuffle around the map content without disturbing the original
** order...
**
*/

#include "build.h"
#include "hw_sections.h"
#include "memarena.h"
#include "c_cvars.h"
#include "gamefuncs.h"

void CreateVertexMap();

FMemArena sectionArena(102400);

TMap<int, bool> bugged;

TArray<SectionLine> sectionLines;
TArray<Section> sections;
TArrayView<TArrayView<int>> sectionsPerSector;
TArray<int> splits;

struct loopcollect
{
	TArray<TArray<int>> loops;
	int bugged = 0;
};

struct sectionbuild
{
	int bugged = 0;
	int wallcount = 0;
	TArray<TArray<int>> loops;
};

struct sectionbuildsector
{
	int sectnum;
	TArray<sectionbuild> sections;
};

static bool cmpLess(double a, double b)
{
	return a < b;
}

static bool cmpGreater(double a, double b)
{
	return a > b;
}

static int sgn(double v)
{
	// Don't try to be smart here - the compiler won't like it.
	return (v > 0)? 1: (v < 0)? -1 : 0;
}

static double dist(const DVector2& a, const DVector2& b)
{
	// We only need to know if it's 1 or higher, so this is enough.
	return fabs(a.X - b.X) + fabs(a.Y - b.Y);
}


using cmp = bool(*)(double, double);

//==========================================================================
//
// This will also be needed by the triangulator because it faces the same problems with eliminating linedef overlaps.
//
//==========================================================================

void StripLoop(TArray<DVector2>& points)
{
	for (int p = 0; p < (int)points.Size(); p++)
	{
		unsigned prev = p == 0 ? points.Size() - 1 : p - 1;
		unsigned next = unsigned(p) == points.Size() - 1 ? 0 : p + 1;

		if (points[next] == points[prev]) // if the two neighboring points are equal, this one dos not contribute to the sector's area.
		{
			if (next == 0)
			{
				points.Delete(0);
				points.Pop();
			}
			else
			{
				points.Delete(p, 2);
				p--;
			}
			if (p > 0) p--; // backtrack one point more to ensure we can check the newly formed connection as well.
		}
		else if ((points[prev].X == points[p].X && points[next].X == points[p].X && sgn(points[next].Y - points[p].Y) == sgn(points[prev].Y - points[p].Y)) ||
			(points[prev].Y == points[p].Y && points[next].Y == points[p].Y && sgn(points[next].X - points[p].X) == sgn(points[prev].X - points[p].X)) ||
			dist(points[prev], points[next]) <= 1/256.) // if the two points are extremely close together, we may also ignore the intermediate point.
		{
			// both connections exit the point into the same direction. Here it is sufficient to just delete it so that the neighboring ones connect directly.
			points.Delete(p);
			p--;
			if (p > 0) p--; // backtrack one point more to ensure we can check the newly formed connection as well.
		}
		// Todo: check the non-orthogonal case of the above, too. Duke E2L7's sector 130 is such a case.
	}
}


//==========================================================================
//
//
//
//==========================================================================

int GetWindingOrder(TArray<DVector2>& poly, cmp comp1 = cmpLess, cmp comp2 = cmpGreater)
{
	int n = poly.Size();
	double minx = poly[0].X;
	double miny = poly[0].Y;
	int m = 0;

	for (int i = 0; i < n; i++) 
	{
		if ((comp1(poly[i].Y, miny)) || ((poly[i].Y == miny) && (comp2(poly[i].X, minx))))
		{
			m = i;
			minx = poly[m].X;
			miny = poly[m].Y;
		}
	}

	double a[2], b[2], c[2];

	int m1 = (m + n - 1) % n;
	int m2 = (m + 1) % n;

	a[0] = poly[m1].X;
	b[0] = poly[m].X;
	c[0] = poly[m2].X;

	a[1] = poly[m1].Y;
	b[1] = poly[m].Y;
	c[1] = poly[m2].Y;

	auto area =
		a[0] * b[1] - a[1] * b[0] +
		a[1] * c[0] - a[0] * c[1] +
		b[0] * c[1] - c[0] * b[1];

	return (area > 0) - (area < 0); 
}

int GetWindingOrder(TArray<int>& poly)
{
	// This is more complicated than it should be due to how doors are designed in Build. 
	// Overlapping and backtracking lines are quite common and need to be removed from the data before determining the winding order.
	TArray<DVector2> points(poly.Size(), true);
	int i = 0;
	for (auto& index : poly)
	{
		points[i++] = wall[index].pos;
	}
	StripLoop(points);
	if (points.Size() == 0) return 1; // Sector has no dimension. We must accept this as valid here.
	int order = GetWindingOrder(points);
	if (order == 0)
	{
		// this may be a diagonal overlap, so try one other corner, too.
		order = GetWindingOrder(points, cmpGreater, cmpLess);
	}
	// if (order == 0) do a pedantic check - this is hopefully not needed ever.
	return order;
}

//==========================================================================
//
//
//
//==========================================================================

static void CollectLoops(TArray<loopcollect>& sectors)
{
	BitArray visited;
	visited.Resize(wall.Size());
	visited.Zero();

	TArray<int> thisloop;

	int count = 0;
	for (unsigned i = 0; i < sector.Size(); i++)
	{
		int first = wallindex(sector[i].walls.Data());
		int last = first + sector[i].walls.Size();
		sectors.Reserve(1);
		sectors.Last().bugged = 0;

		for (int w = first; w < last; w++)
		{
			if (visited[w]) continue;
			thisloop.Clear();
			thisloop.Push(w);
			visited.Set(w);

			for (int ww = wall[w].point2; ww != w; ww = wall[ww].point2)
			{
				if (ww < first || ww >= last)
				{
					Printf("Found wall %d outside sector %d in a loop\n", ww, i);
					sectors.Last().bugged = ESectionFlag::Unclosed;
					bugged.Insert(i, true);
					break;
				}
				if (visited[ww])
				{
					// quick check for the only known cause of this in proper maps: 
					// RRRA E1L3 and SW $yamato have a wall duplicate where the duplicate's index is the original's + 1. These can just be deleted here and be ignored.
					if (ww > 1 && wall[ww-1].pos == wall[ww-2].pos && wall[ww-1].point2 == wall[ww-2].point2 && wall[ww - 1].point2 == ww)
					{
						thisloop.Clear();
						break;
					}
					Printf("found already visited wall %d\nLinked by:", ww);
					bugged.Insert(i, true);
					for (unsigned walnum = 0; walnum < wall.Size(); walnum++)
					{
						if (wall[walnum].point2 == ww)
							Printf(" %d,", walnum);
					}
					Printf("\n");
					sectors.Last().bugged = ESectionFlag::Unclosed;
					break;
				}
				thisloop.Push(ww);
				visited.Set(ww);
			}
			if (thisloop.Size() > 0)
			{
				count++;
				int o = GetWindingOrder(thisloop);
				if (o == 0)
				{
					//Printf("Unable to determine winding order of loop in sector %d!\n", i);
					bugged.Insert(i, true);
				}

				thisloop.Push(o);
				sectors.Last().loops.Push(std::move(thisloop));
			}
		}
	}
}

//==========================================================================
//
// checks if a point is within a given section
//
//==========================================================================

static TArray<DVector2> points;
static int insideLoop(int vertex, TArray<int>& loop)
{
	points.Resize(loop.Size() - 1);
	for (unsigned ii = 0; ii < loop.Size() - 1; ii++)
	{
		points[ii] = wall[loop[ii]].pos;
	}
	
	// to reliably detect walls where vertices lie directly on outer walls, we must test the wall's center as well.
	// SW: Wanton Destrcution's $bath.map, sector 601 is an example for that.
	DVector2 pts[] = { wall[vertex].pos, wall[vertex].center() };
	for (int i = 0; i < 2; i++)
	{
		int isinside = insidePoly(pts[i].X, pts[i].Y, points.Data(), points.Size());
		if (isinside == 1) return 1;
	}
	return -1;
}

static int insideLoop(TArray<int>& check, TArray<int>& loop)
{
	for (unsigned v = 0; v < check.Size() - 1; v++)
	{
		if (insideLoop(check[v], loop) == 1)
		{
			return true;
		}
	}
	return false;
}

//==========================================================================
//
//
//
//==========================================================================

static void GroupData(TArray<loopcollect>& collect, TArray<sectionbuildsector>& builders)
{
	for (unsigned i = 0; i < sector.Size(); i++)
	{
		auto& builder = builders[i];
		builder.sectnum = i;
		auto& sectloops = collect[i].loops;

		// Handle the two easy cases  explicitly so that they can be done without running into more complex checks
		if (sectloops.Size() == 1)
		{
			// we got one loop - do this quickly without any checks.
			auto& loop = sectloops[0];
			builder.sections.Reserve(1);
			int last = loop.Last();
			builder.sections.Last().wallcount = loop.Size() - 1;
			builder.sections.Last().loops.Push(std::move(loop));
			builder.sections.Last().bugged = collect[i].bugged;
			if (last != 1)
			{
				builder.sections.Last().bugged = ESectionFlag::BadWinding; // Todo: Use flags for bugginess.
				//Printf("Sector %d has wrong winding order\n", i);
				bugged.Insert(i, true);
			}
			continue;
		}
		if (!collect[i].bugged)	// only try to build a proper set of sections if the sector is not malformed. Otherwise just make a single one of everything.
		{
			unsigned wind1count = 0;
			unsigned windnegcount = 0;
			int posplace = -1;
			for (unsigned l = 0; l < sectloops.Size(); l++)
			{
				auto& loop = sectloops[l];
				if (loop.Last() == 1)
				{
					wind1count++;
					posplace = l;
				}
				else if (loop.Last() == -1) windnegcount++;
			}
			// Check for one outer loop with multiple inner loops. This is also fairly common and quickly found.
			if (wind1count == 1 && windnegcount == sectloops.Size() - 1)
			{
				if (posplace > 0) sectloops[0].Swap(sectloops[posplace]);
				unsigned insidecount = 0;
				for (unsigned l = 1; l < sectloops.Size(); l++)
				{
					if (insideLoop(sectloops[l], sectloops[0])) insidecount++;
				}
				if (insidecount == sectloops.Size() - 1)
				{
					builder.sections.Reserve(1);
					builder.sections.Last().wallcount = 0;
					builder.sections.Last().bugged = 0;
					for (auto& loop : sectloops)
					{
						builder.sections.Last().wallcount += loop.Size() - 1;
						builder.sections.Last().loops.Push(std::move(loop));
					}
					continue;
				}
			}
			// Check for multiple outer loops with no inner loops. Less frequent, but still a regular occurence.
			if (wind1count == sectloops.Size() && windnegcount == 0)
			{
				for (auto& loop1 : sectloops) for (auto& loop2 : sectloops)
				{
					if (&loop1 != &loop2 && insideLoop(loop1, loop2))
					{
						goto nope; // just get out of here.
					}
				}
				for (auto& loop : sectloops)
				{
					builder.sections.Reserve(1);
					builder.sections.Last().bugged = 0;
					builder.sections.Last().wallcount = loop.Size() - 1;
					builder.sections.Last().loops.Push(std::move(loop));
				}
				continue;
			}
		nope:;

			// Now try the case where we got multiple sections where some have holes.
			// For that, first build a map to see which sectors lie inside others.
			TArray<int> inside(sectloops.Size(), true);
			TArray<TArray<int>> outside(sectloops.Size(), true);
			for (auto& in : inside) in = -1;
			for (unsigned a = 0; a < sectloops.Size(); a++)
			{
				for (unsigned b = 0; b < sectloops.Size(); b++)
				{
					if (b != a && insideLoop(sectloops[a], sectloops[b]))
					{
						if (inside[a] == -1)
						{
							if (sectloops[a].Last() != -1 || sectloops[b].Last() != 1)
							{
								//Printf("Bad winding order for loops in sector %d\n", i);
								bugged.Insert(i, true);
								inside[a] = inside[b] = -2; // invalidate both loops
							}
							else
							{
								inside[a] = b;
								outside[b].Push(a);
							}
						}
						else
						{
							//Printf("Nested loops found in sector %d, comparing loops starting at %d and %d\n", i, sectloops[a][0], sectloops[b][0]);
							bugged.Insert(i, true);
							if (inside[a] != -2)
							{
								inside[inside[a]] = -2;
							}
							inside[a] = inside[b] = -2;
						}
					}
				}
			}
			// Now write out the proper sections we were able to find.
			for (unsigned a = 0; a < sectloops.Size(); a++)
			{
				if (inside[a] == -1 && sectloops[a].Size() > 0 && sectloops[a].Last() == 1)
				{
					auto& loop = sectloops[a];
					builder.sections.Reserve(1);
					builder.sections.Last().bugged = -1; // debug only - remove once checked!!!
					builder.sections.Last().wallcount = loop.Size() - 1;
					builder.sections.Last().loops.Push(std::move(loop));
					for (auto c: outside[a])
					{
						if (inside[c] == (int)a)
						{
							auto& iloop = sectloops[c];
							builder.sections.Last().wallcount += iloop.Size() - 1;
							builder.sections.Last().loops.Push(std::move(iloop));
							inside[c] = -1;
						}
					}
				}
			}
		}

		// Whatever gets here is in a shape where any guesswork is futile. Just dump it into a single section and don't think about it any further.
		bool tossit = false;
		for (unsigned a = 0; a < sectloops.Size(); a++)
		{
			if (sectloops[a].Size() > 0)
			{
				if (!tossit) // Have we created our dumping section yet? If no, do so now and print a warning.
				{
					tossit = true;
					//Printf("Potential problem at sector %d with %d loops\n", i, sectloops.Size());
					bugged.Insert(i, true);
					builder.sections.Reserve(1);
					builder.sections.Last().bugged = ESectionFlag::Dumped;	// this will most likely require use of the node builder to triangulate anyway.
				}
				auto& loop = sectloops[a];
				builder.sections.Last().wallcount += loop.Size() - 1;
				builder.sections.Last().loops.Push(std::move(loop));
			}
		}
	}
}

//==========================================================================
//
// handle explicit sector splits while we still have simple index arrays.
// This operates on the generated sections
//
//==========================================================================

static void TrySplitLoop(sectionbuildsector& builder, int firstwall, int lastwall)
{
	for (unsigned s = 0; s < builder.sections.Size(); s++)
	{
		auto& section = builder.sections[s];
		if (section.loops.Size() > 1)
		{
			// Must have one loop to split a section. Should this ever be needed for sections with holes the loops need to be joined before running this.
			Printf("Unable to split sector %d between walls %d and %d\n", builder.sectnum, firstwall, lastwall);
			return;
		}
		auto& loop = section.loops[0];
		unsigned i1 = loop.Find(firstwall);
		unsigned i2 = loop.Find(lastwall);
		if (i1 >= loop.Size() || i2 >= loop.Size()) continue;
		if (i2 < i1) std::swap(i1, i2);
		TArray<int> newloop1;
		TArray<int> newloop2;
		auto it = loop.begin();
		auto end = loop.end() - 1;
		while (it != end && *it != firstwall) newloop1.Push(*it++);
		newloop1.Push(-firstwall);
		while (it != end && *it != lastwall) newloop2.Push(*it++);
		newloop2.Push(-lastwall);
		while (it != end) newloop1.Push(*it++);
		section.wallcount = newloop1.Size();
		newloop1.Push(loop.Last());
		section.loops[0] = std::move(newloop1);
		builder.sections.Reserve(1);
		auto& newsect = builder.sections.Last();
		newsect.bugged = false;
		newsect.wallcount = newloop2.Size();
		newloop2.Push(loop.Last());
		newsect.loops.Resize(1);
		newsect.loops[0] = std::move(newloop2);
		break;
	}
}

static void SplitLoops(TArray<sectionbuildsector>& builders)
{
	for (unsigned i = 0; i < splits.Size(); i += 3)
	{
		int sector = splits[0];
		TrySplitLoop(builders[sector], splits[i + 1], splits[i + 2]);
	}
}

//==========================================================================
//
//
//
//==========================================================================

static void ConstructSections(TArray<sectionbuildsector>& builders)
{
	// count all sections and allocate the global buffers.
	TArray<int> splitwalls;

	// Allocate all Section walls.
	sectionLines.Resize(wall.Size() + splits.Size() * 2 / 3);

	for (unsigned i = 0; i < splits.Size(); i++)
	{
		if (i % 3) splitwalls.Push(splits[i]);
	}

	unsigned nextwall = wall.Size();
	for (unsigned i = 0; i < wall.Size(); i++)
	{
		sectionLines[i].startpoint = i;
		sectionLines[i].endpoint = wall[i].point2;
		sectionLines[i].wall = i;
		sectionLines[i].partner = wall[i].nextwall;
	}
	for (unsigned i = wall.Size(); i < sectionLines.Size(); i++)
	{
		unsigned pair = (i - wall.Size());
		sectionLines[i].startpoint = splitwalls[pair];
		sectionLines[i].endpoint = splitwalls[pair ^ 1];
		sectionLines[i].wall = -1;
		sectionLines[i].partner = wall.Size() + (pair ^ 1);
	}

	unsigned count = 0;
	// allocate as much as possible from the arena here.
	size_t size = sizeof(*sectionsPerSector.Data()) * sector.Size();
	auto data = sectionArena.Calloc(size);
	sectionsPerSector.Set(static_cast<decltype(sectionsPerSector.Data())>(data), sector.Size());

	for (unsigned i = 0; i < sector.Size(); i++)
	{
		auto& builder = builders[i];
		count += builder.sections.Size();

		size = sizeof(int) * builder.sections.Size();
		data = sectionArena.Calloc(size);
		sectionsPerSector[i].Set(static_cast<int* >(data), builder.sections.Size()); // although this may need reallocation, it is too small to warrant single allocations for each sector.
	}
	sections.Resize(count); // this we cannot put into the arena because its size may change.
	memset(sections.Data(), 0, count * sizeof(*sections.Data()));

	// now fill in the data

	int cursection = 0;
	for (unsigned i = 0; i < sector.Size(); i++)
	{
		auto& builder = builders[i];
		for (unsigned j = 0; j < builder.sections.Size(); j++)
		{
			auto section = &sections[cursection];
			auto& srcsect = builder.sections[j];
			sectionsPerSector[i][j] = cursection;
			section->sector = i;
			section->index = cursection++;

			int sectwalls = srcsect.wallcount;
			auto walls = (int*)sectionArena.Calloc(sectwalls * sizeof(int));
			section->lines.Set(walls, sectwalls);

			unsigned srcloops = srcsect.loops.Size();
			auto loops = (Section2Loop*)sectionArena.Calloc(srcloops * sizeof(Section2Loop));
			section->loops.Set(loops, srcloops);

			int curwall = 0;
			for (unsigned ii = 0; ii < srcloops; ii++)
			{
				auto& srcloop = srcsect.loops[ii];
				auto& loop = section->loops[ii];
				unsigned numsectionwalls = srcloop.Size() - 1;
				auto wallarray = (int*)sectionArena.Calloc(numsectionwalls * sizeof(int));
				loop.walls.Set(wallarray, numsectionwalls);
				for (unsigned w = 0; w < numsectionwalls; w++)
				{
					int wall_i = srcloop[w];
					if (wall_i >= 0)
					{
						auto wal = &sectionLines[wall_i];
						section->lines[curwall++] = loop.walls[w] = wall_i;
						wal->section = section->index;
					}
					else
					{
						wall_i = (int)wall.Size() + splitwalls.Find(-wall_i);
						auto wal = &sectionLines[wall_i];
						section->lines[curwall++] = loop.walls[w] = wall_i;
						wal->section = section->index;
					}
				}
			}
		}
	}
	// Can only do this after completing everything else.
	for (auto& line : sectionLines)
	{
		line.partnersection = line.partner == -1 ? -1 : sectionLines[line.partner].section;
	}
	sectionLines.ShrinkToFit();
	sections.ShrinkToFit();
}

//==========================================================================
//
//
//
//==========================================================================

void hw_CreateSections()
{
	bugged.Clear();
	sectionArena.FreeAll();
	sections.Clear();
	sectionLines.Clear();
	TArray<loopcollect> collect;
	CollectLoops(collect);

	TArray<sectionbuildsector> builders(sector.Size(), true);
	GroupData(collect, builders);
	SplitLoops(builders);

	ConstructSections(builders);
	CreateVertexMap();
}

//==========================================================================
//
// Create a set of vertex loops from a given session
//
//==========================================================================

Outline BuildOutline(Section* section)
{
	Outline output(section->loops.Size(), true);
	for (unsigned i = 0; i < section->loops.Size(); i++)
	{
		output[i].Resize(section->loops[i].walls.Size());
		for (unsigned j = 0; j < section->loops[i].walls.Size(); j++)
		{
			output[i][j] = sectionLines[section->loops[i].walls[j]].v1();
		}
		StripLoop(output[i]);
	}
	return output;
}


void hw_SetSplitSector(int sectnum, int start, int end)
{
	splits.Push(sectnum);
	splits.Push(start);
	splits.Push(end);
}

void hw_ClearSplitSector()
{
	splits.Clear();
}
