
#include "legolod.h"

#include "legoroi.h"
#include "misc/legocontainer.h"
#include "misc/legostorage.h"
#include "shape/legomesh.h"
#include "tgl/d3drm/impl.h"

#include <SDL3/SDL_stdinc.h>

DECOMP_SIZE_ASSERT(LODObject, 0x04)
DECOMP_SIZE_ASSERT(ViewLOD, 0x0c)
DECOMP_SIZE_ASSERT(LegoLOD, 0x20)
DECOMP_SIZE_ASSERT(LegoLOD::Mesh, 0x08)

// GLOBAL: LEGO1 0x101013d4
// GLOBAL: BETA10 0x10207230
LPDIRECT3DRMMATERIAL g_lodMaterial = NULL;

// GLOBAL: LEGO1 0x101013dc
// GLOBAL: BETA10 0x10207238
const char* g_InhPrefix = "inh";

#ifdef BETA10
inline BOOL GetD3DRM_legolod(IDirect3DRM2*& d3drm, Tgl::Renderer* pRenderer);
#else
inline IDirect3DRM2* GetD3DRM_legolod(Tgl::Renderer* pRenderer);
#endif
inline BOOL GetMeshData(IDirect3DRMMesh** mesh, D3DRMGROUPINDEX* index, Tgl::Mesh* pMesh);

// FUNCTION: LEGO1 0x100aa380
// FUNCTION: BETA10 0x1018ce90
LegoLOD::LegoLOD(Tgl::Renderer* p_renderer) : ViewLOD(p_renderer)
{
	if (g_lodMaterial == NULL) {
#ifdef BETA10
		IDirect3DRM2* d3drm = NULL;
		assert((p_renderer != NULL));
		GetD3DRM_legolod(d3drm, p_renderer);
		if (d3drm->CreateMaterial(10.0, &g_lodMaterial)) {
			assert(0);
		}
#else
		GetD3DRM_legolod(p_renderer)->CreateMaterial(10.0, &g_lodMaterial);
#endif
	}

	m_melems = NULL;
	m_numMeshes = 0;
	m_numVertices = 0;
	m_numPolys = 0;
	m_meshOffset = 0;
}

// FUNCTION: LEGO1 0x100aa450
// FUNCTION: BETA10 0x1018d017
LegoLOD::~LegoLOD()
{
	if (m_numMeshes && m_melems != NULL) {
		for (LegoU32 i = 0; i < m_numMeshes; i++) {
			if (m_melems[i].m_tglMesh != NULL) {
				delete m_melems[i].m_tglMesh;
				m_melems[i].m_tglMesh = NULL;
			}
		}
	}

	if (m_melems) {
		delete[] m_melems;
	}
}

#ifdef BETA10
/// This class does not exist in LEGO1.
class UnknownBeta0x1018e7e0 {
public:
	// FUNCTION: BETA10 0x1018e7e0
	UnknownBeta0x1018e7e0()
	{
		m_unk0x00 = 0;
		m_unk0x04 = 0;
		m_unk0x08 = 0;
		m_unk0x0c = 0;
		m_unk0x10 = 0;
		m_unk0x14 = 0;
		m_unk0x18 = 0;
	}

	// STUB: BETA10 0x1018e840
	undefined4 BETA10_1018e840(LegoStorage* p_storage) { return 0; }

	undefined4 m_unk0x00;
	undefined4 m_unk0x04;
	undefined4 m_unk0x08;
	undefined4 m_unk0x0c;
	undefined4 m_unk0x10;
	undefined4 m_unk0x14;
	undefined4 m_unk0x18;
	undefined4 m_unk0x1c;
};
#endif

