/********************************************************************
	Minecraft: Pocket Edition - Decompilation Project
	Copyright (C) 2023 iProgramInCpp

	The following code is licensed under the BSD 1 clause license.
	SPDX-License-Identifier: BSD-1-Clause
 ********************************************************************/

#include "LevelRenderer.hpp"
#include "client/app/Minecraft.hpp"
#include "renderer/GL/GL.hpp"

#include "world/tile/LeafTile.hpp"
#include "world/tile/GrassTile.hpp"

LevelRenderer::LevelRenderer(Minecraft* pMC, Textures* pTexs)
{
	field_4 = -9999.0f;
	field_8 = -9999.0f;
	field_C = -9999.0f;
	field_10 = 0.0f;
	field_14 = 2;
	field_18 = 0;
	field_1C = 0;
	field_20 = 0;
	field_30 = 0;
	field_54 = 0;
	field_58 = 0;
	field_5C = 0;
	field_60 = 0;
	field_64 = 0;
	field_68 = 0;
	field_6C = 0;
	field_70 = 0;
	field_74 = 0;
	field_78 = 0;
	field_7C = 0;
	field_80 = 0;
	m_pLevel = nullptr;
	m_chunks = nullptr;
	field_98 = nullptr;
	m_chunksLength = 0;
	m_pTileRenderer = nullptr;
	field_A4 = 0;
	field_A8 = 0;
	field_AC = 0;
	field_B0 = 0;
	field_B8 = false;
	field_BC = -1;
	m_ticksSinceStart = 0;
	m_nBuffers = 26136;

	m_pMinecraft = pMC;
	m_pTextures = pTexs;

	m_pBuffers = new GLuint[m_nBuffers];
	xglGenBuffers(m_nBuffers, m_pBuffers);
	LOG_I("numBuffers: %d", m_nBuffers);

	xglGenBuffers(1, &m_skyBuffer);

	generateSky(); // inlined in the 0.1.0 demo
}

void LevelRenderer::generateSky()
{
	Tesselator& t = Tesselator::instance;
	t.begin();
	m_skyBufferCount = 0;

	//float m = 16.0f;
	int n = 4;
	int p = 128;

	for (int i = n * -p; i <= n * p; i += p)
	{
		for (int j = n * -p; j <= n * p; j += p)
		{
			t.vertex(float(i + 0.0f), 16.0f, float(j + 0.0f));
			t.vertex(float(i + p)   , 16.0f, float(j + 0.0f));
			t.vertex(float(i + p)   , 16.0f, float(j + p)   );
			t.vertex(float(i + 0.0f), 16.0f, float(j + p)   );

			m_skyBufferCount += 4;
		}
	}

	t.end(m_skyBuffer);
}

void LevelRenderer::deleteChunks()
{
	for (int i = 0; i < field_AC; i++)
	{
		for (int j = 0; j < field_A8; j++)
		{
			for (int k = 0; k < field_A4; k++)
			{
				int index = k + field_A4 * (j + field_A8 * i);
				delete m_chunks[index];
			}
		}
	}

	if (m_chunks)
		delete[] m_chunks;
	m_chunks = nullptr;

	if (field_98)
		delete[] field_98;
	field_98 = nullptr;
}

void LevelRenderer::cull(Culler* pCuller, float f)
{
	for (int i = 0; i < m_chunksLength; i++)
	{
		Chunk* pChunk = m_chunks[i];
		if (pChunk->isEmpty())
			continue;

		//@TODO: What does the shift do? (x % 4 == 0)?
		if (!pChunk->m_bVisible || !((i + field_30) << 28))
		{
			pChunk->cull(pCuller);
		}
	}

	field_30++;
}

void LevelRenderer::allChanged()
{
	deleteChunks();

	LeafTile* pLeaves = (LeafTile*)Tile::leaves;

	pLeaves->m_bTransparent = m_pMinecraft->getOptions()->m_bFancyGraphics;
	pLeaves->m_TextureFrame = !pLeaves->m_bTransparent + pLeaves->field_74;

	TileRenderer::m_bFancyGrass = m_pMinecraft->getOptions()->m_bFancyGrass;
	TileRenderer::m_bBiomeColors = m_pMinecraft->getOptions()->m_bBiomeColors;

	field_BC = m_pMinecraft->getOptions()->m_iViewDistance;

	int x1 = 64 << (3 - field_BC);
	if (x1 >= 400)
		x1 = 400;

	field_A4 = x1 / 16 + 1;
	field_AC = x1 / 16 + 1;
	field_A8 = 8;

	m_chunksLength = field_A8 * field_A4 * field_AC;
	LOG_I("chunksLength: %d", m_chunksLength);
	m_chunks = new Chunk* [m_chunksLength];
	field_98 = new Chunk* [m_chunksLength];

	field_6C = 0;
	field_70 = 0;
	field_74 = 0;

	field_88.clear();

	field_78 = field_A4;
	field_80 = field_AC;
	field_7C = field_A8;

	int x2 = 0, x3 = 0;

	for (int i = 0; i < field_A4; i++)
	{
		if (field_A8 <= 0)
			continue;

		for (int j = 0; j < field_A8; j++)
		{
			for (int k = 0; k < field_AC; k++)
			{
				int index = i + field_A4 * (j + field_A8 * k);

				Chunk* pChunk = new Chunk(m_pLevel, 16 * i, 16 * j, 16 * k, 16, x3 + field_B0, &m_pBuffers[x3]);

				if (field_B8)
					pChunk->field_50 = 0;

				pChunk->field_4E = false;
				pChunk->field_4D = true;
				pChunk->m_bVisible = true;
				pChunk->field_48 = x2++;
				pChunk->setDirty();
				m_chunks[index] = pChunk;
				field_98[index] = pChunk;

				x3 += 3;
				field_88.push_back(pChunk);
			}
		}
	}

	if (m_pLevel)
	{
		Mob* pMob = m_pMinecraft->m_pMobPersp;
		if (pMob)
		{
			resortChunks(Mth::floor(pMob->m_pos.x), Mth::floor(pMob->m_pos.y), Mth::floor(pMob->m_pos.z));

			std::sort(&field_98[0], &field_98[m_chunksLength], DistanceChunkSorter(pMob));
		}
	}

	field_14 = 2;
}

