/* +------------------------------------------------------------------------+
   |                     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          |
   +------------------------------------------------------------------------+ */
#pragma once

#include <mrpt/containers/PerThreadDataHolder.h>
#include <mrpt/img/CImage.h>

#include <array>
#include <optional>

namespace mrpt::opengl
{
using texture_name_t = unsigned int;
using texture_unit_t = int;  //!< the "i" in GL_TEXTUREi

/** Texture "name" and "unit". \sa Texture \ingroup mrpt_opengl_grp */
struct texture_name_unit_t
{
  texture_name_unit_t() = default;
  texture_name_unit_t(texture_name_t Name, texture_unit_t Unit = 0) : name(Name), unit(Unit) {}

  texture_name_t name = 0;
  texture_unit_t unit = 0;  //!< the "i" in GL_TEXTUREi
};

/** Resource management for OpenGL 2D or Cube textures.
 *
 * The texture is generated when images are assigned via
 * assignImage2D() or assignCubeImages().
 *
 * \sa CRenderizableShaderTexturedTriangles
 * \ingroup mrpt_opengl_grp
 */
class Texture
{
 public:
  Texture() = default;
  ~Texture() = default;

  enum class Wrapping : uint8_t
  {
    Repeat = 0,
    MirroredRepeat,
    ClampToEdge,
    ClapToBorder
  };

  /// Options while creating a texture from an image.
  struct Options
  {
    Options() = default;

    bool generateMipMaps = true;

    /** If set (true), interpolation will happen when getting closer to the
     * texture (magnify). Otherwise (false), it will be not interpolated, so
     * each pixel will be rendered as a square box with constant color (e.g.
     * suitable for gridmaps) */
    bool magnifyLinearFilter = true;

    bool enableTransparency = false;

    /** How to repeat texture coordinate "S" */
    Wrapping wrappingModeS = Wrapping::Repeat;

    /** How to repeat texture coordinate "T" */
    Wrapping wrappingModeT = Wrapping::Repeat;

    /** If enabled, 16-bit images will be automatically bright-adjusted so they are visible
     *  as 8-bit image textures. Set to false to disable adjustment.
     */
    bool autoScale16to8bitConversion = true;
  };

  /** This is how an 2D texture image is loaded into this object, and a
   * texture ID is generated underneath. Valid image formats are 8bit per
   * channel RGB or RGBA.
   */
  void assignImage2D(const mrpt::img::CImage& rgb, const Options& o, int textureUnit = 0);

  /// \overload With alpha (transparency) channel as an independent image
  void assignImage2D(
      const mrpt::img::CImage& rgb,
      const mrpt::img::CImage& alpha,
      const Options& o,
      int textureUnit = 0);

  /** This is how an Cube texture is loaded into this object, and a
   * texture ID is generated underneath. Valid image formats are 8bit per
   * channel RGB or RGBA.
   *
   * Indices of faces in the array follow the numeric ordering of
   * mrpt::opengl::CUBE_TEXTURE_FACE values.
   */
  void assignCubeImages(const std::array<mrpt::img::CImage, 6>& imgs, int textureUnit = 0);

  /** Returns true if an image has been already assigned and an OpenGL
   * texture ID was already generated. */
  bool initialized() const;

  /** Binds the texture to GL_TEXTURE_2D */
  void bindAsTexture2D();

  /** Binds the texture to GL_TEXTURE_CUBE_MAP */
  void bindAsCubeTexture();

  void unloadTexture();

  /**  Texture unit = the "i" in GL_TEXTUREi */
  texture_unit_t textureUnit() const { return m_tex.get()->unit; }
  texture_name_t textureNameID() const { return m_tex.get()->name; }

 private:
  template <class T>
  using ptdh = mrpt::containers::PerThreadDataHolder<T>;

  mutable ptdh<std::optional<texture_name_unit_t>> m_tex;

  const auto& get() const { return m_tex.get(); }
  auto& get() { return m_tex.get(); }

  void internalAssignImage_2D(
      const mrpt::img::CImage* in_rgb,
      const mrpt::img::CImage* in_alpha,
      const Options& o,
      int textureUnit);
};

// Normally users should not need to call these, but they are exposed just in
// case they are useful someday.
texture_name_t getNewTextureNumber(const uint8_t* optionalRgbDataForAssociation);
void releaseTextureName(const texture_name_t& t);

}  // namespace mrpt::opengl