// FUNCTION: LEGO1 0x100aa510
// FUNCTION: BETA10 0x1018d15d
LegoResult LegoLOD::Read(Tgl::Renderer* p_renderer, LegoTextureContainer* p_textureContainer, LegoStorage* p_storage)
{
	using Tgl::Succeeded;

	float(*normals)[3] = NULL;
	float(*vertices)[3] = NULL;
	float(*textureVertices)[2] = NULL;
	LegoS32 numVerts = 0;
	LegoS32 numNormals = 0;
	LegoS32 numTextureVertices = 0;
	LegoMesh* legoMesh = NULL;
	LegoU32(*polyIndices)[3] = NULL;
	LegoU32(*textureIndices)[3] = NULL;
	LegoTextureInfo* textureInfo = NULL;
	LegoU8 local4c = 0; // BETA10 only, only written, never read
	LegoU32 numPolys, numVertices, numTextureIndices, meshIndex;
	LegoU32 i, indexBackwards, indexForwards, tempNumVertsAndNormals;
	LegoFloat red, green, blue, alpha;
	IDirect3DRMMesh* d3dmesh;
	D3DRMGROUPINDEX index;

	unsigned char paletteEntries[256];

	if (p_storage->Read(&m_flags, sizeof(LegoU32)) != SUCCESS) {
		goto done;
	}

	if (SkipReadingData()) {
#ifdef BETA10
		// There was an additional field of the correct type here in BETA10
		m_flags = (unsigned int) new UnknownBeta0x1018e7e0();
		if (((UnknownBeta0x1018e7e0*) m_flags)->BETA10_1018e840(p_storage)) {
			assert(0);
			return FAILURE;
		}
#endif
		return SUCCESS;
	}

	m_meshBuilder = p_renderer->CreateMeshBuilder();

	if (p_storage->Read(&m_numMeshes, sizeof(LegoU32)) != SUCCESS) {
		goto done;
	}

	if (m_numMeshes == 0) {
#ifndef BETA10
		ClearFlag(c_hasMesh);
#endif
		return SUCCESS;
	}

#ifndef BETA10
	SetFlag(c_hasMesh);
#endif

	m_melems = new Mesh[m_numMeshes];
	memset(m_melems, 0, sizeof(*m_melems) * m_numMeshes);

	indexBackwards = m_numMeshes - 1;
	indexForwards = 0;

	if (p_storage->Read(&tempNumVertsAndNormals, sizeof(LegoU32)) != SUCCESS) {
		assertIfBeta10(0);
		goto done;
	}

	// TODO: Can't get this one right
	numVerts = *((LegoU16*) &tempNumVertsAndNormals) & MAXSHORT;
	numNormals = (*((LegoU16*) &tempNumVertsAndNormals + 1) >> 1) & MAXSHORT;

	if (p_storage->Read(&numTextureVertices, sizeof(LegoS32)) != SUCCESS) {
		assertIfBeta10(0);
		goto done;
	}

	if (numVerts > 0) {
		vertices = new float[numVerts][sizeOfArray(*vertices)];
		if (p_storage->Read(vertices, numVerts * 3 * sizeof(float)) != SUCCESS) {
			// LINE: BETA10 0x1018d443
			assertIfBeta10(0);
			goto done;
		}
	}

	if (numNormals > 0) {
		normals = new float[numNormals][sizeOfArray(*normals)];
		if (p_storage->Read(normals, numNormals * 3 * sizeof(float)) != SUCCESS) {
			assertIfBeta10(0);
			goto done;
		}
	}

	if (numTextureVertices > 0) {
		textureVertices = new float[numTextureVertices][sizeOfArray(*textureVertices)];
		if (p_storage->Read(textureVertices, numTextureVertices * 2 * sizeof(float)) != SUCCESS) {
			// LINE: BETA10 0x1018d513
			assertIfBeta10(0);
			goto done;
		}
	}

	for (i = 0; i < m_numMeshes; i++) {
		local4c = 0;
		const LegoChar *textureName, *materialName;
		Tgl::ShadingModel shadingModel;

		if (p_storage->Read(&numPolys, 2) != SUCCESS) {
			assertIfBeta10(0);
			goto done;
		}

		m_numPolys += numPolys & USHRT_MAX;

		if (p_storage->Read(&numVertices, 2) != SUCCESS) {
			assertIfBeta10(0);
			goto done;
		}

		polyIndices = new LegoU32[numPolys & USHRT_MAX][sizeOfArray(*polyIndices)];
		if (p_storage->Read(polyIndices, (numPolys & USHRT_MAX) * 3 * sizeof(LegoU32)) != SUCCESS) {
			assertIfBeta10(0);
			goto done;
		}

		if (p_storage->Read(&numTextureIndices, sizeof(numTextureIndices)) != SUCCESS) {
			assertIfBeta10(0);
			goto done;
		}

		if (numTextureIndices > 0) {
			textureIndices = new LegoU32[numPolys & USHRT_MAX][sizeOfArray(*textureIndices)];
			if (p_storage->Read(textureIndices, (numPolys & USHRT_MAX) * 3 * sizeof(LegoU32)) != SUCCESS) {
				assertIfBeta10(0);
				goto done;
			}
		}
		else {
			textureIndices = NULL;
		}

		legoMesh = new LegoMesh();

		if (legoMesh->Read(p_storage) != SUCCESS) {
			assertIfBeta10(0);
			goto done;
		}

		switch (legoMesh->GetShading()) {
		case LegoMesh::e_flat:
			shadingModel = Tgl::Flat;
			break;
		case LegoMesh::e_wireframe:
			shadingModel = Tgl::Wireframe;
			break;
		default:
			shadingModel = Tgl::Gouraud;
		}

		m_numVertices += numVertices & USHRT_MAX;

		textureName = legoMesh->GetTextureName();
		materialName = legoMesh->GetMaterialName();

		if (HasInhPrefix(textureName) || HasInhPrefix(materialName)) {
			meshIndex = indexBackwards;
			indexBackwards--;
		}
		else {
			local4c = 1;
			meshIndex = indexForwards;
			indexForwards++;
		}

		Tgl::MeshBuilder* locMesh = m_meshBuilder;
		assert(locMesh);

		m_melems[meshIndex].m_tglMesh = locMesh->CreateMesh(
			numPolys & USHRT_MAX,
			numVertices & USHRT_MAX,
			vertices,
			normals,
			textureVertices,
			polyIndices,
			textureIndices,
			shadingModel
			// LINE: LEGO1 0x100aa885
		);

		if (m_melems[meshIndex].m_tglMesh == NULL) {
			assertIfBeta10(0);
			goto done;
		}

		Tgl::Result tglResult = m_melems[meshIndex].m_tglMesh->SetShadingModel(shadingModel);

		// clang-format off
		assert(Succeeded( tglResult ));
		// clang-format on

		if (textureName != NULL) {
			if (legoMesh->GetUseAlias() &&
				LegoROI::GetPaletteEntries(textureName, paletteEntries, sizeOfArray(paletteEntries))) {
#ifdef BETA10
				textureName = (const LegoChar*) paletteEntries;
#endif
			}

			textureInfo = p_textureContainer->Get(legoMesh->GetTextureName());

			if (textureInfo == NULL) {
				assertIfBeta10(0);
				goto done;
			}

			tglResult = m_melems[meshIndex].m_tglMesh->SetColor(1.0F, 1.0F, 1.0F, 0.0F);
			// clang-format off
			assert(Succeeded( tglResult ));
			// clang-format on

#ifdef BETA10
			// This typecast is invalid, `textureInfo` had a different type in BETA10
			tglResult = m_melems[meshIndex].m_tglMesh->SetTexture((TglImpl::TextureImpl*) textureInfo);
			// clang-format off
			assert(Succeeded( tglResult ));
			// clang-format on
#else
			LegoTextureInfo::SetGroupTexture(m_melems[meshIndex].m_tglMesh, textureInfo);
#endif

			m_melems[meshIndex].m_textured = TRUE;
		}
		else {
			red = 1.0F;
			// LINE: BETA10 0x1018db2d
			green = 0.0F;
			blue = 1.0F;
			alpha = 0.0F;

			if (legoMesh->GetUseAlias()) {
				LegoROI::GetRGBAColor(materialName, red, green, blue, alpha);
			}
			else {
				red = legoMesh->GetColor().GetRed() / 255.0;
				green = legoMesh->GetColor().GetGreen() / 255.0;
				blue = legoMesh->GetColor().GetBlue() / 255.0;
				alpha = legoMesh->GetAlpha();
			}

			tglResult = m_melems[meshIndex].m_tglMesh->SetColor(red, green, blue, alpha);
			// clang-format off
			// LINE: BETA10 0x1018dc72
			assert(Succeeded( tglResult ));
			// clang-format on
		}

		if (legoMesh->GetUnknown0x0d() > 0) {
			GetMeshData(&d3dmesh, &index, m_melems[meshIndex].m_tglMesh);
			d3dmesh->SetGroupMaterial(index, g_lodMaterial);
		}

		if (legoMesh != NULL) {
			delete legoMesh;
			legoMesh = NULL;
		}
		if (polyIndices != NULL) {
			delete[] polyIndices;
			polyIndices = NULL;
		}
		if (textureIndices != NULL) {
			delete[] textureIndices;
			textureIndices = NULL;
		}
	}

	// LINE: LEGO1 0x100aab45
	m_meshOffset = indexForwards;

	if (textureVertices != NULL) {
		delete[] textureVertices;
	}
	if (normals != NULL) {
		delete[] normals;
	}
	if (vertices != NULL) {
		delete[] vertices;
	}

	return SUCCESS;

done:
	if (normals != NULL) {
		delete[] normals;
	}
	if (vertices != NULL) {
		delete[] vertices;
	}
	if (textureVertices != NULL) {
		delete[] textureVertices;
	}
	if (legoMesh != NULL) {
		delete legoMesh;
	}
	if (polyIndices != NULL) {
		delete[] polyIndices;
	}
	if (textureIndices != NULL) {
		delete[] textureIndices;
	}

	return FAILURE;
}