void LevelRenderer::resortChunks(int x, int y, int z)
{
	int field_A4; // r11
	int z1; // r2
	int field_AC; // r10
	int x2; // r0
	int x3; // r9
	int x5; // r3
	int x9; // r0
	int x10; // r0
	int field_78; // r3
	int x11; // r7
	int field_AC_1; // r5
	int field_A8; // r6
	int x12; // r0
	int x13; // r8
	int field_80; // r3
	int x14; // r5
	int field_A4_1; // r2
	int x15; // r6
	int x16; // r11
	int field_7C; // r3
	bool v25; // cc
	Chunk** m_chunks; // r3
	int x17; // r2
	bool bIsDirty; // r6
	int x6_1; // [sp+0h] [bp-58h]
	int x8_1; // [sp+4h] [bp-54h]
	int x1; // [sp+8h] [bp-50h]
	int x10_1; // [sp+Ch] [bp-4Ch]
	int x4; // [sp+10h] [bp-48h]
	int x7; // [sp+1Ch] [bp-3Ch]
	int x6; // [sp+20h] [bp-38h]
	int x8; // [sp+24h] [bp-34h]
	Chunk* pChunk; // [sp+2Ch] [bp-2Ch] BYREF

	field_A4 = this->field_A4;
	z1 = 8 - z;
	this->field_6C = 0x7FFFFFFF;
	this->field_70 = 0x7FFFFFFF;
	this->field_74 = 0x7FFFFFFF;
	this->field_78 = 0x80000000;
	this->field_7C = 0x80000000;
	this->field_80 = 0x80000000;
	if (field_A4 > 0)
	{
		x1 = 16 * field_A4;
		field_AC = this->field_AC;
		x2 = (16 * field_A4) >> 1;
		x3 = 0;
		x4 = x2 + 8 - x;
		x5 = 1 - 16 * field_A4;
		x6 = z1 + x2;
		x7 = x5 + x4;
		x8 = z1 + x2 + x5;
		while (1)
		{
			x9 = x4 & ~(x4 >> 31);
			if (x4 < 0)
				x9 = x7;
			x10 = 16 * x3 - x1 * (x9 / x1);
			field_78 = this->field_78;
			x10_1 = x10;
			if (x10 < this->field_6C)
				this->field_6C = x10;
			if (x10 > field_78)
				this->field_78 = x10;
			if (field_AC > 0)
				break;
		LABEL_32:
			++x3;
			x4 += 16;
			x7 += 16;
			if (field_A4 <= x3)
				return;
		}
		x11 = 0;
		field_AC_1 = field_AC;
		field_A8 = this->field_A8;
		x6_1 = x6;
		x8_1 = x8;
		while (1)
		{
			x12 = x6_1 & ~(x6_1 >> 31);
			if (x6_1 < 0)
				x12 = x8_1;
			x13 = 16 * x11 - x1 * (x12 / x1);
			field_80 = this->field_80;
			if (x13 < this->field_74)
				this->field_74 = x13;
			if (x13 > field_80)
				this->field_80 = x13;
			if (field_A8 > 0)
				break;
		LABEL_30:
			++x11;
			x6_1 += 16;
			x8_1 += 16;
			if (field_AC_1 <= x11)
			{
				field_AC = field_AC_1;
				goto LABEL_32;
			}
		}
		x14 = 0;
		for (field_A4_1 = field_A4; ; field_A4_1 = this->field_A4)
		{
			x15 = x14 + field_A8 * x11;
			x16 = 16 * x14;
			field_7C = this->field_7C;
			if (this->field_70 > 16 * x14)
				this->field_70 = x16;
			v25 = field_7C < x16;
			m_chunks = this->m_chunks;
			x17 = x3 + field_A4_1 * x15;
			if (v25)
				this->field_7C = x16;
			pChunk = m_chunks[x17];
			bIsDirty = pChunk->isDirty();
			pChunk->Chunk::setPos(x10_1, 16 * x14, x13);
			if (bIsDirty || !pChunk->isDirty())
				goto LABEL_19;

			if (true) // need to expand
				break;

			++x14;

			field_A8 = this->field_A8;

			if (field_A8 <= x14)
			{
			LABEL_29:
				field_A4 = this->field_A4;
				field_AC_1 = this->field_AC;
				goto LABEL_30;
			}
		LABEL_20:
			;
		}
		this->field_88.push_back(pChunk);
	LABEL_19:
		field_A8 = this->field_A8;
		if (field_A8 <= ++x14)
			goto LABEL_29;
		goto LABEL_20;
	}
}

void LevelRenderer::entityAdded(Entity* pEnt)
{
	// TODO
}

