//**************************************************************************
//**
//**	##   ##    ##    ##   ##   ####     ####   ###     ###
//**	##   ##  ##  ##  ##   ##  ##  ##   ##  ##  ####   ####
//**	 ## ##  ##    ##  ## ##  ##    ## ##    ## ## ## ## ##
//**	 ## ##  ########  ## ##  ##    ## ##    ## ##  ###  ##
//**	  ###   ##    ##   ###    ##  ##   ##  ##  ##       ##
//**	   #    ##    ##    #      ####     ####   ##       ##
//**
//**	$Id: level.cpp 1625 2006-07-30 16:35:14Z dj_jl $
//**
//**	Copyright (C) 1999-2006 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 ------------------------------------------------------------

#define check(a)

#include "cmdlib.h"
#include "wadlib.h"
#include "array.h"
#include <time.h>

using namespace VavoomUtils;

#include "fmapdefs.h"

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

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

//
// LineDef attributes.
//
enum
{
	ML_BLOCKING				= 0x00000001,	// Solid, is an obstacle.
	ML_BLOCKMONSTERS		= 0x00000002,	// Blocks monsters only.
	ML_TWOSIDED				= 0x00000004,	// Backside will not be present at all
	ML_DONTPEGTOP			= 0x00000008,	// upper texture unpegged
	ML_DONTPEGBOTTOM		= 0x00000010,	// lower texture unpegged
	ML_SECRET				= 0x00000020,	// don't map as two sided: IT'S A SECRET!
	ML_SOUNDBLOCK			= 0x00000040,	// don't let sound cross two of these
	ML_DONTDRAW				= 0x00000080,	// don't draw on the automap
	ML_MAPPED				= 0x00000100,	// set if already drawn in automap
	ML_REPEAT_SPECIAL		= 0x00000200,	// special is repeatable
	ML_SPAC_SHIFT			= 10,
	ML_SPAC_MASK			= 0x00001c00,
	ML_MONSTERSCANACTIVATE	= 0x00002000,	//	Monsters (as well as players) can activate the line
	ML_BLOCKEVERYTHING		= 0x00008000,	//	Line blocks everything.
	ML_PASSUSE_BOOM				= 0x0200,	//	Boom's ML_PASSUSE flag (conflicts with ML_REPEAT_SPECIAL)
	ML_RAILING_STRIFE			= 0x0200,
	ML_BLOCK_FLOATERS_STRIFE	= 0x0400,
	ML_UNKNOWN2_STRIFE			= 0x0800,
	ML_TRANSLUCENT_STRIFE		= 0x1000,
};
#define GET_SPAC(_flags_)		(((_flags_) & ML_SPAC_MASK) >> ML_SPAC_SHIFT)

//
// Special activation types
//
enum
{
	SPAC_Cross,			// when player crosses line
	SPAC_Use,			// when player uses line
	SPAC_MCross,		// when monster crosses line
	SPAC_Impact,		// when projectile hits line
	SPAC_Push,			// when player/monster pushes line
	SPAC_PCross,		// when projectile crosses line
	SPAC_UseThrough,	// SPAC_USE, but passes it through
	SPAC_PTouch			// when a projectiles crosses or hits line
};

enum
{
	MTF_EASY		= 0x0001,	// Skill flags.
	MTF_NORMAL		= 0x0002,
	MTF_HARD		= 0x0004,
	MTF_AMBUSH		= 0x0008,	// Deaf monsters/do not react to sound.
	MTF_DORMANT		= 0x0010,	// The thing is dormant
	MTF_FIGHTER		= 0x0020,	// Thing appearing in player classes
	MTF_CLERIC		= 0x0040,
	MTF_MAGE		= 0x0080,
	MTF_GSINGLE		= 0x0100,	// Appearing in game modes
	MTF_GCOOP		= 0x0200,
	MTF_GDEATHMATCH	= 0x0400,
	MTF_SHADOW		= 0x0800,
	MTF_ALTSHADOW	= 0x1000,
	MTF_FRIENDLY	= 0x2000,
	MTF_STANDSTILL	= 0x4000,
};

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

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

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

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

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

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

static TIWadFile	inwad;
static TOWadFile	outwad;

static TArray<char>	TextMap;

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

//==========================================================================
//
//	AddText
//
//==========================================================================