// FUNCTION: LEGO1 0x100aabb0
// FUNCTION: BETA10 0x1018e02a
LegoLOD* LegoLOD::Clone(Tgl::Renderer* p_renderer)
{
	LegoLOD* dupLod = new LegoLOD(p_renderer);

	dupLod->m_meshBuilder = m_meshBuilder->Clone();
	dupLod->m_melems = new Mesh[m_numMeshes];

	assert(dupLod->m_melems);

	for (LegoU32 i = 0; i < m_numMeshes; i++) {
		dupLod->m_melems[i].m_tglMesh = m_melems[i].m_tglMesh->ShallowClone(dupLod->m_meshBuilder);
		dupLod->m_melems[i].m_textured = m_melems[i].m_textured;
	}

	dupLod->m_flags = m_flags;
	dupLod->m_numMeshes = m_numMeshes;
	dupLod->m_numVertices = m_numVertices;
	dupLod->m_numPolys = m_numPolys;
	dupLod->m_meshOffset = m_meshOffset;

	return dupLod;
}

// FUNCTION: LEGO1 0x100aacb0
LegoResult LegoLOD::SetColor(LegoFloat p_red, LegoFloat p_green, LegoFloat p_blue, LegoFloat p_alpha)
{
	for (LegoU32 i = m_meshOffset; i < m_numMeshes; i++) {
		if (!m_melems[i].m_textured) {
			m_melems[i].m_tglMesh->SetColor(p_red, p_green, p_blue, p_alpha);
		}
	}

	return SUCCESS;
}