std::string LevelRenderer::gatherStats1()
{
	//@NOTE: This data is based on the Java Edition pre-1.8 legend. This may not be accurate, but it's a good guideline.
	//See https://minecraft.fandom.com/wiki/Debug_screen#Pre-1.8_legend
	std::stringstream ss;
	ss  << "C: " << field_60 << "/" << field_54 // Number of chunk sections rendered over total number of chunks.
		<< ". F: " << field_58 // Number of chunk sections loaded outside the viewing distance.
		<< ", O: " << field_5C // Number of occluded chunk sections.
		<< ", E: " << field_64 // Number of empty chunk sections.
		<< "\n";

	return ss.str();
}

void LevelRenderer::onGraphicsReset()
{
	xglGenBuffers(m_nBuffers, m_pBuffers);
	allChanged();

	xglGenBuffers(1, &m_skyBuffer);
	generateSky(); // inlined in the 0.1.0 demo
}

void LevelRenderer::render(const AABB& aabb) const
{
	Tesselator& t = Tesselator::instance;

	t.begin(GL_LINE_STRIP);
	t.vertex(aabb.min.x, aabb.min.y, aabb.min.z);
	t.vertex(aabb.max.x, aabb.min.y, aabb.min.z);
	t.vertex(aabb.max.x, aabb.min.y, aabb.max.z);
	t.vertex(aabb.min.x, aabb.min.y, aabb.max.z);
	t.vertex(aabb.min.x, aabb.min.y, aabb.min.z);
	t.draw();
	t.begin(GL_LINE_STRIP);
	t.vertex(aabb.min.x, aabb.max.y, aabb.min.z);
	t.vertex(aabb.max.x, aabb.max.y, aabb.min.z);
	t.vertex(aabb.max.x, aabb.max.y, aabb.max.z);
	t.vertex(aabb.min.x, aabb.max.y, aabb.max.z);
	t.vertex(aabb.min.x, aabb.max.y, aabb.min.z);
	t.draw();
	t.begin(GL_LINES);
	t.vertex(aabb.min.x, aabb.min.y, aabb.min.z);
	t.vertex(aabb.min.x, aabb.max.y, aabb.min.z);
	t.vertex(aabb.max.x, aabb.min.y, aabb.min.z);
	t.vertex(aabb.max.x, aabb.max.y, aabb.min.z);
	t.vertex(aabb.max.x, aabb.min.y, aabb.max.z);
	t.vertex(aabb.max.x, aabb.max.y, aabb.max.z);
	t.vertex(aabb.min.x, aabb.min.y, aabb.max.z);
	t.vertex(aabb.min.x, aabb.max.y, aabb.max.z);
	t.draw();
}

void LevelRenderer::checkQueryResults(int a, int b)
{
}

void LevelRenderer::renderSameAsLast(int a, float b)
{
	m_renderList.render();
}

int LevelRenderer::renderChunks(int start, int end, int a, float b)
{
	field_24.clear();

	int result = 0;
	for (int i = start; i < end; i++)
	{
		Chunk* pChunk = field_98[i];
		if (!a)
		{
			field_54++;
			if (pChunk->field_1C[0])
			{
				field_64++;
			}
			else if (pChunk->m_bVisible)
			{
				if (!field_B8 || pChunk->field_4D)
					field_60++;
				else
					field_5C++;
			}
			else
			{
				field_58++;
			}
		}

		if (!pChunk->field_1C[a] && pChunk->m_bVisible && pChunk->field_4D && pChunk->getList(a) >= 0)
		{
			result++;
			field_24.push_back(pChunk);
		}
	}

	Mob* pMob = m_pMinecraft->m_pMobPersp;

	float fPosX = pMob->field_98.x + (pMob->m_pos.x - pMob->field_98.x) * b;
	float fPosY = pMob->field_98.y + (pMob->m_pos.y - pMob->field_98.y) * b;
	float fPosZ = pMob->field_98.z + (pMob->m_pos.z - pMob->field_98.z) * b;

	m_renderList.clear();
	m_renderList.init(fPosX, fPosY, fPosZ);

	for (int i = 0; i < int(field_24.size()); i++)
	{
		Chunk* pChk = field_24[i];
		m_renderList.addR(*pChk->getRenderChunk(a));
		m_renderList.field_14++;
	}

	renderSameAsLast(a, b);
	return result;
}

