/*
** buildtexture.cpp
** Handling Build textures
**
**---------------------------------------------------------------------------
** Copyright 2019 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.
**
** 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 "files.h"
#include "zstring.h"
#include "buildtiles.h"
#include "image.h"

#include "palette.h"
#include "m_crc32.h"
#include "build.h"
#include "gamecontrol.h"
#include "palettecontainer.h"
#include "texturemanager.h"

enum
{
	MAXARTFILES_BASE = 200,
	MAXARTFILES_TOTAL = 220
};


BuildTiles TileFiles;

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

picanm_t tileConvertAnimFormat(int32_t const picanimraw, int* lo, int* to)
{
	// Unpack a 4 byte packed anim descriptor into something more accessible
	picanm_t anm;
	anm.num = picanimraw & 63;
	*lo = (int8_t)((picanimraw >> 8) & 255);
	*to = (int8_t)((picanimraw >> 16) & 255);
	anm.sf = ((picanimraw >> 24) & 15) | (picanimraw & 192);
	anm.extra = (picanimraw >> 28) & 15;
	return anm;
}


//==========================================================================
//
// Base class for Build tile textures
// This needs a few subclasses for different use cases.
//
//==========================================================================

int FTileTexture::CopyPixels(FBitmap* bmp, int conversion)
{
	TArray<uint8_t> buffer;
	bmp->Create(Width, Height);
	auto ppix = GetRawData();
	if (ppix)
	{
		bmp->CopyPixelData(0, 0, ppix, Width, Height, Height, 1, 0, GPalette.BaseColors);
	}
	return 0;
}

TArray<uint8_t> FTileTexture::CreatePalettedPixels(int conversion)
{
	TArray<uint8_t> buffer(Width * Height, true);
	auto p = GetRawData();
	if (p) memcpy(buffer.Data(), p, buffer.Size());
	else memset(buffer.Data(), 0, buffer.Size());
	return buffer;
}

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

static FGameTexture* GetTileTexture(const char* name, const TArray<uint8_t>& backingstore, uint32_t offset, int width, int height)
{
	auto tex = new FArtTile(backingstore, offset, width, height);
	auto p = &backingstore[offset];
	auto siz = width * height;
	for (int i = 0; i < siz; i++, p++)
	{
		// move transparent color to index 0 to get in line with the rest of the texture management.
		if (*p == 0) *p = 255;
		else if (*p == 255) *p = 0;
	}

	if (tex)
	{
		return MakeGameTexture(new FImageTexture(tex), name, ETextureType::Any);
	}
	return nullptr;
}


void BuildTiles::Init()
{
	Placeholder = TexMan.GameByIndex(0);
	for (auto& tile : tiledata)
	{
		tile.texture = Placeholder;
		tile.backup = Placeholder;
		tile.picanm = {};
		tile.RotTile = { -1,-1 };
		tile.replacement = ReplacementType::Art;
		tile.alphaThreshold = 0.5;
	}

}
//==========================================================================
//
// 
//
//==========================================================================

void BuildTiles::AddTile(int tilenum, FGameTexture* tex, bool permap)
{
	assert(!tex->GetID().isValid());	// must not be added yet.
	TexMan.AddGameTexture(tex);
	tiledata[tilenum].texture = tex;
	if (!permap) tiledata[tilenum].backup = tex;
}

//===========================================================================
//
// AddTiles
//
// Adds all the tiles in an artfile to the texture manager.
//
//===========================================================================

void BuildTiles::AddTiles (int firsttile, TArray<uint8_t>& RawData, const char *mapname)
{

	const uint8_t *tiles = RawData.Data();
//	int numtiles = LittleLong(((uint32_t *)tiles)[1]);	// This value is not reliable
	int tilestart = LittleLong(((int *)tiles)[2]);
	int tileend = LittleLong(((int *)tiles)[3]);
	const uint16_t *tilesizx = &((const uint16_t *)tiles)[8];
	const uint16_t *tilesizy = &tilesizx[tileend - tilestart + 1];
	const uint32_t *picanm = (const uint32_t *)&tilesizy[tileend - tilestart + 1];
	const uint8_t *tiledata = (const uint8_t *)&picanm[tileend - tilestart + 1];

	if (firsttile != -1)
	{
		tileend = tileend - tilestart + firsttile;
		tilestart = firsttile;
	}

	for (int i = tilestart; i <= tileend; ++i)
	{
		int pic = i - tilestart;
		int width = LittleShort(tilesizx[pic]);
		int height = LittleShort(tilesizy[pic]);
		uint32_t anm = LittleLong(picanm[pic]);
		int size = width*height;

		if (width <= 0 || height <= 0) continue;

		FString texname;
		if (mapname) texname.Format("maptile_%s_%05d", mapname, i);
		else texname.Format("#%05d", i);
		auto tex = GetTileTexture(texname, RawData, uint32_t(tiledata - tiles), width, height);
		AddTile(i, tex);
		int leftoffset, topoffset;
		this->tiledata[i].picanmbackup = this->tiledata[i].picanm = tileConvertAnimFormat(anm, &leftoffset, &topoffset);
		tex->SetOffsets(leftoffset, topoffset);

		tiledata += size;
	}
}

//===========================================================================
//
// Replacement textures
//
//===========================================================================

void BuildTiles::AddReplacement(int picnum, const HightileReplacement& replace)
{
	auto& Hightiles = tiledata[picnum].Hightiles;
	for (auto& ht : Hightiles)
	{
		if (replace.palnum == ht.palnum && (replace.faces[1] == nullptr) == (ht.faces[1] == nullptr))
		{
			ht = replace;
			return;
		}
	}
	Hightiles.Push(replace);
}

void BuildTiles::DeleteReplacement(int picnum, int palnum)
{
	auto& Hightiles = tiledata[picnum].Hightiles;
	for (int i = Hightiles.Size() - 1; i >= 0; i--)
	{
		if (Hightiles[i].palnum == palnum) Hightiles.Delete(i);
	}
}

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

HightileReplacement* BuildTiles::FindReplacement(int picnum, int palnum, bool skybox)
{
	auto& Hightiles = tiledata[picnum].Hightiles;
	for (;;)
	{
		for (auto& rep : Hightiles)
		{
			if (rep.palnum == palnum && (rep.faces[1] != nullptr) == skybox) return &rep;
		}
		if (!palnum || palnum >= MAXPALOOKUPS - RESERVEDPALS) break;
		palnum = 0;
	}
	return nullptr;	// no replacement found
}


//===========================================================================
//
// CountTiles
//
// Returns the number of tiles provided by an artfile
//
//===========================================================================

int CountTiles (const char *fn, const uint8_t *RawData)
{
	int version = LittleLong(*(uint32_t *)RawData);
	if (version != 1)
	{
		Printf("%s: Invalid art file version.  Must be 1, got %d\n", fn, version);
		return 0;
	}

	int tilestart = LittleLong(((uint32_t *)RawData)[2]);
	int tileend = LittleLong(((uint32_t *)RawData)[3]);

	if ((unsigned)tilestart >= MAXUSERTILES || (unsigned)tileend >= MAXUSERTILES)
	{
		Printf("%s: Invalid tilestart or tileend\n", fn);
		return 0;
	}
	if (tileend < tilestart)
	{
		Printf("%s: tileend < tilestart\n", fn);
		return 0;
	}

	return tileend >= tilestart ? tileend - tilestart + 1 : 0;
}

//===========================================================================
//
// InvalidateTile
//
//===========================================================================

void BuildTiles::InvalidateTile(int num)
{
	if ((unsigned) num < MAXTILES)
	{
		auto tex = tiledata[num].texture;
		tex->GetTexture()->SystemTextures.Clean();
		for (auto &rep : tiledata[num].Hightiles)
		{
			for (auto &reptex : rep.faces)
			{
				if (reptex) reptex->GetTexture()->SystemTextures.Clean();
			}
		}
		tiledata[num].rawCache.data.Clear();
	}
}

//===========================================================================
//
// MakeCanvas
//
// Turns texture into a canvas (i.e. camera texture)
//
//===========================================================================

void BuildTiles::MakeCanvas(int tilenum, int width, int height)
{
	auto canvas = ValidateCustomTile(tilenum, ReplacementType::Canvas);
	canvas->SetSize(width*4, height*4);
	canvas->SetDisplaySize(width, height);
	canvas->GetTexture()->SetSize(width * 4, height * 4);
	static_cast<FCanvasTexture*>(canvas->GetTexture())->aspectRatio = (float)width / height;
}

//===========================================================================
//
// LoadArtFile
//
// Returns the number of tiles found.
//
// let's load everything into memory on startup.
// Even for Ion Fury this will merely add 80 MB, because the engine already needs to cache the data, albeit in a compressed-per-lump form,
// so its 100MB art file will only have a partial impact on memory.
//
//===========================================================================

int BuildTiles::LoadArtFile(const char *fn, const char *mapname, int firsttile)
{
	auto old = FindFile(fn);
	if (old >= ArtFiles.Size())	// Do not process if already loaded.
	{
		FileReader fr = fileSystem.OpenFileReader(fn);
		if (fr.isOpen())
		{
			auto artdata = fr.Read();
			const uint8_t *artptr = artdata.Data();
			if (artdata.Size() > 16)
			{
				if (memcmp(artptr, "BUILDART", 8) == 0)
				{
					artdata.Delete(0, 8);
				}
				// Only load the data if the header is present
				if (CountTiles(fn, artptr) > 0)
				{
					auto file = new BuildArtFile;
					ArtFiles.Push(file);
					file->filename = fn;
					file->RawData = std::move(artdata);
					AddTiles(firsttile, file->RawData, mapname);
				}
			}
		}
		else
		{
			//Printf("%s: file not found\n", fn);
			return -1;
		}
	}
	return 0;
}

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

void BuildTiles::LoadArtSet(const char* filename)
{
	for (int index = 0; index < MAXARTFILES_BASE; index++)
	{
		FStringf fn(filename, index);
		LoadArtFile(fn, nullptr);
	}
	for (auto& addart : addedArt)
	{
		LoadArtFile(addart, nullptr);
	}
}


//==========================================================================
//
// Checks if a custom tile has alredy been added to the list.
// For each tile index there may only be one replacement and its
// type may never change!
//
// All these uses will need some review further down the line so that the texture manager's content is immutable.
//
//==========================================================================

FGameTexture* BuildTiles::ValidateCustomTile(int tilenum, ReplacementType type)
{
	if (tilenum < 0 || tilenum >= MAXTILES) return nullptr;
	auto &td = tiledata[tilenum];
	if (td.texture != td.backup) return nullptr;	// no mucking around with map tiles.
	auto tile = td.texture;
	auto reptype = td.replacement;
	if (reptype == type) return tile;		// already created
	if (reptype > ReplacementType::Art) return nullptr;		// different custom type - cannot replace again.
	FTexture* replacement = nullptr;
	td.replacement = type;
	if (type == ReplacementType::Writable)
	{
		// Creates an empty writable tile.
		// Current use cases are:
		// Camera textures (should be made to be creatable by the hardware renderer instead of falling back on the software renderer.)
		// thumbnails for savegame and loadgame (should bypass the texture manager entirely.)
		// view tilting in the software renderer (this should just use a local buffer instead of relying on the texture manager.)
		// Movie playback (like thumbnails this should bypass the texture manager entirely.)
		// Blood's 'lens' effect (apparently MP only) - combination of a camera texture with a distortion map - should be made a shader effect to be applied to the camera texture.
		replacement = new FImageTexture(new FWritableTile);
	}
	else if (type == ReplacementType::Restorable)
	{
		// This is for modifying an existing tile.
		// It only gets used for the crosshair and a few specific effects:
		// A) the fire in Blood.
		// B) the pin display in Redneck Rampage's bowling lanes.
		// C) Exhumed's menu plus one special effect tile.
		// All of these effects should probably be redone without actual texture hacking...
		if (tile->GetTexelWidth() == 0 || tile->GetTexelHeight() == 0) return nullptr;	// The base must have a size for this to work.
		// todo: invalidate hardware textures for tile.
		replacement = new FImageTexture(new FRestorableTile(tile->GetTexture()->GetImage()));
	}
	else if (type == ReplacementType::Canvas)
	{
		replacement = new FCanvasTexture(1, 1);
	}
	else return nullptr;
	auto rep = MakeGameTexture(replacement, tile->GetName(), ETextureType::Override);
	AddTile(tilenum, rep);
	return rep;
}

//==========================================================================
//
//  global interface
//
//==========================================================================

int32_t BuildTiles::artLoadFiles(const char* filename)
{
	TileFiles.LoadArtSet(filename);
	memset(gotpic, 0, sizeof(gotpic));
	return 0;
}

//==========================================================================
//
// Creates a tile for displaying custom content
//
//==========================================================================

uint8_t* BuildTiles::tileCreate(int tilenum, int width, int height)
{
	if (width <= 0 || height <= 0) return nullptr;
	auto tex = ValidateCustomTile(tilenum, ReplacementType::Writable);
	if (tex == nullptr) return nullptr;
	auto wtex = static_cast<FWritableTile*>(tex->GetTexture()->GetImage());
	if (!wtex->ResizeImage(width, height)) return nullptr;
	tex->SetSize(width, height);
	return wtex->GetRawData();
}

//==========================================================================
//
// Makes a tile writable - only used for a handful of special cases
// (todo: Investigate how to get rid of this)
//
//==========================================================================

uint8_t* BuildTiles::tileMakeWritable(int num)
{
	auto tex = ValidateCustomTile(num, ReplacementType::Restorable);
	auto wtex = static_cast<FWritableTile*>(tex->GetTexture()->GetImage());
	return wtex ? wtex->GetRawData() : nullptr;
}

//==========================================================================
//
// Processes data from .def files into the textures
//
//==========================================================================

void BuildTiles::PostLoadSetup()
{
	for (auto& tile : tiledata)
	{
		FGameTexture* detailTex = nullptr, * glowTex = nullptr, * normalTex = nullptr, *specTex = nullptr;
		float scalex = 1.f, scaley = 1.f;
		for (auto& rep : tile.Hightiles)
		{
			if (rep.palnum == GLOWPAL)
			{
				glowTex = rep.faces[0];
			}
			if (rep.palnum == NORMALPAL)
			{
				normalTex = rep.faces[0];
			}
			if (rep.palnum == SPECULARPAL)
			{
				specTex = rep.faces[0];
			}
			if (rep.palnum == DETAILPAL)
			{
				detailTex = rep.faces[0];
				scalex = rep.scale.X;
				scaley = rep.scale.Y;
			}
		}
		if (!detailTex && !glowTex && !normalTex && !specTex) continue; // if there's no layers there's nothing to do.
		for (auto& rep : tile.Hightiles)
		{
			if (rep.faces[1]) continue;	// do not muck around with skyboxes (yet)
			if (rep.palnum < NORMALPAL)
			{
				auto tex = rep.faces[0];
				// Make a copy so that multiple appearances of the same texture can be handled. They will all refer to the same internal texture anyway.
				tex = MakeGameTexture(tex->GetTexture(), "", ETextureType::Any);
				tex->SetGlowmap(glowTex->GetTexture());
				tex->SetDetailmap(detailTex->GetTexture());
				tex->SetNormalmap(normalTex->GetTexture());
				tex->SetSpecularmap(specTex->GetTexture());
				tex->SetDetailScale(scalex, scaley);
				rep.faces[0] = tex;
			}
		}
	}
}

//==========================================================================
//
// Returns checksum for a given tile
//
//==========================================================================

int32_t tileGetCRC32(int tileNum)
{
	if ((unsigned)tileNum >= (unsigned)MAXTILES) return 0;
	auto tile = dynamic_cast<FArtTile*>(TileFiles.tiledata[tileNum].texture->GetTexture()->GetImage());	// only consider original ART tiles.
	if (!tile) return 0;
	auto pixels = tile->GetRawData();
	if (!pixels) return 0;

	auto size = tile->GetWidth() * tile->GetHeight();
	if (size == 0) return 0;

	// Temporarily revert the data to its original form with 255 being transparent. Otherwise the CRC won't match.
	auto p = pixels;
	for (int i = 0; i < size; i++, p++)
	{
		// move transparent color to index 0 to get in line with the rest of the texture management.
		if (*p == 0) *p = 255;
		else if (*p == 255) *p = 0;
	}

	auto crc = crc32(0, (const Bytef*)pixels, size);

	// ... and back again.
	p = pixels;
	for (int i = 0; i < size; i++, p++)
	{
		// move transparent color to index 0 to get in line with the rest of the texture management.
		if (*p == 0) *p = 255;
		else if (*p == 255) *p = 0;
	}
	return crc;
}


//==========================================================================
//
// Import a tile from an external image.
// This has been signifcantly altered so it may not cover everything yet.
//
//==========================================================================

int tileImportFromTexture(const char* fn, int tilenum, int alphacut, int istexture)
{
	FTextureID texid = TexMan.CheckForTexture(fn, ETextureType::Any);
	if (!texid.isValid()) return -1;
	auto tex = TexMan.GetGameTexture(texid);

	int32_t xsiz = tex->GetTexelWidth(), ysiz = tex->GetTexelHeight();

	if (xsiz <= 0 || ysiz <= 0)
		return -2;

	TileFiles.tiledata[tilenum].texture = tex;
#pragma message("tileImportFromTexture needs rework!")	// Reminder so that this place isn't forgotten.
//#if 0
	// Does this make any difference when the texture gets *properly* inserted into the tile array? Answer: Yes, it affects how translations affect it.
	//if (istexture)
		tileSetHightileReplacement(tilenum, 0, fn, (float)(255 - alphacut) * (1.f / 255.f), 1.0f, 1.0f, 1.0, 1.0, 0);	// At the moment this is the only way to load the texture. The texture creation code is not ready yet for downconverting an image.
//#endif
	return 0;

}

//==========================================================================
//
// Copies a tile into another and optionally translates its palette.
//
//==========================================================================

void tileCopy(int tile, int source, int pal, int xoffset, int yoffset, int flags)
{
	// Todo. Since I do not know if some mod needs this it's of low priority now.
	// Let's get things working first.
	picanm_t* picanm = nullptr;
	picanm_t* sourceanm = nullptr;
	int srcxo, srcyo;
	FGameTexture* tex;

	if (pal == -1 && tile == source)
	{
		// Only modify the picanm info.
		tex = tileGetTexture(tile);
		if (!tex) return;
		picanm = &TileFiles.tiledata[tile].picanm;
		sourceanm = picanm;
		srcxo = tex->GetTexelLeftOffset(0);
		srcyo = tex->GetTexelTopOffset(0);
	}
	else
	{
		if (source == -1) source = tile;
		tex = tileGetTexture(source);
		if (!tex) return;
		sourceanm = &TileFiles.tiledata[source].picanm;
		srcxo = tex->GetTexelLeftOffset(0);
		srcyo = tex->GetTexelTopOffset(0);

		TArray<uint8_t> buffer = tex->GetTexture()->Get8BitPixels(false);

		if (pal != -1)
		{
			auto remap = lookups.getTable(pal);
			for (auto& pixel : buffer)
			{
				pixel = remap[pixel];
			}
		}
		tex = MakeGameTexture(new FImageTexture(new FLooseTile(buffer, tex->GetTexelWidth(), tex->GetTexelHeight())), "", ETextureType::Any);
		picanm = &TileFiles.tiledata[tile].picanm;
		TileFiles.AddTile(tile, tex);
	}

	if (xoffset != -1024) srcxo = clamp(xoffset, -128, 127);
	if (yoffset != -1024) srcyo = clamp(yoffset, -128, 127);
	tex->SetOffsets(srcxo, srcyo);
	picanm->sf = (picanm->sf & ~PICANM_MISC_MASK) | (sourceanm->sf & PICANM_MISC_MASK) | flags;
}

//==========================================================================
//
// Clear map specific ART
//
//==========================================================================

void artClearMapArt(void)
{
	for (auto& td : TileFiles.tiledata)
	{
		td.texture = td.backup;
		td.picanm = td.picanmbackup;
	}
	TileFiles.SetupReverseTileMap();
}

//==========================================================================
//
// Load map specficied ART
//
//==========================================================================
static FString currentMapArt;

void artSetupMapArt(const char* filename)
{
	if (currentMapArt.CompareNoCase(filename)) return;
	currentMapArt = filename;
	artClearMapArt();

	FString lcfilename = filename;
	lcfilename.MakeLower();

	// Re-get from the texture manager if this map's tiles have already been created.
	if (TileFiles.maptilesadded.Find(lcfilename) < TileFiles.maptilesadded.Size())
	{
		for (int i = 0; i < MAXTILES; i++)
		{
			FStringf name("maptile_%s_%05d", lcfilename.GetChars(), i);
			auto texid = TexMan.CheckForTexture(name, ETextureType::Any);
			if (texid.isValid())
			{
				TileFiles.tiledata[i].texture = TexMan.GetGameTexture(texid);
			}
		}
		return;
	}

	TileFiles.maptilesadded.Push(lcfilename);


	FStringf firstname("%s_00.art", lcfilename.GetChars());
	auto fr = fileSystem.OpenFileReader(firstname);
	if (!fr.isOpen()) return;
	for (auto& td : TileFiles.tiledata)
	{
		td.picanmbackup = td.picanm;
	}


	for (bssize_t i = 0; i < MAXARTFILES_TOTAL - MAXARTFILES_BASE; i++)
	{
		FStringf fullname("%s_%02d.art", filename, i);
		TileFiles.LoadArtFile(fullname, filename);
	}
	TileFiles.SetupReverseTileMap();
}

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

void tileDelete(int tile)
{
	TileFiles.TextureToTile.Remove(tileGetTexture(tile));
	TileFiles.tiledata[tile].texture = TileFiles.tiledata[tile].backup = TexMan.GameByIndex(0);
	vox_undefine(tile);
	md_undefinetile(tile);
	tileRemoveReplacement(tile);
}

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

void tileRemoveReplacement(int tile)
{
	if ((unsigned)tile >= MAXTILES) return;
	TileFiles.DeleteReplacements(tile);
}

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

void tileSetDummy(int tile, int width, int height)
{
	if (width == 0 || height == 0)
	{
		tileDelete(tile);
	}
	else if (width > 0 && height > 0)
	{
		FStringf texname("#%05d", tile);
		auto dtile = MakeGameTexture(new FImageTexture(new FDummyTile(width, height)), texname, ETextureType::Any);
		TileFiles.AddTile(tile, dtile);
	}
}

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

int BuildTiles::findUnusedTile(void)
{
	static int lastUnusedTile = MAXUSERTILES - 1;

	for (; lastUnusedTile >= 0; --lastUnusedTile)
	{
		auto tex = tileGetTexture(lastUnusedTile);
		if (!tex || !tex->isValid()) return lastUnusedTile;
	}
	return -1;
}

//==========================================================================
//
// fixme: This *really* needs to be done by rotating the texture coordinates,
// not by creating an entirely new texture.
// Unfortunately it's in all the wrong place in the rendering code so it
// has to wait for later.
//
//==========================================================================

int BuildTiles::tileCreateRotated(int tileNum)
{
	if ((unsigned)tileNum >= MAXTILES) return tileNum;
	auto tex = tileGetTexture(tileNum);
	if (!tex || tex->GetTexelWidth() <= 0 || tex->GetTexelHeight() <= 0) return tileNum;
	TArray<uint8_t> buffer = tex->GetTexture()->Get8BitPixels(false);
	TArray<uint8_t> dbuffer(tex->GetTexelWidth() * tex->GetTexelHeight(), true);

	auto src = buffer.Data();
	auto dst = dbuffer.Data();

	auto width = tex->GetTexelWidth();
	auto height = tex->GetTexelHeight();
	for (int x = 0; x < width; ++x)
	{
		int xofs = width - x - 1;
		int yofs = height * x;

		for (int y = 0; y < height; ++y)
			*(dst + y * width + xofs) = *(src + y + yofs);
	}

	auto dtex = MakeGameTexture(new FImageTexture(new FLooseTile(dbuffer, tex->GetTexelHeight(), tex->GetTexelWidth())), "", ETextureType::Override);
	int index = findUnusedTile();
	bool mapart = TileFiles.tiledata[tileNum].texture != TileFiles.tiledata[tileNum].backup;
	TileFiles.AddTile(index, dtex, mapart);
	return index;
}

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

void BuildTiles::CloseAll()
{
	ArtFiles.DeleteAndClear();
}

//==========================================================================
//
//   Specifies a replacement texture for an ART tile.
//
//==========================================================================

int tileSetHightileReplacement(int picnum, int palnum, const char* filename, float alphacut, float xscale, float yscale, float specpower, float specfactor, uint8_t flags)
{
	if ((uint32_t)picnum >= (uint32_t)MAXTILES) return -1;
	if ((uint32_t)palnum >= (uint32_t)MAXPALOOKUPS) return -1;

	auto tex = tileGetTexture(picnum);
	if (tex->GetTexelWidth() <= 0 || tex->GetTexelHeight() <= 0)
	{
		Printf("Warning: defined hightile replacement for empty tile %d.", picnum);
		return -1;	// cannot add replacements to empty tiles, must create one beforehand
	}
	HightileReplacement replace = {};

	FTextureID texid = TexMan.CheckForTexture(filename, ETextureType::Any);
	if (!texid.isValid()) 
	{
		Printf("%s: Replacement for tile %d does not exist or is invalid\n", filename, picnum);
		return -1;
	}

	replace.faces[0] = TexMan.GetGameTexture(texid);
	if (replace.faces[0] == nullptr)
    replace.alphacut = min(alphacut,1.f);
	replace.scale = { xscale, yscale };
	replace.specpower = specpower; // currently unused
	replace.specfactor = specfactor; // currently unused
	replace.flags = flags;
	replace.palnum = (uint16_t)palnum;
	TileFiles.AddReplacement(picnum, replace);
	return 0;
}


//==========================================================================
//
//  Define the faces of a skybox
//
//==========================================================================

int tileSetSkybox(int picnum, int palnum, const char **facenames, int flags )
{
    if ((uint32_t)picnum >= (uint32_t)MAXTILES) return -1;
    if ((uint32_t)palnum >= (uint32_t)MAXPALOOKUPS) return -1;

	auto tex = tileGetTexture(picnum);
	if (tex->GetTexelWidth() <= 0 || tex->GetTexelHeight() <= 0)
	{
		Printf("Warning: defined skybox replacement for empty tile %d.", picnum);
		return -1;	// cannot add replacements to empty tiles, must create one beforehand
	}
	HightileReplacement replace = {};
	
	for (auto &face : replace.faces)
	{
		FTextureID texid = TexMan.CheckForTexture(*facenames, ETextureType::Any);
		if (!texid.isValid())
		{
			Printf("%s: Skybox image for tile %d does not exist or is invalid\n", *facenames, picnum);
			return -1;
		}
		face = TexMan.GetGameTexture(texid);
	}
    replace.flags = flags;
	replace.palnum = (uint16_t)palnum;
	TileFiles.AddReplacement(picnum, replace);
	return 0;
}

//==========================================================================
//
//  Remove a replacement
//
//==========================================================================

int tileDeleteReplacement(int picnum, int palnum)
{
    if ((uint32_t)picnum >= (uint32_t)MAXTILES) return -1;
    if ((uint32_t)palnum >= (uint32_t)MAXPALOOKUPS) return -1;
	TileFiles.DeleteReplacement(picnum, palnum);
    return 0;
}


//==========================================================================
//
//  Copy a block of a tile.
//  Only used by RR's bowling lane.
//
//==========================================================================

void tileCopySection(int tilenum1, int sx1, int sy1, int xsiz, int ysiz, int tilenum2, int sx2, int sy2)
{
	int xsiz1 = tileWidth(tilenum1);
	int ysiz1 = tileHeight(tilenum1);
	int xsiz2 = tileWidth(tilenum2);
	int ysiz2 = tileHeight(tilenum2);
	if (xsiz1 > 0 && ysiz1 > 0 && xsiz2 > 0 && ysiz2 > 0)
	{
		auto p1 = tilePtr(tilenum1);
		auto p2 = tileData(tilenum2);
		if (p2 == nullptr) return;	// Error: Destination is not writable.
		
		int x1 = sx1;
		int x2 = sx2;
		for (int i=0; i<xsiz; i++)
		{
			int y1 = sy1;
			int y2 = sy2;
			for (int j=0; j<ysiz; j++)
			{
				if (x2 >= 0 && y2 >= 0 && x2 < xsiz2 && y2 < ysiz2)
				{
					auto src = p1[x1 * ysiz1 + y1];
					if (src != TRANSPARENT_INDEX)
						p2[x2 * ysiz2 + y2] = src;
				}
				
				y1++;
				y2++;
				if (y1 >= ysiz1) y1 = 0;
			}
			x1++;
			x2++;
			if (x1 >= xsiz1) x1 = 0;
		}
	}
	TileFiles.InvalidateTile(tilenum2);
}

//===========================================================================
// 
//	Picks a texture for rendering for a given tilenum/palette combination
//
//===========================================================================

// Test CVARs.
CVAR(Int, fixpalette, -1, 0)
CVAR(Int, fixpalswap, -1, 0)

bool PickTexture(int picnum, FGameTexture* tex, int paletteid, TexturePick& pick)
{
	if (!tex) tex = tileGetTexture(picnum);
	if (picnum == -1) picnum = TileFiles.GetTileIndex(tex);	// Allow getting replacements also when the texture is not passed by its tile number.

	if (!tex->isValid() || tex->GetTexelWidth() <= 0 || tex->GetTexelHeight() <= 0) return false;
	pick.texture = tex;
	int curbasepal = GetTranslationType(paletteid) - Translation_Remap;
	int palette = GetTranslationIndex(paletteid);
	int usepalette = fixpalette >= 0 ? fixpalette : curbasepal;
	int usepalswap = fixpalswap >= 0 ? fixpalswap : palette;
	int TextureType = hw_int_useindexedcolortextures && picnum >= 0 ? TT_INDEXED : TT_TRUECOLOR;

	pick.translation = TRANSLATION(usepalette + Translation_Remap, usepalswap);
	pick.basepalTint = 0xffffff;

	auto& h = lookups.tables[palette];
	bool applytint = false;
	// Canvas textures must be treated like hightile replacements in the following code.
	if (picnum < 0) picnum = TileFiles.GetTileIndex(tex);	// Allow getting replacements also when the texture is not passed by its tile number.
	auto rep = (picnum >= 0 && hw_hightile && !(h.tintFlags & TINTF_ALWAYSUSEART)) ? TileFiles.FindReplacement(picnum, palette) : nullptr;
	if (rep || tex->GetTexture()->isHardwareCanvas())
	{
		if (usepalette != 0)
		{
			// This is a global setting for the entire scene, so let's do it here, right at the start. (Fixme: Store this in a static table instead of reusing the same entry for all palettes.)
			auto& hh = lookups.tables[MAXPALOOKUPS - 1];
			// This sets a tinting color for global palettes, e.g. water or slime - only used for hires replacements (also an option for low-resource hardware where duplicating the textures may be problematic.)
			pick.basepalTint = hh.tintColor;
		}

		if (rep)
		{
			tex = rep->faces[0];
		}
		if (!rep || rep->palnum != palette || (h.tintFlags & TINTF_APPLYOVERALTPAL)) applytint = true;
		pick.translation = 0;
	}
	else
	{
		// Only look up the palette if we really want to use it (i.e. when creating a true color texture of an ART tile.)
		if (TextureType == TT_TRUECOLOR)
		{
			if (h.tintFlags & (TINTF_ALWAYSUSEART | TINTF_USEONART))
			{
				applytint = true;
				if (!(h.tintFlags & TINTF_APPLYOVERPALSWAP)) usepalswap = 0;
			}
			pick.translation = TRANSLATION(usepalette + Translation_Remap, usepalswap);
		}
		else pick.translation |= 0x80000000;
	}

	if (applytint && h.tintFlags)
	{
		pick.tintFlags = h.tintFlags;
		pick.tintColor = h.tintColor;
	}
	else
	{
		pick.tintFlags = -1;
		pick.tintColor = 0xffffff;
	}

	return true;
}



TileSiz tilesiz;
PicAnm picanm;