static void AddText(const char *error, ...)
{
	va_list		argptr;
	char		Buf[2048];

	va_start(argptr, error);
	vsprintf(Buf, error, argptr);
	va_end(argptr);

	int Len = strlen(Buf);
	for (int i = 0; i < Len; i++)
	{
		TextMap.Append(Buf[i]);
	}
}

//==========================================================================
//
//	ConvertVertexes
//
//==========================================================================

static void ConvertVertexes(int lump)
{
	int numvertexes = inwad.LumpSize(lump) / sizeof(mapvertex_t);
	void* data = inwad.GetLump(lump);
	mapvertex_t* ml = (mapvertex_t *)data;
	for (int i = 0; i < numvertexes; i++, ml++)
	{
		AddText("vertex\n");
		AddText("{\n");
		AddText(" x = %d;\n", LittleShort(ml->x));
		AddText(" y = %d;\n", LittleShort(ml->y));
		AddText("}\n");
	}
	Free(data);
}

//==========================================================================
//
//  ConvertSectors
//
//==========================================================================

static void ConvertSectors(int lump)
{
	int numsectors = inwad.LumpSize(lump) / sizeof(mapsector_t);
	void* data = inwad.GetLump(lump);
	mapsector_t* ml = (mapsector_t*)data;
	char TmpName[12];
	for (int i = 0; i < numsectors; i++, ml++)
	{
		AddText("sector\n");
		AddText("{\n");
		AddText(" heightfloor = %d;\n", LittleShort(ml->floorheight));
		AddText(" heightceiling = %d;\n", LittleShort(ml->ceilingheight));
		CleanupName(ml->floorpic, TmpName);
		AddText(" texturefloor = \"%s\";\n", TmpName);
		CleanupName(ml->ceilingpic, TmpName);
		AddText(" textureceiling = \"%s\";\n", TmpName);
		AddText(" lightlevel = %d;\n", LittleShort(ml->lightlevel));
		if (ml->special)
			AddText(" special = %d;\n", LittleShort(ml->special));
		if (ml->tag)
			AddText(" id = %d;\n", LittleShort(ml->tag));
		AddText("}\n");
	}
	Free(data);
}

//==========================================================================
//
//  ConvertSideDefs
//
//==========================================================================

static void ConvertSideDefs(int lump)
{
	int numsides = inwad.LumpSize(lump) / sizeof(mapsidedef_t);
	void* data = inwad.GetLump(lump);
	mapsidedef_t* ml = (mapsidedef_t*)data;
	char TmpName[12];
	for (int i = 0; i < numsides; i++, ml++)
	{
		AddText("sidedef\n");
		AddText("{\n");
		if (ml->textureoffset)
			AddText(" offsetx = %d;\n", LittleShort(ml->textureoffset));
		if (ml->rowoffset)
			AddText(" offsety = %d;\n", LittleShort(ml->rowoffset));
		CleanupName(ml->toptexture, TmpName);
		if (strcmp(TmpName, "-"))
			AddText(" texturetop = \"%s\";\n", TmpName);
		CleanupName(ml->bottomtexture, TmpName);
		if (strcmp(TmpName, "-"))
			AddText(" texturebottom = \"%s\";\n", TmpName);
		CleanupName(ml->midtexture, TmpName);
		if (strcmp(TmpName, "-"))
			AddText(" texturemiddle = \"%s\";\n", TmpName);
		AddText(" sector = %d;\n", LittleShort(ml->sector));
		AddText("}\n");
	}
	Free(data);
}

//==========================================================================
//
//  ConvertLineDefs1
//
//==========================================================================

static void ConvertLineDefs1(int lump)
{
	int numlines = inwad.LumpSize(lump) / sizeof(maplinedef1_t);
	void* data = inwad.GetLump(lump);
	maplinedef1_t* ml = (maplinedef1_t*)data;
	for (int i = 0; i < numlines; i++, ml++)
	{
		AddText("linedef\n");
		AddText("{\n");
		AddText(" v1 = %d;\n", LittleShort(ml->v1));
		AddText(" v2 = %d;\n", LittleShort(ml->v2));
		short Flags = LittleShort(ml->flags);
		if (Flags & ML_BLOCKING)
			AddText(" blocking = true;\n");
		if (Flags & ML_BLOCKMONSTERS)
			AddText(" blockmonsters = true;\n");
		if (Flags & ML_TWOSIDED)
			AddText(" twosided = true;\n");
		if (Flags & ML_DONTPEGTOP)
			AddText(" dontpegtop = true;\n");
		if (Flags & ML_DONTPEGBOTTOM)
			AddText(" dontpegbottom = true;\n");
		if (Flags & ML_SECRET)
			AddText(" secret = true;\n");
		if (Flags & ML_SOUNDBLOCK)
			AddText(" blocksound = true;\n");
		if (Flags & ML_DONTDRAW)
			AddText(" dontdraw = true;\n");
		if (Flags & ML_MAPPED)
			AddText(" mapped = true;\n");
		if (Flags & ML_PASSUSE_BOOM)
			AddText(" passuse = true;\n");
		if (ml->special)
			AddText(" special = %d;\n", LittleShort(ml->special));
		if (ml->tag)
			AddText(" arg0 = %d;\n", LittleShort(ml->tag));
		AddText(" id = %d;\n", LittleShort(ml->tag));
		AddText(" sidefront = %d;\n", LittleShort(ml->sidenum[0]));
		if (LittleShort(ml->sidenum[1]) != -1)
			AddText(" sideback = %d;\n", LittleShort(ml->sidenum[1]));
		AddText("}\n");
	}
	Free(data);
}