void LevelRenderer::render(Mob* pMob, int a, float b)
{
	for (int i = 0; i < 10; i++)
	{
		field_68 = (field_68 + 1) % m_chunksLength;
		Chunk* pChunk = m_chunks[field_68];

		if (!pChunk->m_bDirty)
			continue;

		std::vector<Chunk*>::iterator iter = std::find(field_88.begin(), field_88.end(), pChunk);
		if (iter != field_88.end())
			continue;

		field_88.push_back(pChunk);
	}

	if (m_pMinecraft->getOptions()->m_iViewDistance != field_BC)
		allChanged();

	if (!a)
		field_54 = field_58 = field_5C = field_60 = field_64 = 0;

	//float mobX1 = pMob->m_pos.x;
	float mobX2 = pMob->field_98.x + (pMob->m_pos.x - pMob->field_98.x) * b;
	//float mobY1 = pMob->m_pos.y;
	float mobY2 = pMob->field_98.y + (pMob->m_pos.y - pMob->field_98.y) * b;
	//float mobZ1 = pMob->m_pos.z;
	float mobZ2 = pMob->field_98.z + (pMob->m_pos.z - pMob->field_98.z) * b;

	float dX = pMob->m_pos.x - field_4, dY = pMob->m_pos.y - field_8, dZ = pMob->m_pos.z - field_C;

	if (dX * dX + dY * dY + dZ * dZ > 16.0f)
	{
		field_4 = pMob->m_pos.x;
		field_8 = pMob->m_pos.y;
		field_C = pMob->m_pos.z;

		resortChunks(Mth::floor(pMob->m_pos.x), Mth::floor(pMob->m_pos.y), Mth::floor(pMob->m_pos.z));
		std::sort(&field_98[0], &field_98[m_chunksLength], DistanceChunkSorter(pMob));
	}

	// @TODO: Fix goto hell

	// @NOTE: Field_B8 doesn't appear to be used??
	if (field_B8 && !a && !m_pMinecraft->getOptions()->m_bAnaglyphs)
	{
		checkQueryResults(0, 16);

		// @HUH: why 16?
		for (int i = 0; i < 16; i++)
		{
			field_98[i]->field_4D = true;
		}

		int x1 = renderChunks(0, 16, 0, b);
		int x2 = 16, x3;

		while (true)
		{
			x3 = 2 * x2;
			if (x3 >= m_chunksLength)
				x3 = m_chunksLength;

			glDisable(GL_TEXTURE_2D);
			glDisable(GL_ALPHA_TEST);
			glDisable(GL_FOG);
			glColorMask(false, false, false, false);
			glDepthMask(false);
			if (x2 < x3)
				break;
		label_37:
			glDepthMask(true);
			glColorMask(true, true, true, true);
			glEnable(GL_TEXTURE_2D);
			glEnable(GL_ALPHA_TEST);
			glEnable(GL_FOG);

			int res = renderChunks(x2, x3, 0, b);
			x1 += res;
			if (x3 >= m_chunksLength)
				return;
			x2 = x3;
		}

		float y1 = 0.0f;
		int y2 = x2;
		int y3 = x2;
		float y4 = 0.0f, y5 = 0.0f;

		while (true)
		{
			while (!field_98[y2]->isEmpty())
			{
				Chunk* pChunk = field_98[y2];

				if (!pChunk->m_bVisible)
				{
					pChunk->field_4D = true;
					goto label_26;
				}

				if (pChunk->field_4E)
					goto label_26;

				float y6 = pChunk->distanceToSqr(pMob);
				int y7 = int(Mth::sqrt(y6) / 128.0f + 1.0f);

				if (m_ticksSinceStart % y7 != y3 % y7)
					goto label_26;

				float fXdiff, fYdiff, fZdiff;
				fXdiff = float(pChunk->m_pos.x) - mobX2 - y5;
				fYdiff = float(pChunk->m_pos.y) - mobY2 - y4;
				fZdiff = float(pChunk->m_pos.z) - mobZ2 - y1;

				if (fXdiff != 0.0f || fYdiff != 0.0f || fZdiff != 0.0f)
				{
					y5 += fXdiff;
					y4 += fYdiff;
					y1 += fZdiff;
					glTranslatef(fXdiff, fYdiff, fZdiff);
				}

				pChunk->renderBB();

				y3++;
				y2++;
				//pChunk->field_4E++;
				pChunk->field_4E = true;
				if (y3 == x3)
					goto label_37;
			}

			field_98[y2]->m_bVisible = 0;
		label_26:
			y3++;
			y2++;

			if (y3 == x3)
				goto label_37;

			continue;
		}
	}

	renderChunks(0, m_chunksLength, a, b);
}

void LevelRenderer::setLevel(Level* level)
{
	if (m_pLevel)
		m_pLevel->removeListener(this);

	field_4 = -9999.0f;
	field_8 = -9999.0f;
	field_C = -9999.0f;

	EntityRenderDispatcher::getInstance()->setLevel(level);
	EntityRenderDispatcher::getInstance()->setMinecraft(m_pMinecraft);

	m_pLevel = level;

	delete m_pTileRenderer;
	m_pTileRenderer = new TileRenderer(m_pLevel);

	if (level)
	{
		level->addListener(this);
		allChanged();
	}
}

void LevelRenderer::setDirty(int x1, int y1, int z1, int x2, int y2, int z2)
{
	int x1c = Mth::intFloorDiv(x1, 16);
	int y1c = Mth::intFloorDiv(y1, 16);
	int z1c = Mth::intFloorDiv(z1, 16);
	int x2c = Mth::intFloorDiv(x2, 16);
	int y2c = Mth::intFloorDiv(y2, 16);
	int z2c = Mth::intFloorDiv(z2, 16);

	for (int x = x1c; x <= x2c; x++)
	{
		int x1 = x % field_A4;
		if (x1 < 0)
			x1 += field_A4;

		for (int y = y1c; y <= y2c; y++)
		{
			int y1 = y % field_A8;
			if (y1 < 0)
				y1 += field_A8;

			for (int z = z1c; z <= z2c; z++)
			{
				int z1 = z % field_AC;
				if (z1 < 0)
					z1 += field_AC;

				Chunk* pChunk = m_chunks[x1 + field_A4 * (y1 + field_A8 * z1)];
				if (pChunk->isDirty())
					continue;

				field_88.push_back(pChunk);
				pChunk->setDirty();
			}
		}
	}
}

void LevelRenderer::setTilesDirty(int x1, int y1, int z1, int x2, int y2, int z2)
{
	setDirty(x1 - 1, y1 - 1, z1 - 1, x2 + 1, y2 + 1, z2 + 1);
}

void LevelRenderer::tick()
{
	m_ticksSinceStart++;
}

