//**************************************************************************
//**
//**	##   ##    ##    ##   ##   ####     ####   ###     ###
//**	##   ##  ##  ##  ##   ##  ##  ##   ##  ##  ####   ####
//**	 ## ##  ##    ##  ## ##  ##    ## ##    ## ## ## ## ##
//**	 ## ##  ########  ## ##  ##    ## ##    ## ##  ###  ##
//**	  ###   ##    ##   ###    ##  ##   ##  ##  ##       ##
//**	   #    ##    ##    #      ####     ####   ##       ##
//**
//**	$Id: level.cpp,v 1.10 2004/10/11 06:49:04 dj_jl Exp $
//**
//**	Copyright (C) 1999-2002 JÆnis Legzdi·ý
//**
//**	This program is free software; you can redistribute it and/or
//**  modify it under 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 program is distributed in the hope that it will be useful,
//**  but WITHOUT ANY WARRANTY; without even the implied warranty of
//**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//**  GNU General Public License for more details.
//**
//**************************************************************************

// HEADER FILES ------------------------------------------------------------

#include "glvisint.h"

namespace VavoomUtils {

#include "fmapdefs.h"

// MACROS ------------------------------------------------------------------

#define TEMP_FILE	"$glvis$$.$$$"

// TYPES -------------------------------------------------------------------

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

// PUBLIC DATA DEFINITIONS -------------------------------------------------

// PRIVATE DATA DEFINITIONS ------------------------------------------------

// CODE --------------------------------------------------------------------

//==========================================================================
//
//	GLVisMalloc
//
//==========================================================================

static void *GLVisMalloc(size_t size)
{
	if (!size)
	{
		size = 4;
	}
	size = (size + 3) & ~3;
	void *ptr = malloc(size);
	if (!ptr)
	{
		throw GLVisError("Couldn't alloc %d bytes", size);
	}
	memset(ptr, 0, size);
	return ptr;
}

//==========================================================================
//
//	GLVisFree
//
//==========================================================================

static void GLVisFree(void *ptr)
{
	free(ptr);
}

//==========================================================================
//
//	TVisBuilder::TVisBuilder
//
//==========================================================================

TVisBuilder::TVisBuilder(TGLVis &AOwner) : Owner(AOwner),
	numvertexes(0), vertexes(NULL), gl_vertexes(NULL),
	numsegs(0), segs(NULL),
	numsubsectors(0), subsectors(NULL),
	numportals(0), portals(NULL),
	vissize(0), vis(NULL),
	bitbytes(0), bitlongs(0),
	portalsee(0), c_leafsee(0), c_portalsee(0),
	c_chains(0), c_portalskip(0), c_leafskip(0),
	c_vistest(0), c_mighttest(0),
	c_portaltest(0), c_portalpass(0), c_portalcheck(0),
	totalvis(0), rowbytes(0)
{
}

//==========================================================================
//
//	TVisBuilder::LoadVertexes
//
//==========================================================================

void TVisBuilder::LoadVertexes(int lump, int gl_lump)
{
	int				i;
	void*			data;
	void*			gldata;
	mapvertex_t*	ml;
	vertex_t*		li;
	int				base_verts;
	int				gl_verts;

	base_verts = mainwad->LumpSize(lump) / sizeof(mapvertex_t);

	gldata = glwad->GetLump(gl_lump);
	if (!strncmp((char*)gldata, GL_V2_MAGIC, 4))
	{
		gl_verts = (glwad->LumpSize(gl_lump) - 4) / sizeof(gl_mapvertex_t);
	}
	else
	{
		gl_verts = glwad->LumpSize(gl_lump) / sizeof(mapvertex_t);
	}
	numvertexes = base_verts + gl_verts;

	// Allocate zone memory for buffer.
	li = vertexes = New<vertex_t>(numvertexes);

	// Load data into cache.
	data = mainwad->GetLump(lump);
	ml = (mapvertex_t *)data;

	// Copy and convert vertex, internal representation as vector.
	for (i = 0; i < base_verts; i++, li++, ml++)
	{
		*li = TVec(LittleShort(ml->x), LittleShort(ml->y));
	}

	// Free buffer memory.
	Free(data);

	//	Save pointer to GL vertexes for seg loading
	gl_vertexes = li;

	if (!strncmp((char*)gldata, GL_V2_MAGIC, 4))
	{
		gl_mapvertex_t*		glml;

		glml = (gl_mapvertex_t *)((byte*)gldata + 4);

		// Copy and convert vertex, internal representation as vector.
		for (i = 0; i < gl_verts; i++, li++, glml++)
		{
			*li = TVec((double)LittleLong(glml->x) / (double)0x10000,
					   (double)LittleLong(glml->y) / (double)0x10000);
		}
	}
	else
	{
		ml = (mapvertex_t *)gldata;

		// Copy and convert vertex, internal representation as vector.
		for (i = 0; i < gl_verts; i++, li++, ml++)
		{
			*li = TVec(LittleShort(ml->x), LittleShort(ml->y));
		}
	}

	// Free buffer memory.
	Free(gldata);
}

//==========================================================================
//
//  TVisBuilder::LoadSectors
//
//==========================================================================

void TVisBuilder::LoadSectors(int lump)
{
	numsectors = mainwad->LumpSize(lump) / sizeof(mapsector_t);
}

//==========================================================================
//
//  TVisBuilder::LoadSideDefs
//
//==========================================================================

void TVisBuilder::LoadSideDefs(int lump)
{
	mapsidedef_t *data;
	int i;

	numsides = mainwad->LumpSize(lump) / sizeof(mapsidedef_t);
	sidesecs = New<int>(numsides);
	data = (mapsidedef_t *)mainwad->GetLump(lump);
	for (i = 0; i < numsides; i++)
	{
		sidesecs[i] = LittleShort(data[i].sector);
	}
	Free(data);
}

//==========================================================================
//
//  TVisBuilder::LoadLineDefs1
//
//  For Doom and Heretic
//
//==========================================================================

void TVisBuilder::LoadLineDefs1(int lump)
{
	void *data;
	int i;
	maplinedef1_t *mld;
	line_t *ld;

	numlines = mainwad->LumpSize(lump) / sizeof(maplinedef1_t);
	lines = New<line_t>(numlines);
	data = mainwad->GetLump(lump);

	mld = (maplinedef1_t *)data;
	ld = lines;
	for (i = 0; i < numlines; i++, mld++, ld++)
	{
		int side0 = LittleShort(mld->sidenum[0]);
		int side1 = LittleShort(mld->sidenum[1]);

		if (side0 != -1)
			ld->secnum[0] = sidesecs[side0];
		else
			ld->secnum[0] = -1;

		if (side1 != -1)
			ld->secnum[1] = sidesecs[side1];
		else
			ld->secnum[1] = -1;
	}

	Free(data);
}

//==========================================================================
//
//  TVisBuilder::LoadLineDefs2
//
//  For Hexen
//
//==========================================================================

void TVisBuilder::LoadLineDefs2(int lump)
{
	void *data;
	int i;
	maplinedef2_t *mld;
	line_t *ld;

	numlines = mainwad->LumpSize(lump) / sizeof(maplinedef2_t);
	lines = New<line_t>(numlines);
	data = mainwad->GetLump(lump);

	mld = (maplinedef2_t *)data;
	ld = lines;
	for (i = 0; i < numlines; i++, mld++, ld++)
	{
		int side0 = LittleShort(mld->sidenum[0]);
		int side1 = LittleShort(mld->sidenum[1]);

		if (side0 != -1)
			ld->secnum[0] = sidesecs[side0];
		else
			ld->secnum[0] = -1;

		if (side1 != -1)
			ld->secnum[1] = sidesecs[side1];
		else
			ld->secnum[1] = -1;
	}

	Free(data);
}

//==========================================================================
//
//	TVisBuilder::LoadSegs
//
//==========================================================================

void TVisBuilder::LoadSegs(int lump)
{
	void*		data;
	int			i;
	seg_t*		li;

	data = glwad->GetLump(lump);
	if (!strncmp((char*)data, GL_V3_MAGIC, 4))
	{
		numsegs = (glwad->LumpSize(lump) - 4) / sizeof(mapglseg_v3_t);
		segs = New<seg_t>(numsegs);
	
		mapglseg_v3_t* ml = (mapglseg_v3_t*)((byte*)data + 4);
		li = segs;
		numportals = 0;
	
		for (i = 0; i < numsegs; i++, li++, ml++)
		{
			dword	v1num =	LittleLong(ml->v1);
			dword	v2num =	LittleLong(ml->v2);
	
			if (v1num & GL_VERTEX_V3)
			{
				v1num ^= GL_VERTEX_V3;
				li->v1 = &gl_vertexes[v1num];
			}
			else
			{
				li->v1 = &vertexes[v1num];
			}
			if (v2num & GL_VERTEX_V3)
			{
				v2num ^= GL_VERTEX_V3;
				li->v2 = &gl_vertexes[v2num];
			}
			else
			{
				li->v2 = &vertexes[v2num];
			}
	
			int partner = LittleLong(ml->partner);
			if (partner >= 0)
			{
				li->partner = &segs[partner];
				numportals++;
			}
	
			int linenum = LittleShort(ml->linedef);
			if (linenum >= 0)
			{
				li->secnum = lines[linenum].secnum[LittleShort(ml->flags) &
					GL_SEG_FLAG_SIDE];
			}
			else
			{
				li->secnum = -1;
			}
	
			//	Calc seg's plane params
			li->Set2Points(*li->v1, *li->v2);
		}
	}
	else
	{
		numsegs = glwad->LumpSize(lump) / sizeof(mapglseg_t);
		segs = New<seg_t>(numsegs);
	
		mapglseg_t* ml = (mapglseg_t *)data;
		li = segs;
		numportals = 0;
	
		for (i = 0; i < numsegs; i++, li++, ml++)
		{
			word	v1num =	LittleShort(ml->v1);
			word	v2num =	LittleShort(ml->v2);
	
			if (v1num & GL_VERTEX)
			{
				v1num ^= GL_VERTEX;
				li->v1 = &gl_vertexes[v1num];
			}
			else
			{
				li->v1 = &vertexes[v1num];
			}
			if (v2num & GL_VERTEX)
			{
				v2num ^= GL_VERTEX;
				li->v2 = &gl_vertexes[v2num];
			}
			else
			{
				li->v2 = &vertexes[v2num];
			}
	
			int partner = LittleShort(ml->partner);
			if (partner >= 0)
			{
				li->partner = &segs[partner];
				numportals++;
			}
	
			int linenum = LittleShort(ml->linedef);
			if (linenum >= 0)
			{
				li->secnum = lines[linenum].secnum[LittleShort(ml->side)];
			}
			else
			{
				li->secnum = -1;
			}
	
			//	Calc seg's plane params
			li->Set2Points(*li->v1, *li->v2);
		}
	}

	Free(data);

	portals = New<portal_t>(numportals);
}

//==========================================================================
//
//	TVisBuilder::LoadSubsectors
//
//==========================================================================

void TVisBuilder::LoadSubsectors(int lump)
{
	void*				data;
	int					i;
	subsector_t*		ss;

	data = glwad->GetLump(lump);
	if (!strncmp((char*)data, GL_V3_MAGIC, 4))
	{
		numsubsectors = (glwad->LumpSize(lump) - 4) / 
			sizeof(mapglsubsector_v3_t);
		subsectors = New<subsector_t>(numsubsectors);
	
		mapglsubsector_v3_t* ms = (mapglsubsector_v3_t*)((byte*)data + 4);
		ss = subsectors;
	
		for (i = 0; i < numsubsectors; i++, ss++, ms++)
		{
			//	Set seg subsector links
			int count = LittleLong(ms->numsegs);
			seg_t *line = &segs[LittleLong(ms->firstseg)];
			ss->secnum = -1;
			while (count--)
			{
				line->leaf = i;
				if (ss->secnum == -1 && line->secnum >= 0)
				{
					ss->secnum = line->secnum;
				}
				else if (ss->secnum != -1 && line->secnum >= 0 &&
					ss->secnum != line->secnum)
				{
					Owner.DisplayMessage("Segs from different sectors\n");
				}
				line++;
			}
			if (ss->secnum == -1)
			{
				throw GLVisError("Subsector without sector");
			}
		}
	}
	else
	{
		numsubsectors = glwad->LumpSize(lump) / sizeof(mapsubsector_t);
		subsectors = New<subsector_t>(numsubsectors);
	
		mapsubsector_t* ms = (mapsubsector_t *)data;
		ss = subsectors;
	
		for (i = 0; i < numsubsectors; i++, ss++, ms++)
		{
			//	Set seg subsector links
			int count = (word)LittleShort(ms->numsegs);
			seg_t *line = &segs[(word)LittleShort(ms->firstseg)];
			ss->secnum = -1;
			while (count--)
			{
				line->leaf = i;
				if (ss->secnum == -1 && line->secnum >= 0)
				{
					ss->secnum = line->secnum;
				}
				else if (ss->secnum != -1 && line->secnum >= 0 &&
					ss->secnum != line->secnum)
				{
					Owner.DisplayMessage("Segs from different sectors\n");
				}
				line++;
			}
			if (ss->secnum == -1)
			{
				throw GLVisError("Subsector without sector");
			}
		}
	}

	Free(data);
}

//==========================================================================
//
//	TVisBuilder::CreatePortals
//
//==========================================================================

void TVisBuilder::CreatePortals(void)
{
	int i;

	portal_t *p = portals;
	for (i = 0; i < numsegs; i++)
	{
		seg_t *line = &segs[i];
		subsector_t *sub = &subsectors[line->leaf];
		if (line->partner)
		{
			// create portal
			if (sub->numportals == MAX_PORTALS_ON_LEAF)
				throw GLVisError("Leaf with too many portals");
			sub->portals[sub->numportals] = p;
			sub->numportals++;

			p->winding.original = true;
			p->winding.points[0] = *line->v1;
			p->winding.points[1] = *line->v2;
			p->normal = line->partner->normal;
			p->dist = line->partner->dist;
			p->leaf = line->partner->leaf;
			p++;
		}
	}
	if (p - portals != numportals)
		throw GLVisError("Portals miscounted");
}
	
//==========================================================================
//
//	TVisBuilder::LoadLevel
//
//==========================================================================

void TVisBuilder::LoadLevel(int lumpnum, int gl_lumpnum)
{
	const char *levelname = mainwad->LumpName(lumpnum);
	bool extended = !stricmp(mainwad->LumpName(lumpnum + ML_BEHAVIOR), "BEHAVIOR");

	LoadVertexes(lumpnum + ML_VERTEXES, gl_lumpnum + ML_GL_VERT);
	LoadSectors(lumpnum + ML_SECTORS);
	LoadSideDefs(lumpnum + ML_SIDEDEFS);
	if (extended)
		LoadLineDefs2(lumpnum + ML_LINEDEFS);
	else
		LoadLineDefs1(lumpnum + ML_LINEDEFS);
	LoadSegs(gl_lumpnum + ML_GL_SEGS);
	LoadSubsectors(gl_lumpnum + ML_GL_SSECT);
	reject = NULL;

	CreatePortals();

	Owner.DisplayMessage(
		"\nLoaded %s, %d vertexes, %d segs, %d subsectors, %d portals\n",
		levelname, numvertexes, numsegs, numsubsectors, numportals);
	Owner.DisplayStartMap(levelname);
}

//==========================================================================
//
//	TVisBuilder::FreeLevel
//
//==========================================================================

void TVisBuilder::FreeLevel(void)
{
	Delete(vertexes);
	Delete(sidesecs);
	Delete(lines);
	Delete(segs);
	Delete(subsectors);
	Delete(portals);
	Delete(vis);
	if (reject)
	{
		Delete(reject);
	}
}

//==========================================================================
//
//	TVisBuilder::IsLevelName
//
//==========================================================================

bool TVisBuilder::IsLevelName(int lump)
{
	if (lump + 10 >= glwad->numlumps)
	{
		return false;
	}

	const char	*name = glwad->LumpName(lump);

	if (Owner.num_specified_maps)
	{
		for (int i = 0; i < Owner.num_specified_maps; i++)
		{
			if (!stricmp(Owner.specified_maps[i], name))
			{
				return true;
			}
		}
	}
	else
	{
		if (!strcmp(glwad->LumpName(lump + 1), "THINGS") &&
			!strcmp(glwad->LumpName(lump + 2), "LINEDEFS") &&
			!strcmp(glwad->LumpName(lump + 3), "SIDEDEFS") &&
			!strcmp(glwad->LumpName(lump + 4), "VERTEXES"))
		{
			return true;
		}
	}
	return false;
}

//==========================================================================
//
//	TVisBuilder::IsGLLevelName
//
//==========================================================================

bool TVisBuilder::IsGLLevelName(int lump)
{
	if (lump + 4 >= glwad->numlumps)
	{
		return false;
	}

	const char	*name = glwad->LumpName(lump);

	if (name[0] != 'G' || name[1] != 'L' || name[2] != '_')
	{
		return false;
	}

	if (Owner.num_specified_maps)
	{
		for (int i = 0; i < Owner.num_specified_maps; i++)
		{
			if (!stricmp(Owner.specified_maps[i], name + 3))
			{
				return true;
			}
		}
	}
	else
	{
		if (!strcmp(glwad->LumpName(lump + 1), "GL_VERT") &&
			!strcmp(glwad->LumpName(lump + 2), "GL_SEGS") &&
			!strcmp(glwad->LumpName(lump + 3), "GL_SSECT") &&
			!strcmp(glwad->LumpName(lump + 4), "GL_NODES"))
		{
			return true;
		}
	}
	return false;
}

//==========================================================================
//
//	TVisBuilder::CopyLump
//
//==========================================================================

void TVisBuilder::CopyLump(int i)
{
	void *ptr =	glwad->GetLump(i);
	outwad.AddLump(glwad->LumpName(i), ptr, glwad->LumpSize(i));
	Free(ptr);
}

//==========================================================================
//
//	TVisBuilder::BuildGWA
//
//==========================================================================

void TVisBuilder::BuildGWA(void)
{
	int i = 0;
	while (i < glwad->numlumps)
	{
		if (IsGLLevelName(i))
		{
			const char *name = glwad->LumpName(i);
			LoadLevel(mainwad->LumpNumForName(name + 3), i);
			BuildPVS();

			//	Write lumps
			for (int ii = 0; ii < 5; ii++)
			{
				CopyLump(i + ii);
			}
			i += 5;
			if (!strcmp("GL_PVS", glwad->LumpName(i)))
			{
				i++;
			}
			outwad.AddLump("GL_PVS", vis, vissize);

			FreeLevel();
		}
		else
		{
			CopyLump(i);
			i++;
		}
	}
}

//==========================================================================
//
//	TVisBuilder::BuildWAD
//
//==========================================================================

void TVisBuilder::BuildWAD(void)
{
	int i = 0;
	while (i < glwad->numlumps)
	{
		if (IsLevelName(i))
		{
			const char *name = glwad->LumpName(i);
			int gl_lump = mainwad->LumpNumForName(va("GL_%s", name));

			LoadLevel(i, gl_lump);
			BuildPVS();
			if (!Owner.no_reject)
			{
				BuildReject();
			}

			//	Write lumps. Assume GL-nodes immediately follows map data
			for (int mi = i; mi < gl_lump + 5; mi++)
			{
				if (mi == i + ML_REJECT && !Owner.no_reject)
				{
					outwad.AddLump("REJECT", reject, rejectsize);
				}
				else
				{
					CopyLump(mi);
				}
			}
			i = gl_lump + 5;
			if (!strcmp("GL_PVS", glwad->LumpName(i)))
			{
				i++;
			}
			outwad.AddLump("GL_PVS", vis, vissize);

			FreeLevel();
		}
		else
		{
			CopyLump(i);
			i++;
		}
	}
}

//==========================================================================
//
//	TVisBuilder::Run
//
//==========================================================================

void TVisBuilder::Run(const char *srcfile)
{
	char filename[1024];
	char destfile[1024];
	char bakext[8];

	if (Owner.Malloc && Owner.Free)
	{
		Malloc = Owner.Malloc;
		Free = Owner.Free;
	}
	else
	{
		Malloc = GLVisMalloc;
		Free = GLVisFree;
	}

	strcpy(filename, srcfile);
	DefaultExtension(filename, ".wad");
	strcpy(destfile, filename);
	inwad.Open(filename);
	mainwad = &inwad;

	StripExtension(filename);
	strcat(filename, ".gwa");
	FILE *ff = fopen(filename, "rb");
	if (ff)
	{
		fclose(ff);
		gwa.Open(filename);
		glwad = &gwa;
		strcpy(destfile, filename);
		strcpy(bakext, ".~gw");
	}
	else
	{
		glwad = &inwad;
		strcpy(bakext, ".~wa");
	}

	outwad.Open(TEMP_FILE, glwad->wadid);

	//	Process lumps
	if (mainwad == glwad)
	{
		BuildWAD();
	}
	else
	{
		BuildGWA();
	}

	inwad.Close();
	if (gwa.handle)
	{
		gwa.Close();
	}
	outwad.Close();

	strcpy(filename, destfile);
	StripExtension(filename);
	strcat(filename, bakext);
	remove(filename);
	rename(destfile, filename);
	rename(TEMP_FILE, destfile);
}

} // namespace VavoomUtils

