/* -------------------------------------------------------------------------------

   Copyright (C) 1999-2007 id Software, Inc. and contributors.
   For a list of contributors, see the accompanying CONTRIBUTORS file.

   This file is part of GtkRadiant.

   GtkRadiant 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.

   GtkRadiant 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.

   You should have received a copy of the GNU General Public License
   along with GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

   ----------------------------------------------------------------------------------

   This code has been altered significantly from its original form, to support
   several games based on the Quake III Arena engine, in the form of "Q3Map2."

   ------------------------------------------------------------------------------- */



/* dependencies */
#include "q3map2.h"
#include "bspfile_abstract.h"
#include <ctime>




/* -------------------------------------------------------------------------------

   this file handles translating the bsp file format used by quake 3, rtcw, and ef
   into the abstracted bsp file used by q3map2.

   ------------------------------------------------------------------------------- */

/* constants */
#define LUMP_ENTITIES       0
#define LUMP_SHADERS        1
#define LUMP_PLANES         2
#define LUMP_NODES          3
#define LUMP_LEAFS          4
#define LUMP_LEAFSURFACES   5
#define LUMP_LEAFBRUSHES    6
#define LUMP_MODELS         7
#define LUMP_BRUSHES        8
#define LUMP_BRUSHSIDES     9
#define LUMP_DRAWVERTS      10
#define LUMP_DRAWINDEXES    11
#define LUMP_FOGS           12
#define LUMP_SURFACES       13
#define LUMP_LIGHTMAPS      14
#define LUMP_LIGHTGRID      15
#define LUMP_VISIBILITY     16
#define LUMP_ADVERTISEMENTS 17
#define LUMP_LIGHTGRID_SH   18

#define HEADER_LUMPS_V46    17      /* standard Q3: lumps 0-16, header = 144 bytes */
#define HEADER_LUMPS_V47    19      /* extended: lumps 0-18, header = 160 bytes */
#define HEADER_LUMPS_MAX    19      /* max for internal use */

#define IBSP_VERSION_V46    46
#define IBSP_VERSION_V47    47


/* types */
struct ibspHeader_v46_t
{
	char ident[ 4 ];
	int version;
	bspLump_t lumps[ HEADER_LUMPS_V46 ];
};

struct ibspHeader_v47_t
{
	char ident[ 4 ];
	int version;
	bspLump_t lumps[ HEADER_LUMPS_V47 ];
};

/* internal header -- always uses max lump count for convenience */
struct ibspHeader_t
{
	char ident[ 4 ];
	int version;
	bspLump_t lumps[ HEADER_LUMPS_MAX ];
};



/* brush sides */
struct ibspBrushSide_t
{
	int planeNum;
	int shaderNum;
	ibspBrushSide_t( const bspBrushSide_t& other ) :
		planeNum ( other.planeNum ),
		shaderNum( other.shaderNum ){}
	operator bspBrushSide_t() const {
		return { planeNum, shaderNum, -1 };
	}
};



/* drawsurfaces */
struct ibspDrawSurface_t
{
	int shaderNum;
	int fogNum;
	bspSurfaceType_t surfaceType;

	int firstVert;
	int numVerts;

	int firstIndex;
	int numIndexes;

	int lightmapNum;
	int lightmapX, lightmapY;
	int lightmapWidth, lightmapHeight;

	Vector3 lightmapOrigin;
	std::array<Vector3, 3> lightmapVecs;

	int patchWidth;
	int patchHeight;
	ibspDrawSurface_t( const bspDrawSurface_t& other ) :
		shaderNum     ( other.shaderNum ),
		fogNum        ( other.fogNum ),
		surfaceType   ( other.surfaceType ),
		firstVert     ( other.firstVert ),
		numVerts      ( other.numVerts ),
		firstIndex    ( other.firstIndex ),
		numIndexes    ( other.numIndexes ),
		lightmapNum   ( other.lightmapNum[0] ),
		lightmapX     ( other.lightmapX[0] ),
		lightmapY     ( other.lightmapY[0] ),
		lightmapWidth ( other.lightmapWidth ),
		lightmapHeight( other.lightmapHeight ),
		lightmapOrigin( other.lightmapOrigin ),
		lightmapVecs  ( other.lightmapVecs ),
		patchWidth    ( other.patchWidth ),
		patchHeight   ( other.patchHeight )
	{}
	operator bspDrawSurface_t() const {
		return bspDrawSurface_t{
			.shaderNum      = shaderNum,
			.fogNum         = fogNum,
			.surfaceType    = surfaceType,
			.firstVert      = firstVert,
			.numVerts       = numVerts,
			.firstIndex     = firstIndex,
			.numIndexes     = numIndexes,
			.lightmapStyles { LS_NORMAL, LS_NONE, LS_NONE, LS_NONE },
			.vertexStyles   { LS_NORMAL, LS_NONE, LS_NONE, LS_NONE },
			.lightmapNum    { lightmapNum, LIGHTMAP_BY_VERTEX, LIGHTMAP_BY_VERTEX, LIGHTMAP_BY_VERTEX },
			.lightmapX      { lightmapX, 0, 0, 0 },
			.lightmapY      { lightmapY, 0, 0, 0 },
			.lightmapWidth  = lightmapWidth,
			.lightmapHeight = lightmapHeight,
			.lightmapOrigin = lightmapOrigin,
			.lightmapVecs   = lightmapVecs,
			.patchWidth     = patchWidth,
			.patchHeight    = patchHeight
		};
	}
};