/*
void LevelRenderer::updateDirtyChunks(Mob* pMob, bool b)
{
	// @TODO This updates 16 chunks per frame. Not good.

	int updated = 0;
	for (int i = 0; i < 16 && i < int(field_88.size()); i++)
	{
		Chunk* pChk = field_88[i];
		pChk->rebuild();
		pChk->setClean();
		updated++;
	}

	field_88.erase(field_88.begin(), field_88.begin() + updated);
}
*/

typedef std::vector<Chunk*> ChunkVector;
typedef ChunkVector::iterator ChunkVectorIterator;

bool LevelRenderer::updateDirtyChunks(Mob* pMob, bool b)
{
	// @TODO: untangle this thing

	int v3; // r4
	ChunkVectorIterator field_88_Beg; // r3
	size_t size; // r9
	int v8; // r11
	Chunk* v9; // r0
	ChunkVector* xvec; // r5
	int v11; // r7
	ChunkVectorIterator v12; // r1
	int v13; // r7
	Chunk* v14; // r1
	bool v15; // r0
	int v16; // r3
	int v17; // r7
	int v18; // r2
	ChunkVectorIterator v19; // r1
	ChunkVectorIterator v20; // r0
	size_t v21; // r8
	size_t v22; // r4
	ChunkVector* v23; // r10
	size_t v24; // r8
	Chunk* v25; // r5
	int v26; // r4
	int v27; // r8
	Chunk* v28; // r5
	bool v29; // r3
	ChunkVectorIterator v31; // r1
	size_t v32; // r5
	int v33; // r0
	int v34; // r3
	Chunk* v35; // r2
	Chunk** v38; // r3
	Chunk* v39; // r2
	ChunkVector* v40; // r0
	Chunk* v42[3]; // [sp+1Ch] [bp+0h] BYREF
	Chunk* a3; // [sp+28h] [bp+Ch] BYREF
	Entity* pMob_1; // [sp+2Ch] [bp+10h] BYREF

	v3 = 0;
	pMob_1 = pMob;
	DirtyChunkSorter dcs(pMob);
	memset(v42, 0, sizeof v42);
	field_88_Beg = this->field_88.begin();
	size = this->field_88.end() - field_88_Beg;
	if (size <= 0)
	{
		v8 = 0;
		goto LABEL_28;
	}
	v8 = 0;
	v9 = *field_88_Beg;
	xvec = 0;
	v11 = 0;
	a3 = *field_88_Beg;
	if (!b)
		goto LABEL_11;
	while (1)
	{
		if (!v9->m_bVisible)
			goto LABEL_9;
	LABEL_5:
		if (!xvec)
		{
			++v8;
			v40 = new ChunkVector;
			v12 = v40->end();
			xvec = v40;
		LABEL_55:
			xvec->insert(v12, a3);
			goto LABEL_8;
		}
		v12 = xvec->end();
		++v8;
		if (true) // (v12 == xvec->capacity)
			goto LABEL_55;
		xvec->insert(v12, a3);
	LABEL_8:
		field_88[v11] = 0;
	LABEL_9:

		if (++v3 == size)
			break;
		while (1)
		{
			v11 = v3;
			v9 = field_88[v3];
			a3 = v9;
			if (b)
				break;
		LABEL_11:
			if (v9->distanceToSqr(pMob) <= 1024.0f)
				goto LABEL_5;
			v13 = b;
			while (1)
			{
				v14 = v42[v13];
				if (v14)
				{
					v15 = dcs(v14, a3);
					v16 = v13;
					if (!v15)
						break;
				}
				if (++v13 == 3)
				{
					v17 = 2;
					v18 = 1;
					v16 = 3;
					goto LABEL_51;
				}
			}
			v17 = v13 - 1;
			if (v17 <= 0)
				goto LABEL_9;
			v18 = v17 - 1;
			if (v17 == 1)
			{
				v42[1] = a3;
				goto LABEL_18;
			}
		LABEL_51:
			v38 = &v42[v16];
			v39 = v42[v18];
			do
			{
				*(v38 - 3) = v39;
				--v38;
			} while (v38 != &v42[2]);
			v42[v17] = a3;
		LABEL_18:
			if (++v3 == size)
				goto LABEL_19;
		}
	}
LABEL_19:
	if (xvec)
	{
		v19 = xvec->end();
		v20 = xvec->begin();
		v21 = v19 - xvec->begin();
		if (v21 > 1)
		{
			std::sort(v20, v19, dcs);
			v20 = xvec->begin();
			v21 = xvec->end() - xvec->begin();
		}
		v22 = v21 - 1;
		if ((int)(v21 - 1) >= 0)
		{
			v23 = xvec;
			v24 = v21 - 1;// v21 + 0x3FFFFFFF;
			while (1)
			{
				v25 = v20[v24--];
				v25->rebuild();
				v25->setClean();
				if ((--v22 & 0x80000000) != 0)
					break;
				v20 = v23->begin();
			}
			xvec = v23;
		}

		delete xvec;
	}
LABEL_28:
	v26 = 2;
	v27 = 0;
	while (2)
	{
		v28 = v42[v26];
		if (!v28)
		{
		LABEL_33:
			if (v26-- == 0)
				goto LABEL_34;
			continue;
		}
		break;
	}
	v29 = v28->m_bVisible;
	if (v28->m_bVisible || v26 == 2)
	{
		++v27;
		v42[v26]->rebuild();
		v28->setClean();
		goto LABEL_33;
	}
	v42[v26] = (Chunk*)v29;
	v42[0] = (Chunk*)v29;
LABEL_34:
	v31 = this->field_88.begin();
	v32 = this->field_88.end() - v31;
	if (v32)
	{
		v33 = 0;
		v34 = 0;
		while (1)
		{
			v35 = v31[v34];
			if (v35 && v42[0] != v35 && v42[1] != v35 && v42[2] != v35)
			{
				if (v33 != v34)
					v31[v33] = v35;
				++v33;
			}
			if (++v34 == v32)
				break;
			v31 = this->field_88.begin();
		}
		if (v34 > v33)
		{
			field_88.resize(v33);
		}
	}
	return v27 + v8 == size;
}