//==========================================================================
//
//  ConvertLineDefs2
//
//==========================================================================

static void ConvertLineDefs2(int lump)
{
	int numlines = inwad.LumpSize(lump) / sizeof(maplinedef2_t);
	void* data = inwad.GetLump(lump);
	maplinedef2_t* ml = (maplinedef2_t*)data;
	for (int i = 0; i < numlines; i++, ml++)
	{
		AddText("linedef\n");
		AddText("{\n");
		AddText(" v1 = %d;\n", LittleShort(ml->v1));
		AddText(" v2 = %d;\n", LittleShort(ml->v2));
		short Flags = LittleShort(ml->flags);
		if (Flags & ML_BLOCKING)
			AddText(" blocking = true;\n");
		if (Flags & ML_BLOCKMONSTERS)
			AddText(" blockmonsters = true;\n");
		if (Flags & ML_TWOSIDED)
			AddText(" twosided = true;\n");
		if (Flags & ML_DONTPEGTOP)
			AddText(" dontpegtop = true;\n");
		if (Flags & ML_DONTPEGBOTTOM)
			AddText(" dontpegbottom = true;\n");
		if (Flags & ML_SECRET)
			AddText(" secret = true;\n");
		if (Flags & ML_SOUNDBLOCK)
			AddText(" blocksound = true;\n");
		if (Flags & ML_DONTDRAW)
			AddText(" dontdraw = true;\n");
		if (Flags & ML_MAPPED)
			AddText(" mapped = true;\n");
		if (Flags & ML_REPEAT_SPECIAL)
			AddText(" repeatspecial = true;\n");
		switch (GET_SPAC(Flags))
		{
		case SPAC_Cross:
			AddText(" playercross = true;\n");
			break;
		case SPAC_Use:
			AddText(" playeruse = true;\n");
			break;
		case SPAC_MCross:
			AddText(" monstercross = true;\n");
			break;
		case SPAC_Impact:
			AddText(" impact = true;\n");
			break;
		case SPAC_Push:
			AddText(" playerpush = true;\n");
			break;
		case SPAC_PCross:
			AddText(" missilecross = true;\n");
			break;
		case SPAC_UseThrough:
			AddText(" playerusethrough = true;\n");
			break;
		case SPAC_PTouch:
			AddText(" missiletouch = true;\n");
			break;
		}
		if (ml->special)
			AddText(" special = %d;\n", ml->special);
		if (ml->arg1)
			AddText(" arg0 = %d;\n", ml->arg1);
		if (ml->arg2)
			AddText(" arg1 = %d;\n", ml->arg2);
		if (ml->arg3)
			AddText(" arg2 = %d;\n", ml->arg3);
		if (ml->arg4)
			AddText(" arg3 = %d;\n", ml->arg4);
		if (ml->arg5)
			AddText(" arg4 = %d;\n", ml->arg5);
		AddText(" sidefront = %d;\n", LittleShort(ml->sidenum[0]));
		if (LittleShort(ml->sidenum[1]) != -1)
			AddText(" sideback = %d;\n", LittleShort(ml->sidenum[1]));
		AddText("}\n");
	}
	Free(data);
}

//==========================================================================
//
//  ConvertThingDefs1
//
//==========================================================================