/* drawverts */
struct ibspDrawVert_t
{
	Vector3 xyz;
	Vector2 st;
	Vector2 lightmap;
	Vector3 normal;
	Color4b color;
	ibspDrawVert_t( const bspDrawVert_t& other ) :
		xyz     ( other.xyz ),
		st      ( other.st ),
		lightmap( other.lightmap[0] ),
		normal  ( other.normal ),
		color   ( other.color[0] )
	{}
	operator bspDrawVert_t() const {
		return bspDrawVert_t{
			.xyz     = xyz,
			.st      = st,
			.lightmap{ lightmap, Vector2( 0 ), Vector2( 0 ), Vector2( 0 ) },
			.normal  = normal,
			.color   { color, Color4b( 0 ), Color4b( 0 ), Color4b( 0 ) }
		};
	}
};



/* light grid */
struct ibspGridPoint_t
{
	Vector3b ambient;
	Vector3b directed;
	byte latLong[ 2 ];
	ibspGridPoint_t( const bspGridPoint_t& other ) :
		ambient ( other.ambient[0] ),
		directed( other.directed[0] ),
		latLong { other.latLong[0], other.latLong[1] }
	{}
	operator bspGridPoint_t() const {
		return bspGridPoint_t{
			.ambient  = makeArray4( ambient ),
			.directed = makeArray4( directed ),
			.styles   { LS_NORMAL, LS_NONE, LS_NONE, LS_NONE },
			.latLong  { latLong[0], latLong[1] }
		};
	}
};



/*
   LoadIBSPFile()
   loads a quake 3 bsp file into memory
 */