void LevelRenderer::renderHit(Player* pPlayer, const HitResult& hr, int i, void* vp, float f)
{
	glEnable(GL_BLEND);
	glEnable(GL_ALPHA_TEST);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE);

	// @BUG: possible leftover from Minecraft Classic? This is overridden anyways
	glColor4f(1.0f, 1.0f, 1.0f, 0.5f * (0.4f + 0.2f * Mth::sin(float(getTimeMs()) / 100.0f)));

	if (!i && field_10 > 0.0f)
	{
		glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);

		m_pTextures->loadAndBindTexture(C_TERRAIN_NAME);
		glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
		glPushMatrix();
		Tile* pTile = nullptr;
		TileID tile = m_pLevel->getTile(hr.m_tileX, hr.m_tileY, hr.m_tileZ);
		if (tile > 0)
			pTile = Tile::tiles[tile];
		glDisable(GL_ALPHA_TEST);
		glPolygonOffset(-3.0f, -3.0f);
		glEnable(GL_POLYGON_OFFSET_FILL);

		float px = pPlayer->field_98.x + (pPlayer->m_pos.x - pPlayer->field_98.x) * f;
		float py = pPlayer->field_98.y + (pPlayer->m_pos.y - pPlayer->field_98.y) * f;
		float pz = pPlayer->field_98.z + (pPlayer->m_pos.z - pPlayer->field_98.z) * f;

		Tesselator& t = Tesselator::instance;
		t.begin();
		t.offset(-px, -py, -pz);
		t.noColor();
		if (!pTile)
			pTile = Tile::rock;

		m_pTileRenderer->tesselateInWorld(pTile, hr.m_tileX, hr.m_tileY, hr.m_tileZ, 240 + int(field_10 * 10.0f));

		t.draw();
		t.offset(0, 0, 0);

		glPolygonOffset(0.0f, 0.0f);
		glDisable(GL_POLYGON_OFFSET_FILL);

		glDepthMask(true); //@HUH: What is the reason for this? You never disable the depth mask.
		glPopMatrix();
	}

	glDisable(GL_BLEND);
	glDisable(GL_ALPHA_TEST);
}

void LevelRenderer::renderHitSelect(Player* pPlayer, const HitResult& hr, int i, void* vp, float f)
{
	if (i) return;

	glEnable(GL_BLEND);
	glDisable(GL_TEXTURE_2D);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);
	glEnable(GL_DEPTH_TEST);

	m_pMinecraft->m_pTextures->loadAndBindTexture(C_TERRAIN_NAME);

	Tile* pTile = nullptr;
	TileID tileID = m_pLevel->getTile(hr.m_tileX, hr.m_tileY, hr.m_tileZ);
	if (tileID > 0)
		pTile = Tile::tiles[tileID];

	glDisable(GL_ALPHA_TEST);
	glColor4f(0.65f, 0.65f, 0.65f, 0.65f);
	glPushMatrix();
	glPolygonOffset(-0.3f, -0.3f);
	glEnable(GL_POLYGON_OFFSET_FILL);

	float px = pPlayer->field_98.x + (pPlayer->m_pos.x - pPlayer->field_98.x) * f;
	float py = pPlayer->field_98.y + (pPlayer->m_pos.y - pPlayer->field_98.y) * f;
	float pz = pPlayer->field_98.z + (pPlayer->m_pos.z - pPlayer->field_98.z) * f;

	Tesselator& t = Tesselator::instance;
	t.begin();
	t.offset(-px, -py, -pz);
	t.noColor();
	if (!pTile)
		pTile = Tile::rock;

	m_pTileRenderer->tesselateInWorld(pTile, hr.m_tileX, hr.m_tileY, hr.m_tileZ);

	t.draw();
	t.offset(0, 0, 0);

	glPolygonOffset(0.0f, 0.0f);
	glDisable(GL_POLYGON_OFFSET_FILL);
	glEnable(GL_TEXTURE_2D);
	glDepthMask(true);
	glPopMatrix();
	glEnable(GL_ALPHA_TEST);
	glDisable(GL_BLEND);
}