static void ConvertThingDefs1(int lump)
{
	int numthings = inwad.LumpSize(lump) / sizeof(mapthing1_t);
	void* data = inwad.GetLump(lump);
	mapthing1_t* ml = (mapthing1_t*)data;
	for (int i = 0; i < numthings; i++, ml++)
	{
		AddText("thing\n");
		AddText("{\n");
		AddText(" x = %d;\n", LittleShort(ml->x));
		AddText(" y = %d;\n", LittleShort(ml->y));
		if (ml->angle)
			AddText(" angle = %d;\n", LittleShort(ml->angle));
		AddText(" type = %d;\n", LittleShort(ml->type));
		short Flags = LittleShort(ml->options);
		if (Flags & MTF_EASY)
		{
			AddText(" skill1 = true;\n");
			AddText(" skill2 = true;\n");
		}
		if (Flags & MTF_NORMAL)
			AddText(" skill3 = true;\n");
		if (Flags & MTF_HARD)
		{
			AddText(" skill4 = true;\n");
			AddText(" skill5 = true;\n");
		}
		if (Flags & MTF_AMBUSH)
			AddText(" ambush = true;\n");
		if (!(Flags & 0x10))
			AddText(" single = true;\n");
		if (!(Flags & 0x20))
			AddText(" dm = true;\n");
		if (!(Flags & 0x40))
			AddText(" coop = true;\n");
		if (Flags & 0x80)
			AddText(" friend = true;\n");
		AddText("}\n");
	}
	Free(data);
}

//==========================================================================
//
//  ConvertThingDefs2
//
//==========================================================================

static void ConvertThingDefs2(int lump)
{
	int numthings = inwad.LumpSize(lump) / sizeof(mapthing2_t);
	void* data = inwad.GetLump(lump);
	mapthing2_t* ml = (mapthing2_t*)data;
	for (int i = 0; i < numthings; i++, ml++)
	{
		AddText("thing\n");
		AddText("{\n");
		if (ml->tid)
			AddText(" id = %d;\n", LittleShort(ml->tid));
		AddText(" x = %d;\n", LittleShort(ml->x));
		AddText(" y = %d;\n", LittleShort(ml->y));
		if (ml->angle)
			AddText(" angle = %d;\n", LittleShort(ml->angle));
		if (ml->height)
			AddText(" height = %d;\n", LittleShort(ml->height));
		AddText(" type = %d;\n", LittleShort(ml->type));
		short Flags = LittleShort(ml->options);
		if (Flags & MTF_EASY)
		{
			AddText(" skill1 = true;\n");
			AddText(" skill2 = true;\n");
		}
		if (Flags & MTF_NORMAL)
			AddText(" skill3 = true;\n");
		if (Flags & MTF_HARD)
		{
			AddText(" skill4 = true;\n");
			AddText(" skill5 = true;\n");
		}
		if (Flags & MTF_AMBUSH)
			AddText(" ambush = true;\n");
		if (Flags & MTF_DORMANT)
			AddText(" dormant = true;\n");
		if (Flags & MTF_FIGHTER)
			AddText(" class1 = true;\n");
		if (Flags & MTF_CLERIC)
			AddText(" class2 = true;\n");
		if (Flags & MTF_MAGE)
			AddText(" class3 = true;\n");
		if (Flags & MTF_GSINGLE)
			AddText(" single = true;\n");
		if (Flags & MTF_GCOOP)
			AddText(" coop = true;\n");
		if (Flags & MTF_GDEATHMATCH)
			AddText(" dm = true;\n");
		if (Flags & MTF_FRIENDLY)
			AddText(" friend = true;\n");
		if (ml->special)
			AddText(" special = %d;\n", ml->special);
		if (ml->arg1)
			AddText(" arg0 = %d;\n", ml->arg1);
		if (ml->arg2)
			AddText(" arg1 = %d;\n", ml->arg2);
		if (ml->arg3)
			AddText(" arg2 = %d;\n", ml->arg3);
		if (ml->arg4)
			AddText(" arg3 = %d;\n", ml->arg4);
		if (ml->arg5)
			AddText(" arg4 = %d;\n", ml->arg5);
		AddText("}\n");
	}
	Free(data);
}

//==========================================================================
//
//	IsLevelName
//
//==========================================================================

static bool IsLevelName(int lump)
{
	if (lump + 10 >= inwad.numlumps)
	{
		return false;
	}

	const char* name = inwad.LumpName(lump);

	if (!strcmp(inwad.LumpName(lump + 1), "THINGS") &&
		!strcmp(inwad.LumpName(lump + 2), "LINEDEFS") &&
		!strcmp(inwad.LumpName(lump + 3), "SIDEDEFS") &&
		!strcmp(inwad.LumpName(lump + 4), "VERTEXES"))
	{
		return true;
	}
	return false;
}