void LoadIBSPFile( const char *filename ){
	/* load the file */
	MemBuffer file = LoadFile( filename );

	/* read ident and version to determine header size */
	const byte *data = (const byte *)file.data();
	const int version = LittleLong( *(const int *)( data + 4 ) );

	/* make sure it matches the format we're trying to load */
	if ( !force && memcmp( data, g_game->bspIdent, 4 ) ) {
		Error( "%s is not a %s file", filename, g_game->bspIdent );
	}
	if ( !force && version != IBSP_VERSION_V46 && version != IBSP_VERSION_V47 ) {
		Error( "%s is version %d, expected %d or %d", filename, version, IBSP_VERSION_V46, IBSP_VERSION_V47 );
	}

	/* determine lump count from version */
	const int numLumps = ( version == IBSP_VERSION_V47 ) ? HEADER_LUMPS_V47 : HEADER_LUMPS_V46;

	/* CopyLump uses (byte*)header + offset to access file data,
	   so we overlay a bspHeader_t at the file buffer start and
	   copy/swap the lump directory into it. bspHeader_t has lumps[100]
	   so it can hold all our lumps. */
	bspHeader_t *header = (bspHeader_t *)file.data();
	/* swap just the lump directory entries we have */
	SwapBlock( (int *)( (byte *)header + 8 ), numLumps * sizeof( bspLump_t ) );
	/* note: lumps beyond numLumps are not zeroed -- they overlap file data
	   in the buffer. We only access extended lumps under version checks. */

	/* load/convert standard lumps (0-16) */
	CopyLump( header, LUMP_SHADERS, bspShaders );
	CopyLump( header, LUMP_MODELS, bspModels );
	CopyLump( header, LUMP_PLANES, bspPlanes );
	CopyLump( header, LUMP_LEAFS, bspLeafs );
	CopyLump( header, LUMP_NODES, bspNodes );
	CopyLump( header, LUMP_LEAFSURFACES, bspLeafSurfaces );
	CopyLump( header, LUMP_LEAFBRUSHES, bspLeafBrushes );
	CopyLump( header, LUMP_BRUSHES, bspBrushes );
	CopyLump<bspBrushSide_t, ibspBrushSide_t>( header, LUMP_BRUSHSIDES, bspBrushSides );
	CopyLump<bspDrawVert_t, ibspDrawVert_t>( header, LUMP_DRAWVERTS, bspDrawVerts );
	CopyLump<bspDrawSurface_t, ibspDrawSurface_t>( header, LUMP_SURFACES, bspDrawSurfaces );
	CopyLump( header, LUMP_FOGS, bspFogs );
	CopyLump( header, LUMP_DRAWINDEXES, bspDrawIndexes );
	CopyLump( header, LUMP_VISIBILITY, bspVisBytes );
	CopyLump( header, LUMP_LIGHTMAPS, bspLightBytes );
	CopyLump( header, LUMP_ENTITIES, bspEntData );
	CopyLump<bspGridPoint_t, ibspGridPoint_t>( header, LUMP_LIGHTGRID, bspGridPoints );

	/* v47 extended lumps (17-18) */
	if ( version == IBSP_VERSION_V47 ) {
		/* advertisements */
		CopyLump( header, LUMP_ADVERTISEMENTS, bspAds );

		/* SH light grid */
		const int length = header->lumps[LUMP_LIGHTGRID_SH].length;
		const int offset = header->lumps[LUMP_LIGHTGRID_SH].offset;
		if ( length > (int)sizeof( bspGridSHHeader_t ) ) {
			const bspGridSHHeader_t *shHeader = (const bspGridSHHeader_t *)( data + offset );
			gridMinsSH = shHeader->gridMins;
			gridSizeSH = shHeader->gridSize;
			gridBoundsSH[0] = shHeader->gridBounds[0];
			gridBoundsSH[1] = shHeader->gridBounds[1];
			gridBoundsSH[2] = shHeader->gridBounds[2];
			const int numPoints = shHeader->numPoints;
			const bspGridPointSH_t *points = (const bspGridPointSH_t *)( data + offset + sizeof( bspGridSHHeader_t ) );
			bspGridPointsSH.assign( points, points + numPoints );
		}
		else {
			bspGridPointsSH.clear();
		}
	}
	else {
		bspAds.clear();
		bspGridPointsSH.clear();
	}
}

/*
   LoadIBSPorRBSPFilePartially()
   loads bsp file parts meaningful for autopacker
 */

void LoadIBSPorRBSPFilePartially( const char *filename ){
	/* load the file */
	MemBuffer file = LoadFile( filename );

	const byte *data = (const byte *)file.data();
	const int version = LittleLong( *(const int *)( data + 4 ) );

	/* make sure it matches the format we're trying to load */
	if ( !force && memcmp( data, g_game->bspIdent, 4 ) ) {
		Error( "%s is not a %s file", filename, g_game->bspIdent );
	}
	if ( !force && version != IBSP_VERSION_V46 && version != IBSP_VERSION_V47 ) {
		Error( "%s is version %d, expected %d or %d", filename, version, IBSP_VERSION_V46, IBSP_VERSION_V47 );
	}

	const int numLumps = ( version == IBSP_VERSION_V47 ) ? HEADER_LUMPS_V47 : HEADER_LUMPS_V46;

	bspHeader_t *header = (bspHeader_t *)file.data();
	SwapBlock( (int *)( (byte *)header + 8 ), numLumps * sizeof( bspLump_t ) );
	for ( int i = numLumps; i < 100; ++i )
		header->lumps[i] = {};

	/* load/convert lumps */
	CopyLump( header, LUMP_SHADERS, bspShaders );
	if( g_game->load == LoadIBSPFile )
		CopyLump<bspDrawSurface_t, ibspDrawSurface_t>( header, LUMP_SURFACES, bspDrawSurfaces );
	else
		CopyLump( header, LUMP_SURFACES, bspDrawSurfaces );

	CopyLump( header, LUMP_FOGS, bspFogs );
	CopyLump( header, LUMP_ENTITIES, bspEntData );
}

/*
   WriteIBSPFile()
   writes an id bsp file
 */