void LevelRenderer::renderHitOutline(Player* pPlayer, const HitResult& hr, int i, void* vp, float f)
{
	if (i != 0 || hr.m_hitType != 0)
		return;

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glColor4f(0.0f, 0.0f, 0.0f, 0.4f);
	//glLineWidth(1.0f);
	glDisable(GL_TEXTURE_2D);
	glDepthMask(false);

	// Maximize Line Width
	glEnable(GL_LINE_SMOOTH);
	
	float range[2];
	glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, range);

	float lineWidth = 2.0f;
	if (lineWidth > range[1])
		lineWidth = range[1];

	glLineWidth(lineWidth);

	TileID tile = m_pLevel->getTile(hr.m_tileX, hr.m_tileY, hr.m_tileZ);
	if (tile > 0)
	{
		Tile::tiles[tile]->updateShape(
			m_pLevel,
			hr.m_tileX,
			hr.m_tileY,
			hr.m_tileZ);
		float posX = pPlayer->field_98.x + ((pPlayer->m_pos.x - pPlayer->field_98.x) * f);
		float posY = pPlayer->field_98.y + ((pPlayer->m_pos.y - pPlayer->field_98.y) * f);
		float posZ = pPlayer->field_98.z + ((pPlayer->m_pos.z - pPlayer->field_98.z) * f);
		AABB aabb, tileAABB = Tile::tiles[tile]->getTileAABB(m_pLevel, hr.m_tileX, hr.m_tileY, hr.m_tileZ);
		aabb.min.y = tileAABB.min.y - 0.002f - posY;
		aabb.max.y = tileAABB.max.y + 0.002f - posY;
		aabb.min.z = tileAABB.min.z - 0.002f - posZ;
		aabb.max.z = tileAABB.max.z + 0.002f - posZ;
		aabb.min.x = tileAABB.min.x - 0.002f - posX;
		aabb.max.x = tileAABB.max.x + 0.002f - posX;
		render(aabb);
	}

	glDepthMask(true);
	glEnable(GL_TEXTURE_2D);
	glDisable(GL_BLEND);
}

void LevelRenderer::tileChanged(int x, int y, int z)
{
	setDirty(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1);
}

void LevelRenderer::renderEntities(Vec3 pos, Culler* culler, float f)
{
	if (field_14 > 0)
	{
		field_14--;
		return;
	}

	Mob* mob = m_pMinecraft->m_pMobPersp;

	EntityRenderDispatcher::getInstance()->prepare(m_pLevel, m_pMinecraft->m_pTextures, m_pMinecraft->m_pFont, mob, m_pMinecraft->getOptions(), f);

	field_18 = 0;
	field_1C = 0;
	field_20 = 0;

	EntityRenderDispatcher::xOff = mob->field_98.x + (mob->m_pos.x - mob->field_98.x) * f;
	EntityRenderDispatcher::yOff = mob->field_98.y + (mob->m_pos.y - mob->field_98.y) * f;
	EntityRenderDispatcher::zOff = mob->field_98.z + (mob->m_pos.z - mob->field_98.z) * f;

	EntityVector* pVec = m_pLevel->getAllEntities();
	field_18 = int(pVec->size());

	for (int i = 0; i < field_18; i++)
	{
		Entity* pEnt = (*pVec)[i];
		if (!pEnt->shouldRender(pos))
			continue;

		if (!culler->isVisible(pEnt->m_hitbox))
			continue;

		if (m_pMinecraft->m_pMobPersp == pEnt && !m_pMinecraft->getOptions()->m_bThirdPerson)
			continue;

		if (m_pLevel->hasChunkAt(Mth::floor(pEnt->m_pos.x), Mth::floor(pEnt->m_pos.y), Mth::floor(pEnt->m_pos.z)))
		{
			field_1C++;
			EntityRenderDispatcher::getInstance()->render(pEnt, f);
		}
	}
}

extern int t_keepPic;

void LevelRenderer::takePicture(TripodCamera* pCamera, Entity* pOwner)
{
	Mob* pOldMob = m_pMinecraft->m_pMobPersp;
	bool bOldDontRenderGui = m_pMinecraft->getOptions()->m_bDontRenderGui;
	bool bOldThirdPerson = m_pMinecraft->getOptions()->m_bThirdPerson;

#ifdef ENH_CAMERA_NO_PARTICLES
	extern bool g_bDisableParticles;
	g_bDisableParticles = true;
#endif

	m_pMinecraft->m_pMobPersp = pCamera;
	m_pMinecraft->getOptions()->m_bDontRenderGui = true;
	m_pMinecraft->getOptions()->m_bThirdPerson = false; // really from the perspective of the camera
	m_pMinecraft->m_pGameRenderer->render(0.0f);
	m_pMinecraft->m_pMobPersp = pOldMob;
	m_pMinecraft->getOptions()->m_bDontRenderGui = bOldDontRenderGui;
	m_pMinecraft->getOptions()->m_bThirdPerson = bOldThirdPerson;

#ifdef ENH_CAMERA_NO_PARTICLES
	g_bDisableParticles = false;
#endif

	t_keepPic = -1;

	static char str[256];
	// @HUH: This has the potential to overwrite a file
#ifdef ORIGINAL_CODE
	sprintf(str, "%s/games/com.mojang/img_%.4d.jpg", m_pMinecraft->m_externalStorageDir.c_str(), getTimeMs());
#else
	sprintf(str, "img_%.4d.png", getTimeMs());
#endif

	m_pMinecraft->platform()->saveScreenshot(std::string(str), Minecraft::width, Minecraft::height);
}

void LevelRenderer::addParticle(const std::string& name, float x, float y, float z, float vx, float vy, float vz)
{
	// TODO: Who's the genius who decided it'd be better to check a name string rather than an enum?
	if (m_pMinecraft->m_pMobPersp->distanceToSqr_inline(x, y, z) > 256.0f)
		return;

	ParticleEngine* pe = m_pMinecraft->m_pParticleEngine;
	if (name == "bubble")
	{
		pe->add(new BubbleParticle(m_pLevel, x, y, z, vx, vy, vz));
		return;
	}
	if (name == "smoke")
	{
		pe->add(new SmokeParticle(m_pLevel, x, y, z, vx, vy, vz, 1.0f));
		return;
	}
	if (name == "explode")
	{
		pe->add(new ExplodeParticle(m_pLevel, x, y, z, vx, vy, vz));
		return;
	}
	if (name == "flame")
	{
		pe->add(new FlameParticle(m_pLevel, x, y, z, vx, vy, vz));
		return;
	}
	if (name == "lava")
	{
		pe->add(new LavaParticle(m_pLevel, x, y, z));
		return;
	}
	if (name == "largesmoke")
	{
		pe->add(new SmokeParticle(m_pLevel, x, y, z, vx, vy, vz, 2.5f));
		return;
	}
	if (name == "reddust")
	{
		pe->add(new RedDustParticle(m_pLevel, x, y, z, vx, vy, vz));
		return;
	}

	LOG_W("Unknown particle type: %s", name.c_str());
}

