/* +------------------------------------------------------------------------+
   |                     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/opengl/CColorBar.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(CColorBar, CRenderizable, mrpt::opengl)

CColorBar::CColorBar(
    /** The colormap to represent. */
    const mrpt::img::TColormap colormap,
    /** size of the color bar */
    double width,
    double height,
    /** limits for [0,1] colormap indices */
    float min_col,
    float max_col,
    /** limits for values associated to extreme colors */
    float min_value,
    float max_value,
    /** sprintf-like format string for values */
    const std::string& label_format,
    /** Label text font size */
    float label_font_size) :
    m_colormap(colormap),
    m_width(width),
    m_height(height),
    m_label_format(label_format),
    m_min_col(min_col),
    m_max_col(max_col),
    m_min_value(min_value),
    m_max_value(max_value),
    m_label_font_size(label_font_size)
{
}

void CColorBar::setColormap(const mrpt::img::TColormap colormap)
{
  m_colormap = colormap;
  CRenderizable::notifyChange();
}

void CColorBar::setColorAndValueLimits(
    float col_min, float col_max, float value_min, float value_max)
{
  m_min_col = col_min;
  m_max_col = col_max;
  m_min_value = value_min;
  m_max_value = value_max;
  CRenderizable::notifyChange();
}

void CColorBar::render(const RenderContext& rc) const
{
  // colobars are typically displayed on-top
  switch (rc.shader_id)
  {
    case DefaultShaderID::TRIANGLES_NO_LIGHT:
      CRenderizableShaderTriangles::render(rc);
      break;
    case DefaultShaderID::WIREFRAME:
      CRenderizableShaderWireFrame::render(rc);
      break;
  };
}
void CColorBar::renderUpdateBuffers() const
{
  const_cast<CColorBar&>(*this).onUpdateBuffers_all();

  CRenderizableShaderTriangles::renderUpdateBuffers();
  CRenderizableShaderWireFrame::renderUpdateBuffers();
}

void CColorBar::onUpdateBuffers_all()
{
  std::unique_lock<std::shared_mutex> trisWriteLock(
      CRenderizableShaderTriangles::m_trianglesMtx.data);
  auto& tris = CRenderizableShaderTriangles::m_triangles;

  auto& lines_vbd = CRenderizableShaderWireFrame::m_vertex_buffer_data;
  auto& lines_cbd = CRenderizableShaderWireFrame::m_color_buffer_data;
  std::unique_lock<std::shared_mutex> wfWriteLock(
      CRenderizableShaderWireFrame::m_wireframeMtx.data);

  lines_vbd.clear();
  lines_cbd.clear();

  // precomputed params:
  unsigned int num_divisions = 64;
  unsigned int num_labels = 4;
  unsigned int one_label_each_nth = num_divisions / num_labels;

  const double x0 = .0, x1 = m_width, x2 = m_width * 1.3;
  const double Ay = m_height / (num_divisions - 1);

  std::vector<mrpt::img::TColor> colors(num_divisions);
  for (unsigned int i = 0; i < num_divisions; i++)
  {
    const float col_idx = m_min_col + i * (m_max_col - m_min_col) / (num_divisions - 1);
    mrpt::img::TColorf colf;
    mrpt::img::colormap(m_colormap, col_idx, colf.R, colf.G, colf.B);
    colors[i] = colf.asTColor();
  }

  // Text labels:
  mrpt::opengl::internal::glSetFont("mono");

  const auto tickColor = mrpt::img::TColor::black();
  const auto textColor = mrpt::img::TColor::white();

  for (unsigned int i = 0; i < num_divisions; i++)
  {
    const double val = m_min_value + i * (m_max_value - m_min_value) / (num_divisions - 1);
    const double y0 = Ay * i;  //, y1 = Ay*(i + 1);

    // Text label:
    bool draw_label = (i % one_label_each_nth) == 0 || i == (num_divisions - 1);

    if (draw_label)
    {
      // Line:
      lines_vbd.emplace_back(x0, y0, 0);
      lines_vbd.emplace_back(x2, y0, 0);
      lines_cbd.emplace_back(tickColor);
      lines_cbd.emplace_back(tickColor);

      // Text:

      // Size: m_label_font_size
      // glTranslated(x2, y0, 0.0);
      mrpt::poses::CPose3D p(x2, y0, 0.0, 0, 0, 0);

      mrpt::opengl::internal::glDrawTextTransformed(
          mrpt::format(m_label_format.c_str(), val), tris, lines_vbd, lines_cbd, p,
          m_label_font_size, textColor, FILL, 1.5, 0.1);
    }
  }

  // Color bar itself:
  for (unsigned int i = 0; i < num_divisions - 1; i++)
  {
    const double y0 = Ay * i, y1 = Ay * (i + 1);
    const TPoint3Df pt00(x0, y0, 0), pt10(x1, y0, 0);
    const TPoint3Df pt01(x0, y1, 0), pt11(x1, y1, 0);

    // Color quad:
    // triangle 1/2
    mrpt::opengl::TTriangle t;
    t.vertices[0].xyzrgba.pt = pt00;
    t.vertices[0].setColor(colors[i]);

    t.vertices[1].xyzrgba.pt = pt10;
    t.vertices[1].setColor(colors[i]);

    t.vertices[2].xyzrgba.pt = pt11;
    t.vertices[2].setColor(colors[i + 1]);
    t.computeNormals();
    tris.emplace_back(t);

    // triangle 2/2
    t.vertices[0].xyzrgba.pt = pt00;
    t.vertices[0].setColor(colors[i]);

    t.vertices[1].xyzrgba.pt = pt11;
    t.vertices[1].setColor(colors[i + 1]);

    t.vertices[2].xyzrgba.pt = pt01;
    t.vertices[2].setColor(colors[i + 1]);
    t.computeNormals();
    tris.emplace_back(t);
  }
}

void CColorBar::onUpdateBuffers_Wireframe()
{
  // Already done in onUpdateBuffers_all()
}
void CColorBar::onUpdateBuffers_Triangles()
{
  // Already done in onUpdateBuffers_all()
}

uint8_t CColorBar::serializeGetVersion() const { return 2; }
void CColorBar::serializeTo(mrpt::serialization::CArchive& out) const
{
  writeToStreamRender(out);
  // version 0
  out << uint32_t(m_colormap) << m_min_col << m_max_col << m_min_value << m_max_value
      << m_label_format << m_label_font_size;
  CRenderizableShaderTriangles::params_serialize(out);  // v2
}
void CColorBar::serializeFrom(mrpt::serialization::CArchive& in, uint8_t version)
{
  switch (version)
  {
    case 0:
    case 1:
    case 2:
      readFromStreamRender(in);

      in.ReadAsAndCastTo<uint32_t, mrpt::img::TColormap>(m_colormap);
      in >> m_min_col >> m_max_col >> m_min_value >> m_max_value >> m_label_format >>
          m_label_font_size;
      if (version == 0)
      {
        bool old_disable_depth_test;
        in >> old_disable_depth_test;
      }
      if (version >= 2) CRenderizableShaderTriangles::params_deserialize(in);

      break;
    default:
      MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version);
  };
  CRenderizable::notifyChange();
}

mrpt::math::TBoundingBoxf CColorBar::internalBoundingBoxLocal() const
{
  return mrpt::math::TBoundingBoxf::FromUnsortedPoints(
      {.0f, .0f, .0f}, {d2f(m_width), d2f(m_height), .0f});
}