// FUNCTION: LEGO1 0x100aad00
// FUNCTION: BETA10 0x1018e241
LegoResult LegoLOD::SetTextureInfo(LegoTextureInfo* p_textureInfo)
{
	using Tgl::Succeeded;

	for (LegoU32 i = m_meshOffset; i < m_numMeshes; i++) {
		if (m_melems[i].m_textured) {
#ifdef BETA10
			// This function likely had a different signature in BETA10
			Tgl::Result tglResult = m_melems[i].m_tglMesh->SetTexture((const Tgl::Texture*) p_textureInfo);
			// clang-format off
			assert(Succeeded( tglResult ));
			// clang-format on
#else
			LegoTextureInfo::SetGroupTexture(m_melems[i].m_tglMesh, p_textureInfo);
#endif
			m_melems[i].m_tglMesh->SetColor(1.0F, 1.0F, 1.0F, 0.0F);
			m_melems[i].m_textured = TRUE;
		}
	}

	return SUCCESS;
}

// FUNCTION: LEGO1 0x100aad70
// FUNCTION: BETA10 0x1018e32c
LegoResult LegoLOD::UpdateTextureInfo(LegoTextureInfo* p_textureInfo)
{
	using Tgl::Succeeded;

	for (LegoU32 i = m_meshOffset; i < m_numMeshes; i++) {
		if (m_melems[i].m_textured) {
#ifdef BETA10
			// This function likely had a different signature in BETA10
			Tgl::Result tglResult = m_melems[i].m_tglMesh->SetTexture((const Tgl::Texture*) p_textureInfo);
			// clang-format off
			assert(Succeeded( tglResult ));
			// clang-format on
#else
			LegoTextureInfo::SetGroupTexture(m_melems[i].m_tglMesh, p_textureInfo);
#endif
		}
	}

	return SUCCESS;
}