//==========================================================================
//
//	CopyLump
//
//==========================================================================

static void CopyLump(int i)
{
	void *ptr = inwad.GetLump(i);
	outwad.AddLump(inwad.LumpName(i), ptr, inwad.LumpSize(i));
	Free(ptr);
}

//==========================================================================
//
//	Run
//
//==========================================================================

static void Run(const char *srcfile, const char *dstfile)
{
	char filename[1024];
	char destfile[1024];

	strcpy(filename, srcfile);
	DefaultExtension(filename, ".wad");
	inwad.Open(filename);

	strcpy(destfile, dstfile);
	DefaultExtension(destfile, ".wad");
	outwad.Open(destfile, inwad.wadid);

	//	Process lumps
	int i = 0;
	while (i < inwad.numlumps)
	{
		if (IsLevelName(i))
		{
			const char *levelname = inwad.LumpName(i);
			bool extended = !stricmp(inwad.LumpName(i + ML_BEHAVIOR), "BEHAVIOR");

			TextMap.Clear();
			AddText("namespace = \"%s\";\n", extended ? "Hexen" : "Doom");

			ConvertVertexes(i + ML_VERTEXES);
			ConvertSectors(i + ML_SECTORS);
			ConvertSideDefs(i + ML_SIDEDEFS);
			if (extended)
			{
				ConvertLineDefs2(i + ML_LINEDEFS);
				ConvertThingDefs2(i + ML_THINGS);
			}
			else
			{
				ConvertLineDefs1(i + ML_LINEDEFS);
				ConvertThingDefs1(i + ML_THINGS);
			}

			//	Copy map header.
			CopyLump(i);

			outwad.AddLump("TEXTMAP", TextMap.Ptr(), TextMap.Num());

			CopyLump(i + ML_REJECT);
			CopyLump(i + ML_BLOCKMAP);

			if (inwad.LumpSize(i + ML_SSECTORS) > 4)
			{
				char* SSecPtr = (char*)inwad.GetLump(i + ML_SSECTORS);
				if (SSecPtr[0] == 'Z' && SSecPtr[1] == 'G' && SSecPtr[2] == 'L' && SSecPtr[3] == 'N')
				{
					outwad.AddLump("ZNODES", SSecPtr, inwad.LumpSize(i + ML_SSECTORS));
				}
				Free(SSecPtr);
			}

			i += 11;
			if (!stricmp(inwad.LumpName(i), "BEHAVIOR"))
			{
				CopyLump(i);
				i++;
				if (!stricmp(inwad.LumpName(i), "SCRIPTS"))
				{
					CopyLump(i);
					i++;
				}
			}
			outwad.AddLump("ENDMAP", NULL, 0);

			fprintf(stderr, "Converted %s\n", levelname);
		}
		else
		{
			CopyLump(i);
			i++;
		}
	}

	inwad.Close();
	outwad.Close();
}

//==========================================================================
//
//	ShowUsage
//
//==========================================================================

static void ShowUsage()
{
	fprintf(stderr, "\nToTextMap version 1.0, Copyright (c)2008 Janis "
		"Legzdinsh ("__DATE__" "__TIME__")\n");
	fprintf(stderr, "Usage: totextmap file[.wad] out[.wad]\n");
	exit(1);
}

//==========================================================================
//
//	main
//
//==========================================================================

int main(int argc, char *argv[])
{
	char *srcfile = NULL;
	char *dstfile = NULL;
	int i;

	int starttime = time(0);

	for (i = 1; i < argc; i++)
	{
		char *arg = argv[i];
		if (*arg == '-')
		{
			ShowUsage();
		}
		else
		{
			if (dstfile)
			{
				ShowUsage();
			}
			else if (srcfile)
			{
				dstfile = arg;
			}
			else
			{
				srcfile = arg;
			}
		}
	}

	if (!srcfile || !dstfile)
	{
		ShowUsage();
	}

	try
	{
		Run(srcfile, dstfile);
	}
	catch (WadLibError& e)
	{
		fprintf(stderr, "%s", e.message);
		return 1;
	}

	int worktime = time(0) - starttime;
	fprintf(stderr, "Time elapsed: %d:%02d:%02d\n", worktime / 3600,
		(worktime / 60) % 60, worktime % 60);

	return 0;
}