using namespace VavoomUtils;

//==========================================================================
//
//	GLVisError::GLVisError
//
//==========================================================================

GLVisError::GLVisError(const char *error, ...)
{
	va_list		argptr;

	va_start(argptr, error);
	vsprintf(message, error, argptr);
	va_end(argptr);
}

//==========================================================================
//
//	TGLVis::Build
//
//==========================================================================

void TGLVis::Build(const char *srcfile)
{
	try
	{
		TVisBuilder VisBuilder(*this);

		VisBuilder.Run(srcfile);
	}
	catch (WadLibError &e)
	{
		throw GLVisError("%s", e.message);
	}
}

//**************************************************************************
//
//	$Log: level.cpp,v $
//	Revision 1.10  2004/10/11 06:49:04  dj_jl
//	Added support for version 3.0 GL nodes.
//	
//	Revision 1.9  2002/01/07 12:30:05  dj_jl
//	Changed copyright year
//	
//	Revision 1.8  2002/01/03 18:35:14  dj_jl
//	Switched to doubles, some fixes
//	
//	Revision 1.7  2001/10/27 07:53:03  dj_jl
//	Fixes for non-closed sectors
//	
//	Revision 1.6  2001/10/18 17:41:47  dj_jl
//	Added reject building
//	
//	Revision 1.5  2001/09/20 16:38:05  dj_jl
//	Moved TGLVis out of namespace
//	
//	Revision 1.4  2001/09/12 17:28:38  dj_jl
//	Created glVIS plugin
//	
//	Revision 1.3  2001/08/24 17:08:34  dj_jl
//	Beautification
//	
//	Revision 1.2  2001/07/27 14:27:55  dj_jl
//	Update with Id-s and Log-s, some fixes
//
//**************************************************************************