void LevelRenderer::playSound(const std::string& name, float x, float y, float z, float a, float b)
{
	// TODO: Who's the genius who decided it'd be better to check a name string rather than an enum?
	float mult = 1.0f, dist = 16.0f;

	if (a > 1.0f)
	{
		mult = 16.0f;
		dist = a * mult;
	}

	if (dist * dist > m_pMinecraft->m_pMobPersp->distanceToSqr(x, y, z))
		m_pMinecraft->m_pSoundEngine->play(name, x, y, z, a, b);
}

void LevelRenderer::renderSky(float f)
{
	if (m_pMinecraft->m_pLevel->m_pDimension->field_C)
		return;

	glDisable(GL_TEXTURE_2D);

	Vec3 skyColor = m_pLevel->getSkyColor(m_pMinecraft->m_pMobPersp, f);
	if (m_pMinecraft->getOptions()->m_bAnaglyphs)
	{
		skyColor.x = (((skyColor.x * 30.0f) + (skyColor.y * 59.0f)) + (skyColor.z * 11.0f)) / 100.0f;
		skyColor.y = ((skyColor.x * 30.0f) + (skyColor.y * 70.0f)) / 100.0f;
		skyColor.z = ((skyColor.x * 30.0f) + (skyColor.z * 70.0f)) / 100.0f;
	}

	glColor4f(skyColor.x, skyColor.y, Mth::Min(1.0f, skyColor.z), 1.0f);
	glDepthMask(false);
	glColor4f(skyColor.x, skyColor.y, skyColor.z, 1.0f);
	drawArrayVT(m_skyBuffer, m_skyBufferCount, sizeof(Tesselator::Vertex));
	glEnable(GL_TEXTURE_2D);
	glDepthMask(true);
}

// TODO: This should be inside of an initialized "Minecraft" instance rather than the global namespace
bool g_bAreCloudsAvailable = false; // false because 0.1 didn't have them

void LevelRenderer::renderClouds(float f)
{
	if (!g_bAreCloudsAvailable)
		return;

	glEnable(GL_TEXTURE_2D);
	glDisable(GL_CULL_FACE);

	float yPos = Lerp(m_pMinecraft->m_pMobPersp->field_98.y, m_pMinecraft->m_pMobPersp->m_pos.y, f); // not certain if this old pos Y is used
	m_pTextures->loadAndBindTexture("environment/clouds.png");

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	Vec3 cloudColor = m_pLevel->getCloudColor(f);

	float offX = Lerp(m_pMinecraft->m_pMobPersp->field_3C.x, m_pMinecraft->m_pMobPersp->m_pos.x, f) + (float(m_ticksSinceStart) + f) * 0.3f;
	float offZ = Lerp(m_pMinecraft->m_pMobPersp->field_3C.z, m_pMinecraft->m_pMobPersp->m_pos.z, f);
	
	int dx2048 = Mth::floor(offX / 2048.0f);
	int dz2048 = Mth::floor(offZ / 2048.0f);

	offX -= float(dx2048 * 2048);
	offZ -= float(dz2048 * 2048);

	Tesselator& t = Tesselator::instance;

	float fYPos = (128.0f - yPos) + 0.33f;
	offX /= 2048.0f;
	offZ /= 2048.0f;
	t.begin();
	t.color(cloudColor.x, cloudColor.y, cloudColor.z, 0.8f);

	const int incr = 32;
	const int in2 = 256 / incr;
	for (int x = -incr * in2; x < incr * in2; x += incr)
	{
		for (int z = -incr * in2; z < incr * in2; z += incr)
		{
			t.vertexUV(float(x) + 0.0f, fYPos, float(z) + incr, float(x + 0.0f) / 2048.0f + offX, float(z + incr) / 2048.0f + offZ);
			t.vertexUV(float(x) + incr, fYPos, float(z) + incr, float(x + incr) / 2048.0f + offX, float(z + incr) / 2048.0f + offZ);
			t.vertexUV(float(x) + incr, fYPos, float(z) + 0.0f, float(x + incr) / 2048.0f + offX, float(z + 0.0f) / 2048.0f + offZ);
			t.vertexUV(float(x) + 0.0f, fYPos, float(z) + 0.0f, float(x + 0.0f) / 2048.0f + offX, float(z + 0.0f) / 2048.0f + offZ);
		}

	}

	t.voidBeginAndEndCalls(false); // why??
	t.draw();
	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
	glDisable(GL_BLEND);
	glEnable(GL_CULL_FACE);
}

void LevelRenderer::skyColorChanged()
{
	for (int i = 0; i < m_chunksLength; i++)
	{
		Chunk* pChunk = m_chunks[i];
		if (!pChunk->field_54)
			continue;

		if (pChunk->isDirty())
			continue;

		field_88.push_back(pChunk);
		pChunk->setDirty();
	}
}