void WriteIBSPFile( const char *filename ){
	/* determine version: v47 if SH data exists, v46 otherwise */
	const bool extendedBSP = !bspGridPointsSH.empty();
	const int bspVersion = extendedBSP ? IBSP_VERSION_V47 : IBSP_VERSION_V46;
	const int numLumps = extendedBSP ? HEADER_LUMPS_V47 : HEADER_LUMPS_V46;
	const int headerSize = 8 + numLumps * (int)sizeof( bspLump_t );

	ibspHeader_t header{};

	//%	Swapfile();

	/* set up header */
	memcpy( header.ident, g_game->bspIdent, 4 );
	header.version = LittleLong( bspVersion );

	/* write initial header (placeholder, overwritten at the end) */
	FILE *file = SafeOpenWrite( filename );
	SafeWrite( file, &header, headerSize );

	{ /* add marker lump */
		time_t t;
		time( &t );
		/* asctime adds an implicit trailing \n */
		const auto marker = StringStream( "I LOVE MY Q3MAP2 " Q3MAP_VERSION " on ", asctime( localtime( &t ) ) );
		AddLump( file, header.lumps[0], std::vector<char>( marker.cbegin(), marker.cend() + 1 ) );
	}

	/* add standard lumps (0-16) */
	AddLump( file, header.lumps[LUMP_SHADERS], bspShaders );
	AddLump( file, header.lumps[LUMP_PLANES], bspPlanes );
	AddLump( file, header.lumps[LUMP_LEAFS], bspLeafs );
	AddLump( file, header.lumps[LUMP_NODES], bspNodes );
	AddLump( file, header.lumps[LUMP_BRUSHES], bspBrushes );
	AddLump( file, header.lumps[LUMP_BRUSHSIDES], std::vector<ibspBrushSide_t>( bspBrushSides.begin(), bspBrushSides.end() ) );
	AddLump( file, header.lumps[LUMP_LEAFSURFACES], bspLeafSurfaces );
	AddLump( file, header.lumps[LUMP_LEAFBRUSHES], bspLeafBrushes );
	AddLump( file, header.lumps[LUMP_MODELS], bspModels );
	AddLump( file, header.lumps[LUMP_DRAWVERTS], std::vector<ibspDrawVert_t>( bspDrawVerts.begin(), bspDrawVerts.end() ) );
	AddLump( file, header.lumps[LUMP_SURFACES], std::vector<ibspDrawSurface_t>( bspDrawSurfaces.begin(), bspDrawSurfaces.end() ) );
	AddLump( file, header.lumps[LUMP_VISIBILITY], bspVisBytes );
	AddLump( file, header.lumps[LUMP_LIGHTMAPS], bspLightBytes );
	AddLump( file, header.lumps[LUMP_LIGHTGRID], std::vector<ibspGridPoint_t>( bspGridPoints.begin(), bspGridPoints.end() ) );
	AddLump( file, header.lumps[LUMP_ENTITIES], bspEntData );
	AddLump( file, header.lumps[LUMP_FOGS], bspFogs );
	AddLump( file, header.lumps[LUMP_DRAWINDEXES], bspDrawIndexes );

	/* v47 extended lumps (17-18) */
	if ( extendedBSP ) {
		/* advertisements */
		AddLump( file, header.lumps[LUMP_ADVERTISEMENTS], bspAds );

		/* SH light grid -- header + points packed into a byte lump */
		const size_t shHeaderSz = sizeof( bspGridSHHeader_t );
		const size_t dataSz = bspGridPointsSH.size() * sizeof( bspGridPointSH_t );
		std::vector<byte> shLump( shHeaderSz + dataSz );
		bspGridSHHeader_t shHeader;
		shHeader.gridMins = gridMinsSH;
		shHeader.gridSize = gridSizeSH;
		shHeader.gridBounds[0] = gridBoundsSH[0];
		shHeader.gridBounds[1] = gridBoundsSH[1];
		shHeader.gridBounds[2] = gridBoundsSH[2];
		shHeader.numPoints = (int)bspGridPointsSH.size();
		memcpy( shLump.data(), &shHeader, shHeaderSz );
		memcpy( shLump.data() + shHeaderSz, bspGridPointsSH.data(), dataSz );
		AddLump( file, header.lumps[LUMP_LIGHTGRID_SH], shLump );
	}

	/* emit bsp size */
	const int size = ftell( file );
	Sys_Printf( "Wrote %.1f MB (%d bytes)\n", (float) size / ( 1024 * 1024 ), size );

	/* write the completed header */
	fseek( file, 0, SEEK_SET );
	SafeWrite( file, &header, headerSize );

	/* close the file */
	fclose( file );
}
