/*
*
** gl_clipper.cpp
**
** Handles visibility checks.
** Loosely based on the JDoom clipper.
**
**---------------------------------------------------------------------------
** Copyright 2003 Tim Stump
** 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 "hw_clipper.h"
#include "basics.h"
#include "build.h"
#include "printf.h"


//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------

void Clipper::ValidateList()
{
#ifdef _DEBUG
	// Make sure we catch ordering issues right away in debug mode.
	auto check = cliphead;
	while (check)
	{
		assert(!check->next || check->end <= check->next->start);
		check = check->next;
	}
#endif
}

//-----------------------------------------------------------------------------
//
// RemoveRange
//
//-----------------------------------------------------------------------------

void Clipper::RemoveRange(ClipNode * range, bool free)
{
	if (range == cliphead)
	{
		cliphead = cliphead->next;
		if (cliphead) cliphead->prev = nullptr;
	}
	else
	{
		if (range->prev) range->prev->next = range->next;
		if (range->next) range->next->prev = range->prev;
	}

	if (free) Free(range);
	ValidateList();
}

//-----------------------------------------------------------------------------
//
// InsertRange
//
//-----------------------------------------------------------------------------

bool Clipper::InsertRange(ClipNode* prev, ClipNode* node)
{
	if (node->start == node->end)
	{
		Free(node);
		return false;
	}
	assert(node->start <= node->end);
	assert(!prev || prev->end <= node->start);
	assert(!prev || !prev->next || prev->next->start >= node->end);

	if (node->topclip <= node->bottomclip)
	{
		if (prev)
		{
			if (prev->topclip <= prev->bottomclip && prev->end >= node->start)
			{
				prev->end = node->end;
				Free(node);
				if (prev->next && prev->end >= prev->next->start && prev->next->topclip <= prev->next->bottomclip)
				{
					prev->end = prev->next->end;
					RemoveRange(prev->next);
					return true;
				}
				ValidateList();
				return false;
			}
			else if (prev->next && node->end >= prev->next->start && prev->next->topclip <= prev->next->bottomclip)
			{
				prev->next->start = node->start;
				Free(node);
				ValidateList();
				return false;
			}
		}
	}

	if (prev)
	{
		node->next = prev->next;
		prev->next = node;
	}
	else
	{
		node->next = cliphead;
		cliphead = node;
	}
	node->prev = prev;
	if (node->next) node->next->prev = node;

	ValidateList();
	return false;
}

//-----------------------------------------------------------------------------
//
// SplitRange
//
//-----------------------------------------------------------------------------

void Clipper::SplitRange(ClipNode* node, int start, int end, float topclip, float bottomclip)
{
	assert(start < end);
	int clones = 0;
	if (end < node->end)
	{
		int nodeend = node->end;
		node->end = end;
		auto endnode = NewRange(end, nodeend, node->topclip, node->bottomclip);
		InsertRange(node, endnode);
	}
	if (start > node->start)
	{
		node->end = start;
		auto startnode = NewRange(start, end, topclip, bottomclip);
		InsertRange(node, startnode);
	}
	else
	{
		// remove and reinsert to do proper checks of the clipping window.
		auto prev = node->prev;
		RemoveRange(node, false);
		node->topclip = topclip;
		node->bottomclip = bottomclip;
		InsertRange(prev, node);
	}
}

//-----------------------------------------------------------------------------
//
// Clear
//
//-----------------------------------------------------------------------------

void Clipper::Clear(angle_t rangestart)
{
	ClipNode *node = cliphead;
	ClipNode *temp;

	while (node != nullptr)
	{
		temp = node;
		node = node->next;
		Free(temp);
	}

	cliphead = nullptr;

	if (visibleStart != 0 || visibleEnd != 0)
	{
		int vstart = int(visibleStart - rangestart);
		if (vstart > 1) AddClipRange(0, vstart);

		int vend = int(visibleEnd - rangestart);
		if (vend > 0 && vend < INT_MAX) AddClipRange(vend, INT_MAX);
	}


}

//-----------------------------------------------------------------------------
//
// IsRangeVisible
//
//-----------------------------------------------------------------------------

bool Clipper::IsRangeVisible(int startAngle, int endAngle)
{
	ClipNode *ci;
	ci = cliphead;

	if (endAngle == 0 && ci && ci->start==0) return false;

	while (ci != nullptr && ci->start < endAngle)
	{
		if (startAngle >= ci->start && endAngle <= ci->end && ci->topclip <= ci->bottomclip)
		{
			return false;
		}
		ci = ci->next;
	}

	return true;
}

//-----------------------------------------------------------------------------
//
// AddClipRange
//
//-----------------------------------------------------------------------------

void Clipper::AddClipRange(int start, int end)
{
	if (cliphead)
	{
		auto node = cliphead;
		while (node != nullptr && node->start < end)
		{
			// check to see if range contains any old ranges.
			// These can be removed, regardless whether they are a window or fully closed.
			if (node->start >= start && node->end <= end)
			{
				auto temp = node;
				node = node->next;
				RemoveRange(temp);
			}
			// check if the new range lies fully within an existing range.
			else if (node->start <= start && node->end >= end)
			{
				// if the existing range is closed, we're done.
				// Other split up the old window to insert the new range in the middle.
				if (node->topclip > node->bottomclip)
				{
					SplitRange(node, start, end, 0, 0);
					return;
				}
			}
			else
			{
				node = node->next;
			}
		}

		// at this point we know that overlaps can only be at one side because all full overlaps have been resolved already.
		// so what follows can at most intersect two other nodes - one at the left and one at the right
		node = cliphead;
		while (node != nullptr && node->start <= end)
		{
			// node overlaps at the start.
			if (node->start < start && node->end >= start)
			{
				if (node->topclip <= node->bottomclip)
				{
					node->end = end;
					auto next = node->next;
					if (next && next->start <= end)
					{
						// check if the following range overlaps. We know already that it will go past the end of the newly added range
						// (otherwise the first loop above would have taken care of it) so we can skip any checks for the full inclusion case.
						if (next->topclip <= next->bottomclip)
						{
							// next range is closed, so merge the two.
							node->end = next->end;
							RemoveRange(next);
						}
						else
						{
							next->start = end;
						}
					}
					// we're done and do not need to add a new node.
					ValidateList();
					return;
				}
				else
				{
					// if the old node is a window, restrict its size to the left of the new range and continue checking
					node->end = start;
					ValidateList();
				}
			}
			// range overlaps at the end.
			else if (node->start >= start && node->start <= end)
			{
				// node is closed - we can just merge this and exit
				if (node->topclip <= node->bottomclip)
				{
					node->start = start;
					ValidateList();
					return;
				}
				// node is a window - so restrict its size and insert the new node in front of it,
				else
				{
					node->start = end;

					auto mynode = NewRange(start, end, 0, 0);
					InsertRange(node->prev, mynode);
					return;
				}
			}
			node = node->next;
		}

		//found no intersections - just add range
		node = cliphead;
		ClipNode* prevNode = nullptr;
		// advance to the place where this can be inserted.		
		while (node != nullptr && node->start < end)
		{
			prevNode = node;
			node = node->next;
		}
		auto mynode = NewRange(start, end, 0, 0);
		InsertRange(prevNode, mynode);
	}
	else
	{
		cliphead = NewRange(start, end, 0, 0);
	}
}


//-----------------------------------------------------------------------------
//
// AddWindowRange
//
//-----------------------------------------------------------------------------

void Clipper::AddWindowRange(int start, int end, float topclip, float bottomclip, float viewz)
{
	auto mergeClip = [](ClipNode* node, float& topclip, float& bottomclip, float viewz)
	{
		// If the node is already closed, return a closed range.
		if (node->topclip <= node->bottomclip)
		{
			topclip = bottomclip = 0;
			return;
		}
		float mintopclip = min(node->topclip, topclip);
		float maxbotclip = max(node->bottomclip, bottomclip);

		if (mintopclip > max(viewz, maxbotclip)) topclip = FLT_MAX;
		else topclip = mintopclip;

		if (maxbotclip < min(viewz, mintopclip)) bottomclip = -FLT_MAX;
		else bottomclip = maxbotclip;
	};

	ClipNode* prevNode = nullptr;

	if (cliphead)
	{
		auto node = cliphead;
		while (node != nullptr && node->start < end)
		{
			// check to see if range contains any old ranges.

			//-----------------------------------------------------------------------------
			//
			// existing range is part of new one.
			//
			//-----------------------------------------------------------------------------

			if (node->start >= start && node->end <= end)
			{
				int nodestart = node->start, nodeend = node->end;
				if (node->topclip > node->bottomclip) // we only need to make adjustments to the old node if it is not closed.
				{
					float mtopclip = topclip, mbottomclip = bottomclip;
					mergeClip(node, mtopclip, mbottomclip, viewz);
					// if the new window is closed, we must remove the old range and insert a closed one, so that it gets merged with its neighbours.
					if (mtopclip <= mbottomclip)
					{
						auto prev = node->prev;
						RemoveRange(node);
						auto mynode = NewRange(nodestart, nodeend, 0, 0);
						InsertRange(prev, mynode);
					}
					else
					{
						// in all other cases we must adjust the node's top and bottom
						node->topclip = mtopclip;
						node->bottomclip = mbottomclip;
					}
				}
				// At this point it is just easier to recursively add the sub-ranges because we'd have to run the full program on both anyway,
				// We must ensure the the new ranges' length are > 0.
				if (start < nodestart) AddWindowRange(start, nodestart, topclip, bottomclip, viewz);
				if (end > nodeend) AddWindowRange(nodeend, end, topclip, bottomclip, viewz);
				return;
			}

			//-----------------------------------------------------------------------------
			//
			// check if the new range lies fully within an existing range.
			//
			//-----------------------------------------------------------------------------

			else if (node->start <= start && node->end >= end)
			{
				// Shortcut if existing range is closed. In this case there's nothing to do.
				if (node->topclip <= node->bottomclip)
				{
					return;
				}

				float mtopclip = topclip, mbottomclip = bottomclip;
				mergeClip(node, mtopclip, mbottomclip, viewz);

				// existing range is a more narrow window on both sides, we're done.
				if (mtopclip > mbottomclip && mtopclip >= node->topclip && mbottomclip <= node->bottomclip)
				{
					return;
				}
				else
				{
					// adapt the window for the intersection.
					SplitRange(node, start, end, mtopclip, mbottomclip);
					return;
				}
			}
			else
			{
				node = node->next;
			}
		}

		// at this point we know that overlaps can only be at one side because all full overlaps have been resolved already.
		// so what follows can at most intersect two other nodes - one at the left and one at the right
		node = cliphead;
		while (node != nullptr && node->start <= end)
		{
			auto next = node->next;	// get this before making any edits.

			//-----------------------------------------------------------------------------
			//
			// node overlaps the start of the new range
			//
			//-----------------------------------------------------------------------------

			if (node->start < start && node->end > start)
			{
				// if the old range is closed, just shorten the new one and continue.
				if (node->topclip < node->bottomclip)
				{
					start = node->end;
					ValidateList();
					if (start >= end) return; // may have been crushed to a single point.
				}
				else
				{
					float mtopclip = topclip, mbottomclip = bottomclip;
					mergeClip(node, mtopclip, mbottomclip, viewz);

					// if the old range is more narrow than the new one, just shorten the new one and continue.
					if (mtopclip > mbottomclip && mtopclip >= node->topclip && mbottomclip <= node->bottomclip)
					{
						start = node->end;
						ValidateList();
						if (start >= end) return; // may have been crushed to a single point.
					}

					// the unaltered new range is more narrow than the old one - just shorten the old one and go on.
					else if (topclip <= bottomclip || (topclip < node->topclip && bottomclip > node->bottomclip))
					{
						node->end = start;
						ValidateList();
						assert(node->end > node->start); // ensured by initial condition.
					}

					// if the intersection needs to take properties of both old and new we need to make a split.
					else
					{
						int nodeend = node->end;
						SplitRange(node, start, nodeend, mtopclip, mbottomclip);
						start = nodeend;
						if (start >= end) return; // may have been crushed to a single point.
						if (mtopclip <= mbottomclip) next = cliphead; // list may have become out of sync.
					}
				}
			}

			//-----------------------------------------------------------------------------
			//
			// nosw overlaps at the end of the new range.
			//
			//-----------------------------------------------------------------------------

			else if (node->start >= start && node->start < end)
			{
				// if the old range is closed, just shorten the new one and continue.
				if (node->topclip < node->bottomclip)
				{
					end = node->start;
					ValidateList();
					if (start >= end) return; // may have been crushed to a single point.
				}
				else
				{
					float mtopclip = topclip, mbottomclip = bottomclip;
					mergeClip(node, mtopclip, mbottomclip, viewz);

					// if the old range is more narrow than the new one, just shorten the new one and continue.
					if (mtopclip > mbottomclip && mtopclip >= node->topclip && mbottomclip <= node->bottomclip)
					{
						end = node->start;
						ValidateList();
						if (start >= end) return; // may have been crushed to a single point.
					}

					// the unaltered new range is more narrow than the old one - just shorten the old one and go on.
					else if (topclip <= bottomclip || (topclip < node->topclip && bottomclip > node->bottomclip))
					{
						node->start = end;
						ValidateList();
						assert(node->end > node->start);
					}

					// if the intersection needs to take properties of both old and new we need to make a split.
					else
					{
						int nodestart = node->start;
						SplitRange(node, nodestart, end, mtopclip, mbottomclip);
						end = nodestart;
						if (start >= end) return; // may have been crushed to a single point.
						if (mtopclip <= mbottomclip) next = cliphead; // list may have become out of sync.
					}
				}
			}
			node = next;
		}

		// cliphead *can* be null here if a sole existing older range got removed because this one covers it entirely.
		if (cliphead)
		{
			// we get here if a new range needs to be inserted.
			node = cliphead;
			// advance to the place where this can be inserted.		
			while (node != nullptr && node->start < end)
			{
				prevNode = node;
				node = node->next;
			}
			assert(!prevNode || prevNode->end <= start);
			assert(!prevNode || !prevNode->next || prevNode->next->start >= end);
			assert(prevNode || (cliphead && (!cliphead->next || cliphead->next->start >= end)));
		}
	}

	if (topclip > viewz) topclip = FLT_MAX;
	if (bottomclip < viewz) bottomclip = -FLT_MAX;
	// only insert a new node if it restricts the window.
	if (topclip != FLT_MAX || bottomclip != -FLT_MAX)
	{
		auto mynode = NewRange(start, end, topclip, bottomclip);
		InsertRange(prevNode, mynode);
	}
}


//-----------------------------------------------------------------------------
//
// RemoveClipRange 
//
//-----------------------------------------------------------------------------

void Clipper::RemoveClipRange(int start, int end)
{
	ClipNode *node, *temp;

	if (cliphead)
	{
		//check to see if range contains any old ranges
		node = cliphead;
		while (node != nullptr && node->start < end)
		{
			if (node->start >= start && node->end <= end)
			{
				temp = node;
				node = node->next;
				RemoveRange(temp);
			}
			else
			{
				node = node->next;
			}
		}

		//check to see if range overlaps a range (or possibly 2)
		node = cliphead;
		while (node != nullptr)
		{
			if (node->start >= start && node->start <= end)
			{
				node->start = end;
				break;
			}
			else if (node->end >= start && node->end <= end)
			{
				node->end=start;
			}
			else if (node->start < start && node->end > end)
			{
				temp = NewRange(end, node->end, node->topclip, node->bottomclip);
				node->end = start;
				InsertRange(node, temp);
				break;
			}
			node = node->next;
		}
	}
	ValidateList();
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------

void Clipper::DumpClipper()
{
	for (auto node = cliphead; node; node = node->next)
	{
		Printf("Range from %2.3f to %2.3f (top = %2.3f, bottom = %2.3f)\n", DAngle::fromBam(node->start).Degrees(), DAngle::fromBam(node->end).Degrees(), node->topclip, node->bottomclip);
	}
}
