/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include "opengl-precomp.h"	 // Precompiled header
//
#include <mrpt/opengl/CSetOfTriangles.h>
#include <mrpt/opengl/CTexturedPlane.h>
#include <mrpt/serialization/CArchive.h>

using namespace mrpt;
using namespace mrpt::opengl;
using namespace mrpt::poses;
using namespace mrpt::math;
using namespace std;

IMPLEMENTS_SERIALIZABLE(CTexturedPlane, CRenderizable, mrpt::opengl)

CTexturedPlane::CTexturedPlane(
	float x_min, float x_max, float y_min, float y_max)
{
	CRenderizableShaderTriangles::enableLight(false);
	CRenderizableShaderTexturedTriangles::enableLight(false);

	setPlaneCorners(x_min, x_max, y_min, y_max);
}

void CTexturedPlane::render(const RenderContext& rc) const
{
	const bool hasTexture =
		textureImageHasBeenAssigned() && !getTextureImage().isEmpty();

	switch (rc.shader_id)
	{
		case DefaultShaderID::TRIANGLES_NO_LIGHT:
		case DefaultShaderID::TRIANGLES_LIGHT:
			if (!hasTexture) CRenderizableShaderTriangles::render(rc);
			break;
		case DefaultShaderID::TEXTURED_TRIANGLES_NO_LIGHT:
		case DefaultShaderID::TEXTURED_TRIANGLES_LIGHT:
			if (hasTexture) CRenderizableShaderTexturedTriangles::render(rc);
			break;
	};
}

void CTexturedPlane::renderUpdateBuffers() const
{
	const bool hasTexture =
		textureImageHasBeenAssigned() && !getTextureImage().isEmpty();

	if (!hasTexture) CRenderizableShaderTriangles::renderUpdateBuffers();
	else
		CRenderizableShaderTexturedTriangles::renderUpdateBuffers();
}

void CTexturedPlane::onUpdateBuffers_TexturedTriangles()
{
	MRPT_START
	using P2f = mrpt::math::TPoint2Df;
	using P3f = mrpt::math::TPoint3Df;

	auto& tris = CRenderizableShaderTexturedTriangles::m_triangles;
	std::unique_lock<std::shared_mutex> writeLock(
		CRenderizableShaderTexturedTriangles::m_trianglesMtx.data);

	tris.clear();

	{
		TTriangle t;
		t.vertices[0].xyzrgba.pt = P3f(m_xMin, m_yMin, 0);
		t.vertices[1].xyzrgba.pt = P3f(m_xMax, m_yMin, 0);
		t.vertices[2].xyzrgba.pt = P3f(m_xMax, m_yMax, 0);

		t.vertices[0].uv = P2f(0, 0);
		t.vertices[1].uv = P2f(1, 0);
		t.vertices[2].uv = P2f(1, 1);

		t.computeNormals();
		tris.emplace_back(t);
	}
	{
		TTriangle t;
		t.vertices[0].xyzrgba.pt = P3f(m_xMin, m_yMin, 0);
		t.vertices[1].xyzrgba.pt = P3f(m_xMax, m_yMax, 0);
		t.vertices[2].xyzrgba.pt = P3f(m_xMin, m_yMax, 0);

		t.vertices[0].uv = P2f(0, 0);
		t.vertices[1].uv = P2f(1, 1);
		t.vertices[2].uv = P2f(0, 1);

		t.computeNormals();
		tris.emplace_back(t);
	}

	MRPT_END
}

void CTexturedPlane::onUpdateBuffers_Triangles()
{
	MRPT_START
	using P3f = mrpt::math::TPoint3Df;

	std::unique_lock<std::shared_mutex> trisWriteLock(
		CRenderizableShaderTriangles::m_trianglesMtx.data);
	auto& tris = CRenderizableShaderTriangles::m_triangles;
	tris.clear();

	const auto col = getColor_u8();
	TTriangle t;
	for (int i = 0; i < 3; i++)
	{
		t.vertices[i].xyzrgba.r = col.R;
		t.vertices[i].xyzrgba.g = col.G;
		t.vertices[i].xyzrgba.b = col.B;
		t.vertices[i].xyzrgba.a = col.A;
	}

	t.vertices[0].xyzrgba.pt = P3f(m_xMin, m_yMin, 0);
	t.vertices[1].xyzrgba.pt = P3f(m_xMax, m_yMin, 0);
	t.vertices[2].xyzrgba.pt = P3f(m_xMax, m_yMax, 0);

	t.computeNormals();
	tris.emplace_back(t);

	t.vertices[0].xyzrgba.pt = P3f(m_xMin, m_yMin, 0);
	t.vertices[1].xyzrgba.pt = P3f(m_xMax, m_yMax, 0);
	t.vertices[2].xyzrgba.pt = P3f(m_xMin, m_yMax, 0);

	t.computeNormals();
	tris.emplace_back(t);

	MRPT_END
}

uint8_t CTexturedPlane::serializeGetVersion() const { return 2; }
void CTexturedPlane::serializeTo(mrpt::serialization::CArchive& out) const
{
	writeToStreamRender(out);

	out << m_xMin << m_xMax;
	out << m_yMin << m_yMax;

	writeToStreamTexturedObject(out);
}

void CTexturedPlane::serializeFrom(
	mrpt::serialization::CArchive& in, uint8_t version)
{
	switch (version)
	{
		case 0:
		case 1:
			THROW_EXCEPTION("Deserialization of old formats not supported.");
			break;
		case 2:
		{
			readFromStreamRender(in);
			in >> m_xMin >> m_xMax;
			in >> m_yMin >> m_yMax;
			readFromStreamTexturedObject(in);
		}
		break;
		default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version);
	};
	CRenderizable::notifyChange();
}

bool CTexturedPlane::traceRay(const mrpt::poses::CPose3D& o, double& dist) const
{
	if (!polygonUpToDate) updatePoly();
	return math::traceRay(tmpPoly, (o - getCPose()).asTPose(), dist);
}

void CTexturedPlane::updatePoly() const
{
	TPolygon3D poly(4);
	poly[0].x = poly[1].x = m_xMin;
	poly[2].x = poly[3].x = m_xMax;
	poly[0].y = poly[3].y = m_yMin;
	poly[1].y = poly[2].y = m_yMax;
	for (size_t i = 0; i < 4; i++)
		poly[i].z = 0;
	tmpPoly.resize(1);
	tmpPoly[0] = poly;
	polygonUpToDate = true;
}

auto CTexturedPlane::internalBoundingBoxLocal() const
	-> mrpt::math::TBoundingBoxf
{
	return mrpt::math::TBoundingBoxf::FromUnsortedPoints(
		{m_xMin, m_yMin, 0.f}, {m_xMax, m_yMax, 0.f});
}
