/**
 * @file gendung.cpp
 *
 * Implementation of general dungeon generation code.
 */
#include "gendung.h"

#include "engine/load_file.hpp"
#include "engine/random.hpp"
#include "init.h"
#include "lighting.h"
#include "options.h"

#include "datatable.h"
#include "../rhi/image.h"

namespace devilution {

uint8_t dungeon[DMAXX][DMAXY];
uint8_t pdungeon[DMAXX][DMAXY];
uint8_t dflags[DMAXX][DMAXY];
int setpc_x;
int setpc_y;
int setpc_w;
int setpc_h;
std::unique_ptr<uint16_t[]> pSetPiece;
bool setloadflag;
StormImage *pSpecialCels;
StormImage *pDungeonCels;
std::array<uint8_t, MAXTILES + 1> block_lvid;
std::array<bool, MAXTILES + 1> nBlockTable;
std::array<bool, MAXTILES + 1> nSolidTable;
std::array<bool, MAXTILES + 1> nTransTable;
std::array<bool, MAXTILES + 1> nMissileTable;
std::array<bool, MAXTILES + 1> nTrapTable;
int dminx;
int dminy;
int dmaxx;
int dmaxy;
dungeon_type leveltype;
BYTE currlevel;
bool setlevel;
_setlevels setlvlnum;
dungeon_type setlvltype;
int ViewX;
int ViewY;
ScrollStruct ScrollInfo;
int MicroTileLen;
char TransVal;
bool TransList[256];
int dPiece[MAXDUNX][MAXDUNY];
int dFloor[MAXDUNX][MAXDUNY];
MICROS dpiece_defs_map_2[MAXDUNX][MAXDUNY];
int8_t dTransVal[MAXDUNX][MAXDUNY];
char dLight[MAXDUNX][MAXDUNY];
char dPreLight[MAXDUNX][MAXDUNY];
int8_t dFlags[MAXDUNX][MAXDUNY];
int8_t dPlayer[MAXDUNX][MAXDUNY];
int16_t dMonster[MAXDUNX][MAXDUNY];
int8_t dDead[MAXDUNX][MAXDUNY];
char dObject[MAXDUNX][MAXDUNY];
int8_t dItem[MAXDUNX][MAXDUNY];
char dSpecial[MAXDUNX][MAXDUNY];
int themeCount;
THEME_LOC themeLoc[MAXTHEMES];

namespace {

bool WillThemeRoomFit(int floor, int x, int y, int minSize, int maxSize, int *width, int *height)
{
	bool yFlag = true;
	bool xFlag = true;
	int xCount = 0;
	int yCount = 0;

	if (x + maxSize > DMAXX && y + maxSize > DMAXY) {
		return false; // Original broken bounds check, avoids lower right corner
	}
	if (x + minSize > DMAXX || y + minSize > DMAXY) {
		return false; // Skip definit OOB cases
	}
	if (!SkipThemeRoom(x, y)) {
		return false;
	}

	int xArray[20] = {};
	int yArray[20] = {};

	for (int ii = 0; ii < maxSize; ii++) {
		if (xFlag && y + ii < DMAXY) {
			for (int xx = x; xx < x + maxSize && xx < DMAXX; xx++) {
				if (dungeon[xx][y + ii] != floor) {
					if (xx >= minSize) {
						break;
					}
					xFlag = false;
				} else {
					xCount++;
				}
			}
			if (xFlag) {
				xArray[ii] = xCount;
				xCount = 0;
			}
		}
		if (yFlag && x + ii < DMAXX) {
			for (int yy = y; yy < y + maxSize && yy < DMAXY; yy++) {
				if (dungeon[x + ii][yy] != floor) {
					if (yy >= minSize) {
						break;
					}
					yFlag = false;
				} else {
					yCount++;
				}
			}
			if (yFlag) {
				yArray[ii] = yCount;
				yCount = 0;
			}
		}
	}

	for (int ii = 0; ii < minSize; ii++) {
		if (xArray[ii] < minSize || yArray[ii] < minSize) {
			return false;
		}
	}

	int xSmallest = xArray[0];
	int ySmallest = yArray[0];

	for (int ii = 0; ii < maxSize; ii++) {
		if (xArray[ii] < minSize || yArray[ii] < minSize) {
			break;
		}
		if (xArray[ii] < xSmallest) {
			xSmallest = xArray[ii];
		}
		if (yArray[ii] < ySmallest) {
			ySmallest = yArray[ii];
		}
	}

	*width = xSmallest - 2;
	*height = ySmallest - 2;
	return true;
}

void CreateThemeRoom(int themeIndex)
{
	const int lx = themeLoc[themeIndex].x;
	const int ly = themeLoc[themeIndex].y;
	const int hx = lx + themeLoc[themeIndex].width;
	const int hy = ly + themeLoc[themeIndex].height;

	for (int yy = ly; yy < hy; yy++) {
		for (int xx = lx; xx < hx; xx++) {
			if (leveltype == DTYPE_CATACOMBS) {
				if (yy == ly || yy == hy - 1) {
					dungeon[xx][yy] = 2;
				} else if (xx == lx || xx == hx - 1) {
					dungeon[xx][yy] = 1;
				} else {
					dungeon[xx][yy] = 3;
				}
			}
			if (leveltype == DTYPE_CAVES) {
				if (yy == ly || yy == hy - 1) {
					dungeon[xx][yy] = 134;
				} else if (xx == lx || xx == hx - 1) {
					dungeon[xx][yy] = 137;
				} else {
					dungeon[xx][yy] = 7;
				}
			}
			if (leveltype == DTYPE_HELL) {
				if (yy == ly || yy == hy - 1) {
					dungeon[xx][yy] = 2;
				} else if (xx == lx || xx == hx - 1) {
					dungeon[xx][yy] = 1;
				} else {
					dungeon[xx][yy] = 6;
				}
			}
		}
	}

	if (leveltype == DTYPE_CATACOMBS) {
		dungeon[lx][ly] = 8;
		dungeon[hx - 1][ly] = 7;
		dungeon[lx][hy - 1] = 9;
		dungeon[hx - 1][hy - 1] = 6;
	}
	if (leveltype == DTYPE_CAVES) {
		dungeon[lx][ly] = 150;
		dungeon[hx - 1][ly] = 151;
		dungeon[lx][hy - 1] = 152;
		dungeon[hx - 1][hy - 1] = 138;
	}
	if (leveltype == DTYPE_HELL) {
		dungeon[lx][ly] = 9;
		dungeon[hx - 1][ly] = 16;
		dungeon[lx][hy - 1] = 15;
		dungeon[hx - 1][hy - 1] = 12;
	}

	if (leveltype == DTYPE_CATACOMBS) {
		switch (GenerateRnd(2)) {
		case 0:
			dungeon[hx - 1][(ly + hy) / 2] = 4;
			break;
		case 1:
			dungeon[(lx + hx) / 2][hy - 1] = 5;
			break;
		}
	}
	if (leveltype == DTYPE_CAVES) {
		switch (GenerateRnd(2)) {
		case 0:
			dungeon[hx - 1][(ly + hy) / 2] = 147;
			break;
		case 1:
			dungeon[(lx + hx) / 2][hy - 1] = 146;
			break;
		}
	}
	if (leveltype == DTYPE_HELL) {
		switch (GenerateRnd(2)) {
		case 0: {
			int yy = (ly + hy) / 2;
			dungeon[hx - 1][yy - 1] = 53;
			dungeon[hx - 1][yy] = 6;
			dungeon[hx - 1][yy + 1] = 52;
			dungeon[hx - 2][yy - 1] = 54;
		} break;
		case 1: {
			int xx = (lx + hx) / 2;
			dungeon[xx - 1][hy - 1] = 57;
			dungeon[xx][hy - 1] = 6;
			dungeon[xx + 1][hy - 1] = 56;
			dungeon[xx][hy - 2] = 59;
			dungeon[xx - 1][hy - 2] = 58;
		} break;
		}
	}
}

void FindTransparencyValues(int i, int j, int x, int y, int d, uint8_t floorID)
{
	if (dTransVal[x][y] != 0 || dungeon[i][j] != floorID) {
		if (d == 1) {
			dTransVal[x][y] = TransVal;
			dTransVal[x][y + 1] = TransVal;
		}
		if (d == 2) {
			dTransVal[x + 1][y] = TransVal;
			dTransVal[x + 1][y + 1] = TransVal;
		}
		if (d == 3) {
			dTransVal[x][y] = TransVal;
			dTransVal[x + 1][y] = TransVal;
		}
		if (d == 4) {
			dTransVal[x][y + 1] = TransVal;
			dTransVal[x + 1][y + 1] = TransVal;
		}
		if (d == 5) {
			dTransVal[x + 1][y + 1] = TransVal;
		}
		if (d == 6) {
			dTransVal[x][y + 1] = TransVal;
		}
		if (d == 7) {
			dTransVal[x + 1][y] = TransVal;
		}
		if (d == 8) {
			dTransVal[x][y] = TransVal;
		}
		return;
	}

	dTransVal[x][y] = TransVal;
	dTransVal[x + 1][y] = TransVal;
	dTransVal[x][y + 1] = TransVal;
	dTransVal[x + 1][y + 1] = TransVal;
	FindTransparencyValues(i + 1, j, x + 2, y, 1, floorID);
	FindTransparencyValues(i - 1, j, x - 2, y, 2, floorID);
	FindTransparencyValues(i, j + 1, x, y + 2, 3, floorID);
	FindTransparencyValues(i, j - 1, x, y - 2, 4, floorID);
	FindTransparencyValues(i - 1, j - 1, x - 2, y - 2, 5, floorID);
	FindTransparencyValues(i + 1, j - 1, x + 2, y - 2, 6, floorID);
	FindTransparencyValues(i - 1, j + 1, x - 2, y + 2, 7, floorID);
	FindTransparencyValues(i + 1, j + 1, x + 2, y + 2, 8, floorID);
}

} // namespace

void FillSolidBlockTbls()
{
	for (unsigned i = 0; i < pDungeonCels->solData.size(); i++) {
		int bv = pDungeonCels->solData[i];
		nSolidTable[i + 1] = (bv & 0x01) != 0;
		nBlockTable[i + 1] = (bv & 0x02) != 0;
		nMissileTable[i + 1] = (bv & 0x04) != 0;
		nTransTable[i + 1] = (bv & 0x08) != 0;
		nTrapTable[i + 1] = (bv & 0x80) != 0;
		block_lvid[i + 1] = (bv & 0x70) >> 4;
	}
}

void SetDungeonMicros()
{
	MicroTileLen = 10;
	int blocks = 10;

	if (leveltype == DTYPE_TOWN) {
		MicroTileLen = 16;
		blocks = 16;
	} else if (leveltype == DTYPE_HELL) {
		MicroTileLen = 12;
		blocks = 16;
	}

	for (int y = 0; y < MAXDUNY; y++) {
		for (int x = 0; x < MAXDUNX; x++) {
// jmarshall - remove min file
			int lv = dPiece[x][y];
			MICROS &micros = dpiece_defs_map_2[x][y];

			micros.mt.celid = lv;
// jmarshall end
		}
	}
}

void DRLG_InitTrans()
{
	memset(dTransVal, 0, sizeof(dTransVal));
	memset(TransList, 0, sizeof(TransList));
	TransVal = 1;
}

void DRLG_MRectTrans(int x1, int y1, int x2, int y2)
{
	x1 = 2 * x1 + 17;
	y1 = 2 * y1 + 17;
	x2 = 2 * x2 + 16;
	y2 = 2 * y2 + 16;

	for (int j = y1; j <= y2; j++) {
		for (int i = x1; i <= x2; i++) {
			dTransVal[i][j] = TransVal;
		}
	}

	TransVal++;
}

void DRLG_RectTrans(int x1, int y1, int x2, int y2)
{
	for (int j = y1; j <= y2; j++) {
		for (int i = x1; i <= x2; i++) {
			dTransVal[i][j] = TransVal;
		}
	}
	TransVal++;
}

void DRLG_CopyTrans(int sx, int sy, int dx, int dy)
{
	dTransVal[dx][dy] = dTransVal[sx][sy];
}

void DRLG_ListTrans(int num, BYTE *list)
{
	for (int i = 0; i < num; i++) {
		uint8_t x1 = *list++;
		uint8_t y1 = *list++;
		uint8_t x2 = *list++;
		uint8_t y2 = *list++;
		DRLG_RectTrans(x1, y1, x2, y2);
	}
}

void DRLG_AreaTrans(int num, BYTE *list)
{
	for (int i = 0; i < num; i++) {
		uint8_t x1 = *list++;
		uint8_t y1 = *list++;
		uint8_t x2 = *list++;
		uint8_t y2 = *list++;
		DRLG_RectTrans(x1, y1, x2, y2);
		TransVal--;
	}
	TransVal++;
}

void DRLG_InitSetPC()
{
	setpc_x = 0;
	setpc_y = 0;
	setpc_w = 0;
	setpc_h = 0;
}

void DRLG_SetPC()
{
	int w = 2 * setpc_w;
	int h = 2 * setpc_h;
	int x = 2 * setpc_x + 16;
	int y = 2 * setpc_y + 16;

	for (int j = 0; j < h; j++) {
		for (int i = 0; i < w; i++) {
			dFlags[i + x][j + y] |= BFLAG_POPULATED;
		}
	}
}

void Make_SetPC(int x, int y, int w, int h)
{
	int dw = 2 * w;
	int dh = 2 * h;
	int dx = 2 * x + 16;
	int dy = 2 * y + 16;

	for (int j = 0; j < dh; j++) {
		for (int i = 0; i < dw; i++) {
			dFlags[i + dx][j + dy] |= BFLAG_POPULATED;
		}
	}
}

void DRLG_PlaceThemeRooms(int minSize, int maxSize, int floor, int freq, bool rndSize)
{
	themeCount = 0;
	memset(themeLoc, 0, sizeof(*themeLoc));
	for (int j = 0; j < DMAXY; j++) {
		for (int i = 0; i < DMAXX; i++) {
			int themeW = 0;
			int themeH = 0;
			if (dungeon[i][j] == floor && GenerateRnd(freq) == 0 && WillThemeRoomFit(floor, i, j, minSize, maxSize, &themeW, &themeH)) {
				if (rndSize) {
					int min = minSize - 2;
					int max = maxSize - 2;
					themeW = min + GenerateRnd(GenerateRnd(themeW - min + 1));
					if (themeW < min || themeW > max)
						themeW = min;
					themeH = min + GenerateRnd(GenerateRnd(themeH - min + 1));
					if (themeH < min || themeH > max)
						themeH = min;
				}
				themeLoc[themeCount].x = i + 1;
				themeLoc[themeCount].y = j + 1;
				themeLoc[themeCount].width = themeW;
				themeLoc[themeCount].height = themeH;
				if (leveltype == DTYPE_CAVES)
					DRLG_RectTrans(2 * i + 20, 2 * j + 20, 2 * (i + themeW) + 15, 2 * (j + themeH) + 15);
				else
					DRLG_MRectTrans(i + 1, j + 1, i + themeW, j + themeH);
				themeLoc[themeCount].ttval = TransVal - 1;
				CreateThemeRoom(themeCount);
				themeCount++;
			}
		}
	}
}

void DRLG_HoldThemeRooms()
{
	for (int i = 0; i < themeCount; i++) {
		for (int y = themeLoc[i].y; y < themeLoc[i].y + themeLoc[i].height - 1; y++) {
			for (int x = themeLoc[i].x; x < themeLoc[i].x + themeLoc[i].width - 1; x++) {
				int xx = 2 * x + 16;
				int yy = 2 * y + 16;
				dFlags[xx][yy] |= BFLAG_POPULATED;
				dFlags[xx + 1][yy] |= BFLAG_POPULATED;
				dFlags[xx][yy + 1] |= BFLAG_POPULATED;
				dFlags[xx + 1][yy + 1] |= BFLAG_POPULATED;
			}
		}
	}
}

int globalCeilingTile = 0;

int DRLG_GetCeilingTile(void)
{
	return globalCeilingTile;
}

void DRLG_LPass3(int lv, int floor)
{
	globalCeilingTile = lv;

	memset(&dFloor[0], 0, sizeof(dFloor));

	MegaTile floorMega = pDungeonCels->megaTiles[floor];
	int floorv1 = SDL_SwapLE16(floorMega.micro1) + 1;
	int floorv2 = SDL_SwapLE16(floorMega.micro2) + 1;
	int floorv3 = SDL_SwapLE16(floorMega.micro3) + 1;
	int floorv4 = SDL_SwapLE16(floorMega.micro4) + 1;

	{
		MegaTile mega = pDungeonCels->megaTiles[lv];
		int v1 = SDL_SwapLE16(mega.micro1) + 1;
		int v2 = SDL_SwapLE16(mega.micro2) + 1;
		int v3 = SDL_SwapLE16(mega.micro3) + 1;
		int v4 = SDL_SwapLE16(mega.micro4) + 1;

		for (int j = 0; j < MAXDUNY; j += 2) {
			for (int i = 0; i < MAXDUNX; i += 2) {
				dPiece[i + 0][j + 0] = v1;
				dPiece[i + 1][j + 0] = v2;
				dPiece[i + 0][j + 1] = v3;
				dPiece[i + 1][j + 1] = v4;
			}
		}
	}

	int yy = 16;
	for (int j = 0; j < DMAXY; j++) {
		int xx = 16;
		for (int i = 0; i < DMAXX; i++) { // NOLINT(modernize-loop-convert)
			int v1 = 0;
			int v2 = 0;
			int v3 = 0;
			int v4 = 0;

			int tileId = dungeon[i][j] - 1;
			if (tileId >= 0) {
				MegaTile mega = pDungeonCels->megaTiles[tileId];
				v1 = SDL_SwapLE16(mega.micro1) + 1;
				v2 = SDL_SwapLE16(mega.micro2) + 1;
				v3 = SDL_SwapLE16(mega.micro3) + 1;
				v4 = SDL_SwapLE16(mega.micro4) + 1;
			}

			bool entireTileIsFloor = pDungeonCels->IsFloorTile(v1) && pDungeonCels->IsFloorTile(v2) && pDungeonCels->IsFloorTile(v3) && pDungeonCels->IsFloorTile(v4);

			dPiece[xx + 0][yy + 0] = v1;
			dPiece[xx + 1][yy + 0] = v2;
			dPiece[xx + 0][yy + 1] = v3;
			dPiece[xx + 1][yy + 1] = v4;

			if (leveltype == DTYPE_TOWN || entireTileIsFloor) {
				dFloor[xx + 0][yy + 0] = v1;
				dFloor[xx + 1][yy + 0] = v2;
				dFloor[xx + 0][yy + 1] = v3;
				dFloor[xx + 1][yy + 1] = v4;
			} else {
				if (!pDungeonCels->IsFloorTile(v1))
					dFloor[xx + 0][yy + 0] = 0;
				else
					dFloor[xx + 0][yy + 0] = floorv1;

				if (!pDungeonCels->IsFloorTile(v2))
					dFloor[xx + 1][yy + 0] = 0;
				else
					dFloor[xx + 1][yy + 0] = floorv2;

				if (!pDungeonCels->IsFloorTile(v3))
					dFloor[xx + 0][yy + 1] = 0;
				else
					dFloor[xx + 0][yy + 1] = floorv3;

				if (!pDungeonCels->IsFloorTile(v4))
					dFloor[xx + 1][yy + 1] = 0;
				else
					dFloor[xx + 1][yy + 1] = floorv4;
			}
			
			xx += 2;
		}
		yy += 2;
	}
}

void DRLG_Init_Globals()
{
	memset(dFlags, 0, sizeof(dFlags));
	memset(dPlayer, 0, sizeof(dPlayer));
	memset(dMonster, 0, sizeof(dMonster));
	memset(dDead, 0, sizeof(dDead));
	memset(dObject, 0, sizeof(dObject));
	memset(dItem, 0, sizeof(dItem));
	memset(dSpecial, 0, sizeof(dSpecial));
	int8_t c = DisableLighting ? 0 : 15;
	memset(dLight, c, sizeof(dLight));
}

bool SkipThemeRoom(int x, int y)
{
	for (int i = 0; i < themeCount; i++) {
		if (x >= themeLoc[i].x - 2 && x <= themeLoc[i].x + themeLoc[i].width + 2
		    && y >= themeLoc[i].y - 2 && y <= themeLoc[i].y + themeLoc[i].height + 2)
			return false;
	}

	return true;
}

void InitLevels()
{
	currlevel = 0;
	leveltype = DTYPE_TOWN;
	setlevel = false;
}

void FloodTransparencyValues(uint8_t floorID)
{
	int yy = 16;
	for (int j = 0; j < DMAXY; j++) {
		int xx = 16;
		for (int i = 0; i < DMAXX; i++) {
			if (dungeon[i][j] == floorID && dTransVal[xx][yy] == 0) {
				FindTransparencyValues(i, j, xx, yy, 0, floorID);
				TransVal++;
			}
			xx += 2;
		}
		yy += 2;
	}
}

} // namespace devilution