// FUNCTION: LEGO1 0x100aadc0
LegoResult LegoLOD::GetTextureInfo(LegoTextureInfo*& p_textureInfo)
{
	for (LegoU32 i = m_meshOffset; i < m_numMeshes; i++) {
		if (m_melems[i].m_textured) {
			if (LegoTextureInfo::GetGroupTexture(m_melems[i].m_tglMesh, p_textureInfo) == TRUE) {
				return SUCCESS;
			}
		}
	}

	return FAILURE;
}

// FUNCTION: LEGO1 0x100aae20
// FUNCTION: BETA10 0x1018e46d
LegoBool LegoLOD::HasInhPrefix(const LegoChar* p_name)
{
	if (p_name != NULL && !SDL_strncasecmp(p_name, g_InhPrefix, strlen(g_InhPrefix))) {
		return TRUE;
	}
	else {
		return FALSE;
	}
}

// FUNCTION: LEGO1 0x100aae60
// FUNCTION: BETA10 0x1018e50f
void LegoLOD::ClearMeshOffset()
{
	m_meshOffset = 0;
}

// FUNCTION: BETA10 0x1018dfc4
inline BOOL GetMeshData(IDirect3DRMMesh** mesh, D3DRMGROUPINDEX* index, Tgl::Mesh* p_tglElem)
{
	assert(p_tglElem);
	TglImpl::MeshImpl* meshImpl = (TglImpl::MeshImpl*) p_tglElem;
	// Note: Diff in BETA10 (thunked in recompile but not in orig)
	*mesh = meshImpl->ImplementationData()->groupMesh;
	*index = meshImpl->ImplementationData()->groupIndex;
	return FALSE;
}

#ifdef BETA10
// FUNCTION: BETA10 0x1018cfc5
inline BOOL GetD3DRM_legolod(IDirect3DRM2*& d3drm, Tgl::Renderer* p_tglRenderer)
{
	// Note: Code duplication with viewmanager.cpp:GetD3DRM()
	assert(p_tglRenderer);
	TglImpl::RendererImpl* renderer = (TglImpl::RendererImpl*) p_tglRenderer;
	// Note: Diff in BETA10 (thunked in recompile but not in orig)
	d3drm = renderer->ImplementationData();
	return 0;
}
#else
inline IDirect3DRM2* GetD3DRM_legolod(Tgl::Renderer* pRenderer)
{
	return ((TglImpl::RendererImpl*) pRenderer)->ImplementationData();
}
#endif
