/*
** gl_sections.cpp
** Splits sectors into continuous separate parts
**
**---------------------------------------------------------------------------
** Copyright 2008 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.
** 4. When not used as part of GZDoom or a GZDoom derivative, this code will be
**    covered by the terms of the GNU Lesser General Public License as published
**    by the Free Software Foundation; either version 2.1 of the License, or (at
**    your option) any later version.
** 5. Full disclosure of the entire project's source code, except for third
**    party libraries is mandatory. (NOTE: This clause is non-negotiable!)
**
** 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 "gl/system/gl_system.h"
#include <GL/GLU.h>
#include "i_system.h"
#include "p_local.h"
#include "c_dispatch.h"
#include "gl/data/gl_sections.h"

typedef void (CALLBACK *tessFunc)();

TArray<FGLSectionLine> SectionLines;
TArray<FGLSectionLoop> SectionLoops;
TArray<FGLSection> Sections;
TArray<int> SectionForSubsector;

CVAR (Bool, dumpsections, false, 0)

#define ISDONE(no, p) (p[(no)>>3] & (1 << ((no)&7)))
#define SETDONE(no, p) p[(no)>>3] |= (1 << ((no)&7))

inline vertex_t *V1(side_t *s)
{
	line_t *ln = s->linedef;
	return s == ln->sidedef[0]? ln->v1: ln->v2;
}

inline vertex_t *V2(side_t *s)
{
	line_t *ln = s->linedef;
	return s == ln->sidedef[0]? ln->v2: ln->v1;
}

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

class FSectionCreator
{
	static FSectionCreator *creator;

	uint8_t *processed_segs;
	uint8_t *processed_subsectors;
	int *section_for_segs;

	vertex_t *v1_l1, *v2_l1;

	FGLSectionLoop *loop;
	FGLSection *section;	// current working section

public:
	//==========================================================================
	//
	//
	//
	//==========================================================================

	FSectionCreator()
	{
		processed_segs = new uint8_t[(numsegs+7)/8];
		processed_subsectors = new uint8_t[(numsubsectors+7)/8];

		memset(processed_segs, 0, (numsegs+7)/8); 
		memset(processed_subsectors, 0, (numsubsectors+7)/8); 

		section_for_segs = new int[numsegs];
		memset(section_for_segs, -1, numsegs * sizeof(int));
	}

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

	~FSectionCreator()
	{
		delete [] processed_segs;
		delete [] processed_subsectors;
		delete [] section_for_segs;
	}

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

	void NewLoop()
	{
		section->numloops++;
		loop = &SectionLoops[SectionLoops.Reserve(1)];
		loop->startline = SectionLines.Size();
		loop->numlines = 0 ;
	}

	void NewSection(sector_t *sec)
	{
		section = &Sections[Sections.Reserve(1)];
		section->sector = sec;
		section->subsectors.Clear();
		section->numloops = 0;
		section->startloop = SectionLoops.Size();
		section->validcount = -1;
		NewLoop();
	}

	void FinalizeSection()
	{
	}

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

	bool AddSeg(seg_t *seg)
	{
		FGLSectionLine &line = SectionLines[SectionLines.Reserve(1)];


		bool firstline = loop->numlines == 0;

		if (ISDONE(seg-segs, processed_segs))
		{
			// should never happen!
			DPrintf("Tried to add seg %d to Sections twice. Cannot create Sections.\n", seg-segs);
			return false;
		}

		SETDONE(seg-segs, processed_segs);
		section_for_segs[seg-segs] = Sections.Size()-1;

		line.start = seg->v1;
		line.end = seg->v2;
		line.sidedef = seg->sidedef;
		line.linedef = seg->linedef;
		line.refseg = seg;
		line.polysub = NULL;
		line.otherside = -1;

		if (loop->numlines == 0)
		{
			v1_l1 = seg->v1;
			v2_l1 = seg->v2;
		}
		loop->numlines++;
		return true;
	}

	//==========================================================================
	//
	// Utility stuff
	//
	//==========================================================================

	sector_t *FrontRenderSector(seg_t *seg)
	{
		return seg->Subsector->render_sector;
	}

	sector_t *BackRenderSector(seg_t *seg)
	{
		if (seg->PartnerSeg == NULL) return NULL;
		return seg->PartnerSeg->Subsector->render_sector;
	}

	bool IntraSectorSeg(seg_t *seg)
	{
		return FrontRenderSector(seg) == BackRenderSector(seg);
	}


	//==========================================================================
	//
	// returns the seg whose partner seg determines where this
	// section continues
	//
	//==========================================================================
	bool AddSubSector(subsector_t *subsec, vertex_t *startpt, seg_t **pNextSeg)
	{
		unsigned i = 0;
		if (startpt != NULL)
		{
			// find the seg in this subsector that starts at the given vertex
			for(i = 0; i < subsec->numlines; i++)
			{
				if (subsec->firstline[i].v1 == startpt) break;
			}
			if (i == subsec->numlines) 
			{
				DPrintf("Vertex not found in subsector %d. Cannot create Sections.\n", subsec-subsectors);
				return false;	// Nodes are bad
			}
		}
		else 
		{
			// Find the first unprocessed non-miniseg
			for(i = 0; i < subsec->numlines; i++)
			{
				seg_t *seg = subsec->firstline + i;

				if (seg->sidedef == NULL) continue;
				if (IntraSectorSeg(seg)) continue;
				if (ISDONE(seg-segs, processed_segs)) continue;
				break;
			}
			if (i == subsec->numlines)
			{
				DPrintf("Unable to find a start seg. Cannot create Sections.\n");
				return false;	// Nodes are bad
			}
	
			startpt = subsec->firstline[i].v1;
		}

		seg_t *thisseg = subsec->firstline + i;
		if (IntraSectorSeg(thisseg))
		{
			SETDONE(thisseg-segs, processed_segs);
			// continue with the loop in the adjoining subsector
			*pNextSeg = thisseg;
			return true;
		}

		while(1)
		{
			if (loop->numlines > 0 && thisseg->v1 == v1_l1 && thisseg->v2 == v2_l1)
			{
				// This loop is complete
				*pNextSeg = NULL;
				return true;
			}

			if (!AddSeg(thisseg)) return NULL;

			i = (i+1) % subsec->numlines;
			seg_t *nextseg = subsec->firstline + i;

			if (thisseg->v2 != nextseg->v1)
			{
				DPrintf("Segs in subsector %d are not continuous. Cannot create Sections.\n", subsec-subsectors);
				return false;	// Nodes are bad
			}

			if (IntraSectorSeg(nextseg))
			{
				SETDONE(nextseg-segs, processed_segs);
				// continue with the loop in the adjoining subsector
				*pNextSeg = nextseg;
				return true;
			}
			thisseg = nextseg;
		}
	}

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

	bool FindNextSeg(seg_t **pSeg)
	{
		// find an unprocessed non-miniseg or a miniseg with an unprocessed 
		// partner subsector that belongs to the same rendersector
		for (unsigned i = 0; i < section->subsectors.Size(); i++)
		{
			for(unsigned j = 0; j < section->subsectors[i]->numlines; j++)
			{
				seg_t *seg = section->subsectors[i]->firstline + j;
				bool intra = IntraSectorSeg(seg);

				if (!intra && !ISDONE(seg-segs, processed_segs))
				{
					*pSeg = seg;
					return true;
				}
				else if (intra && 
					!ISDONE(seg->PartnerSeg->Subsector-subsectors, processed_subsectors))
				{
					*pSeg = seg->PartnerSeg;
					return true;
				}
			}
		}
		*pSeg = NULL;
		return true;
	}

	//=============================================================================
	//
	// all segs and subsectors must be grouped into Sections
	//
	//=============================================================================
	bool CheckSections()
	{
		bool res = true;
		for (int i = 0; i < numsegs; i++)
		{
			if (segs[i].sidedef != NULL && !ISDONE(i, processed_segs) && !IntraSectorSeg(&segs[i]))
			{
				Printf("Seg %d (Linedef %d) not processed during section creation\n", i, segs[i].linedef-lines);
				res = false;
			}
		}
		for (int i = 0; i < numsubsectors; i++)
		{
			if (!ISDONE(i, processed_subsectors))
			{
				Printf("Subsector %d (Sector %d) not processed during section creation\n", i, subsectors[i].sector-sectors);
				res = false;
			}
		}
		return res;
	}

	//=============================================================================
	//
	//
	//
	//=============================================================================
	void DeleteLine(int i)
	{
		SectionLines.Delete(i);
		for(int i = SectionLoops.Size() - 1; i >= 0; i--)
		{
			FGLSectionLoop *loop = &SectionLoops[i];
			if (loop->startline > i) loop->startline--;
		}
	}

	//=============================================================================
	//
	//
	//
	//=============================================================================
	void MergeLines(FGLSectionLoop *loop)
	{
		int i;
		int deleted = 0;
		FGLSectionLine *ln1;
		FGLSectionLine *ln2;
		// Merge identical lines in the list
		for(i = loop->numlines - 1; i > 0; i--)
		{
			ln1 = loop->GetLine(i);
			ln2 = loop->GetLine(i-1);

			if (ln1->sidedef == ln2->sidedef && ln1->otherside == ln2->otherside)
			{
				// identical references. These 2 lines can be merged.
				ln2->end = ln1->end;
				SectionLines.Delete(loop->startline + i);
				loop->numlines--;
				deleted++;
			}
		}

		// If we started in the middle of a sidedef the first and last lines
		// may reference the same sidedef. check that, too.

		int loopstart = 0;

		ln1 = loop->GetLine(0);
		for(i = loop->numlines - 1; i > 0; i--)
		{
			ln2 = loop->GetLine(i);
			if (ln1->sidedef != ln2->sidedef || ln1->otherside != ln2->otherside)
				break;
		}
		if (i < loop->numlines-1)
		{
			i++;
			ln2 = loop->GetLine(i);
			ln1->start = ln2->start;
			SectionLines.Delete(loop->startline + i, loop->numlines - i);
			deleted += loop->numlines - i;
			loop->numlines = i;
		}

		// Adjust all following loops
		for(unsigned ii = unsigned(loop - &SectionLoops[0]) + 1; ii < SectionLoops.Size(); ii++)
		{
			SectionLoops[ii].startline -= deleted;
		}
	}

	//=============================================================================
	//
	//
	//
	//=============================================================================
	void SetReferences()
	{
		for(unsigned i = 0; i < SectionLines.Size(); i++)
		{
			FGLSectionLine *ln = &SectionLines[i];
			seg_t *seg = ln->refseg;

			if (seg != NULL)
			{
				seg_t *partner = seg->PartnerSeg;

				if (seg->PartnerSeg == NULL)
				{
					ln->otherside = -1;
				}
				else
				{
					ln->otherside = section_for_segs[partner-segs];
				}
			}
			else
			{
				ln->otherside = -1;
			}
		}

		for(unsigned i = 0; i < SectionLoops.Size(); i++)
		{
			MergeLines(&SectionLoops[i]);
		}
	}

	//=============================================================================
	//
	// cbTessBegin
	//
	// called when the tesselation of a new loop starts
	//
	//=============================================================================

	static void CALLBACK cbTessBegin(GLenum type, void *section)
	{
		FGLSection *sect = (FGLSection*)section;
		sect->primitives.Push(type);
		sect->primitives.Push(sect->vertices.Size());
	}

	//=============================================================================
	//
	// cbTessError
	//
	// called when the tesselation failed
	//
	//=============================================================================

	static void CALLBACK cbTessError(GLenum error, void *section)
	{
	}

	//=============================================================================
	//
	// cbTessCombine
	//
	// called when the two or more vertexes are on the same coordinate
	//
	//=============================================================================

	static void CALLBACK cbTessCombine( GLdouble coords[3], void *vert[4], GLfloat w[4], void **dataOut )
	{
		*dataOut = vert[0];
	}

	//=============================================================================
	//
	// cbTessVertex
	//
	// called when a vertex is found
	//
	//=============================================================================

	static void CALLBACK cbTessVertex( void *vert, void *section )
	{
		FGLSection *sect = (FGLSection*)section;
		sect->vertices.Push(int(intptr_t(vert)));
	}

	//=============================================================================
	//
	// cbTessEnd
	//
	// called when the tesselation of a the current loop ends
	//
	//=============================================================================

	static void CALLBACK cbTessEnd(void *section)
	{
	}

	//=============================================================================
	//
	//
	//
	//=============================================================================
	void tesselateSections()
	{
		// init tesselator
		GLUtesselator *tess = gluNewTess();
		if (!tess)
		{
			return;
		}
		// set callbacks
		gluTessCallback(tess, GLU_TESS_BEGIN_DATA, (tessFunc)cbTessBegin);
		gluTessCallback(tess, GLU_TESS_VERTEX_DATA, (tessFunc)cbTessVertex);
		gluTessCallback(tess, GLU_TESS_ERROR_DATA, (tessFunc)cbTessError);
		gluTessCallback(tess, GLU_TESS_COMBINE, (tessFunc)cbTessCombine);
		gluTessCallback(tess, GLU_TESS_END_DATA, (tessFunc)cbTessEnd);

		for(unsigned int i=0;i<Sections.Size(); i++)
		{
			FGLSection *sect = &Sections[i];

			gluTessBeginPolygon(tess, sect);

			for(int j=0; j< sect->numloops; j++)
			{
				gluTessBeginContour(tess);
				FGLSectionLoop *loop = sect->GetLoop(j);
				for(int k=0; k<loop->numlines; k++)
				{
					FGLSectionLine *line = loop->GetLine(k);
					vertex_t *vert = line->start;
					GLdouble v[3] = {
						-(double)vert->x/(double)FRACUNIT,	// negate to get proper winding
						0.0,
						(double)vert->y/(double)FRACUNIT 
					};
				    gluTessVertex(tess, v, (void*)(vert - vertexes));
				}
				gluTessEndContour(tess);
			}
			gluTessEndPolygon(tess);
			sect->vertices.Push(-1337);
			sect->vertices.ShrinkToFit();
		}
		gluDeleteTess(tess);
	}


	//=============================================================================
	//
	// First mark all subsectors that have no outside boundaries as processed
	// No line in such a subsector will ever be part of a section's border
	//
	//=============================================================================

	void MarkInternalSubsectors()
	{
		for(int i=0; i < numsubsectors; i++)
		{
			subsector_t *sub = &subsectors[i];
			int j;

			for(j=0; j < sub->numlines; j++)
			{
				seg_t *seg = sub->firstline + j;
				if (!IntraSectorSeg(seg)) break;
			}
			if (j==sub->numlines)
			{
				// All lines are intra-sector so mark this as processed
				SETDONE(i, processed_subsectors);
				for(j=0; j < sub->numlines; j++)
				{
					seg_t *seg = sub->firstline + j;
					SETDONE((sub->firstline-segs)+j, processed_segs);
					if (seg->PartnerSeg != NULL)
					{
						SETDONE(int(seg->PartnerSeg - segs), processed_segs);
					}
				}
			}
		}
	}

	//=============================================================================
	//
	//
	//
	//=============================================================================
	bool CreateSections()
	{
		int pick = 0;

		MarkInternalSubsectors();
		while (pick < numsubsectors)
		{
			if (ISDONE(pick, processed_subsectors)) 
			{
				pick++;
				continue;
			}


			subsector_t *subsector = &subsectors[pick];

			seg_t *workseg = NULL;
			vertex_t *startpt = NULL;

			NewSection(subsector->render_sector);
			while (1)
			{
				if (!ISDONE(subsector-subsectors, processed_subsectors))
				{
					SETDONE(subsector-subsectors, processed_subsectors);
					section->subsectors.Push(subsector);
					SectionForSubsector[subsector - subsectors] = int(section - &Sections[0]);
				}

				bool result = AddSubSector(subsector, startpt, &workseg);

				if (!result)
				{
					return false;	// couldn't create Sections
				}
				else if (workseg != NULL)
				{
					// crossing into another subsector
					seg_t *partner = workseg->PartnerSeg;
					if (workseg->v2 != partner->v1)
					{
						DPrintf("Inconsistent subsector references in seg %d. Cannot create Sections.\n", workseg-segs);
						return false;
					}
					subsector = partner->Subsector;
					startpt = workseg->v1;
				}
				else
				{
					// loop complete. Check adjoining subsectors for other loops to
					// be added to this section
					if (!FindNextSeg(&workseg))
					{
						return false;
					}
					else if (workseg == NULL)
					{
						// No more subsectors found. This section is complete!
						FinalizeSection();
						break;
					}
					else
					{
						subsector = workseg->Subsector;
						// If this is a regular seg, start there, otherwise start
						// at the subsector's first seg
						startpt = workseg->sidedef == NULL? NULL : workseg->v1;

						NewLoop();
					}
				}
			}
		}

		if (!CheckSections()) return false;
		SetReferences();

		Sections.ShrinkToFit();
		SectionLoops.ShrinkToFit();
		SectionLines.ShrinkToFit();

		tesselateSections();

		return true;
	}
};

FSectionCreator *FSectionCreator::creator;


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

void DumpSection(int no, FGLSection *sect)
{
	Printf(PRINT_LOG, "Section %d, sector %d\n{\n", no, sect->sector->sectornum);

	for(int i = 0; i < sect->numloops; i++)
	{
		Printf(PRINT_LOG, "\tLoop %d\n\t{\n", i);

		FGLSectionLoop *loop = sect->GetLoop(i);

		for(int i = 0; i < loop->numlines; i++)
		{
			FGLSectionLine *ln = loop->GetLine(i);
			if (ln->sidedef != NULL)
			{
				vertex_t *v1 = V1(ln->sidedef);
				vertex_t *v2 = V2(ln->sidedef);
				double dx = FIXED2DBL(v2->x-v1->x);
				double dy = FIXED2DBL(v2->y-v1->y);
				double dx1 = FIXED2DBL(ln->start->x-v1->x);
				double dy1 = FIXED2DBL(ln->start->y-v1->y);
				double dx2 = FIXED2DBL(ln->end->x-v1->x);
				double dy2 = FIXED2DBL(ln->end->y-v1->y);
				double d = sqrt(dx*dx+dy*dy);
				double d1 = sqrt(dx1*dx1+dy1*dy1);
				double d2 = sqrt(dx2*dx2+dy2*dy2);

				Printf(PRINT_LOG, "\t\tLinedef %d, %s: Start (%1.2f, %1.2f), End (%1.2f, %1.2f)", 
					ln->linedef - lines, ln->sidedef == ln->linedef->sidedef[0]? "front":"back",
					ln->start->x/65536.f, ln->start->y/65536.f,
					ln->end->x/65536.f, ln->end->y/65536.f);

				if (ln->otherside != -1)
				{
					Printf (PRINT_LOG, ", other side = %d", ln->otherside);
				}
				if (d1 > 0.005 || d2 < 0.995)
				{
					Printf(PRINT_LOG, ", Range = %1.3f, %1.3f", d1/d, d2/d);
				}
			}
			else
			{
				Printf(PRINT_LOG, "\t\tMiniseg: Start (%1.3f, %1.3f), End (%1.3f, %1.3f)\n", 
					ln->start->x/65536.f, ln->start->y/65536.f, ln->end->x/65536.f, ln->end->y/65536.f);

				if (ln->otherside != -1)
				{
					Printf (PRINT_LOG, ", other side = %d", ln->otherside);
				}
			}
			Printf(PRINT_LOG, "\n");
		}
		Printf(PRINT_LOG, "\t}\n");
	}
	int prim = 1;
	for(unsigned i = 0; i < sect->vertices.Size(); i++)
	{
		int v = sect->vertices[i];
		if (v < 0)
		{
			if (i > 0)
			{
				Printf(PRINT_LOG, "\t}\n");
			}
			switch (v)
			{
			case -GL_TRIANGLE_FAN:
				Printf(PRINT_LOG, "\t%d: Triangle fan\n\t{\n", prim);
				break;

			case -GL_TRIANGLE_STRIP:
				Printf(PRINT_LOG, "\t%d: Triangle strip\n\t{\n", prim);
				break;

			case -GL_TRIANGLES:
				Printf(PRINT_LOG, "\t%d: Triangles\n\t{\n", prim);
				break;

			default:
				break;
			}
			prim++;
		}
		else
		{
			Printf(PRINT_LOG, "\t\tVertex %d: (%1.2f, %1.2f)\n", 
				v, vertexes[v].x/65536.f, vertexes[v].y/65536.f);
		}
	}
	Printf(PRINT_LOG, "}\n\n");
}

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

void DumpSections()
{
	for(unsigned i = 0; i < Sections.Size(); i++)
	{
		DumpSection(i, &Sections[i]);
	}
}

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

void gl_CreateSections()
{
	SectionLines.Clear();
	SectionLoops.Clear();
	Sections.Clear();
	SectionForSubsector.Resize(numsubsectors);
	memset(&SectionForSubsector[0], -1, numsubsectors * sizeof(SectionForSubsector[0]));
	FSectionCreator creat;
	creat.CreateSections();
	if (dumpsections) DumpSections();
}