/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, 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/containers/yaml.h>
#include <mrpt/opengl/CText.h>
#include <mrpt/opengl/opengl_api.h>
#include <mrpt/serialization/CArchive.h>

#include "gltext.h"

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

IMPLEMENTS_SERIALIZABLE(CText, CRenderizable, mrpt::opengl)

CText::~CText() = default;

constexpr double text_spacing = 1.5;
constexpr double text_kerning = 0.1;

void CText::onUpdateBuffers_Text()
{
  std::unique_lock<std::shared_mutex> writeLock(m_textDataMtx.data);

  auto& vbd = CRenderizableShaderText::m_vertex_buffer_data;
  auto& tris = CRenderizableShaderText::m_triangles;
  auto& cbd = CRenderizableShaderText::m_color_buffer_data;
  vbd.clear();
  tris.clear();
  cbd.clear();

  mrpt::opengl::internal::glSetFont(m_fontName);
  mrpt::opengl::internal::glDrawText(
      m_str, tris, vbd, mrpt::opengl::FILL, text_spacing, text_kerning);

  // All lines & triangles, the same color:
  cbd.assign(vbd.size(), getColor_u8());
  for (auto& tri : m_triangles) tri.setColor(getColor_u8());
}

std::pair<double, double> CText::computeTextExtension() const
{
  mrpt::opengl::internal::glSetFont(m_fontName);
  const auto [textW, textH] =
      mrpt::opengl::internal::glGetExtends(m_str, text_spacing, text_kerning);
  return {textW, textH};
}

void CText::render(const RenderContext& rc) const
{
#if MRPT_HAS_OPENGL_GLUT || MRPT_HAS_EGL

  // Compute pixel of (0,0,0) for this object:
  // "px" will be given in the range:
  //     [-1,-1] (left-bottom) - [+1,+1] (top-right)
  //
  const auto& pmv = rc.state->pmv_matrix;
  if (std::abs(pmv(3, 3)) < 1e-10) return;

  const auto px = mrpt::math::TPoint2Df(pmv(0, 3) / pmv(3, 3), pmv(1, 3) / pmv(3, 3));

  // Load matrices in shader:
  const GLint u_pmat = rc.shader->uniformId("p_matrix");
  const GLint u_mvmat = rc.shader->uniformId("mv_matrix");

  // Projection: identity
  static const auto eye4 = mrpt::math::CMatrixFloat44::Identity();
  glUniformMatrix4fv(u_pmat, 1, true, eye4.data());

  // Model-view: translate and scale
  auto mv = mrpt::math::CMatrixFloat44::Identity();
  mv(0, 3) = px.x;
  mv(1, 3) = px.y;
  mv(2, 3) = pmv(2, 3) / pmv(3, 3);  // depth

  if (rc.state->viewport_height <= 0 || rc.state->viewport_width <= 0)
  {
    std::cerr << "[CText] Warning: invalid viewport size!\n";
    return;
  }

  // Find scale according to font height in pixels:
  const float scale = this->m_fontHeight / static_cast<float>(rc.state->viewport_height);

  const float aspect = rc.state->viewport_width / double(rc.state->viewport_height);
  mv(0, 0) *= scale / aspect;
  mv(1, 1) *= scale;

  glUniformMatrix4fv(u_mvmat, 1, true, mv.data());

  CRenderizableShaderText::render(rc);

#endif
}

uint8_t CText::serializeGetVersion() const { return 2; }
void CText::serializeTo(mrpt::serialization::CArchive& out) const
{
  writeToStreamRender(out);
  out << m_str;
  out << m_fontName;
  out << (uint32_t)m_fontHeight;
}

void CText::serializeFrom(mrpt::serialization::CArchive& in, uint8_t version)
{
  switch (version)
  {
    case 0:
    case 1:
    case 2:
    {
      uint32_t i;
      readFromStreamRender(in);
      in >> m_str;
      if (version >= 1)
      {
        in >> m_fontName;
        in >> i;
        m_fontHeight = i;

        if (version < 2)
        {
          in >> i;
          // dummy, removed in v2: m_fontWidth = i;
        }
      }
    }
    break;
    default:
      MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version);
  };
}

auto CText::internalBoundingBoxLocal() const -> mrpt::math::TBoundingBoxf
{
  return {
      {0.f, 0.f, 0.f},
      {0.f, 0.f, 0.f}
  };
}

void CText::toYAMLMap(mrpt::containers::yaml& propertiesMap) const
{
  CRenderizable::toYAMLMap(propertiesMap);
  propertiesMap["text"] = m_str;
}
