/* SPDX-FileCopyrightText: 2025 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/**
 * Functionality internal to the GPU binder implementations. It is not to be used outside of this
 * module. Subclasses of the GPUShaderBinder are allowed to access these types.
 *
 * Implementation is in gpu_shader_binder.cc (implementation file of the public
 * OCIO_gpu_shader_binder.hh).
 */

#pragma once

#include <list>

#include "BLI_string_ref.hh"
#include "BLI_utility_mixins.hh"
#include "BLI_vector.hh"

#include "ocio_shader_shared.hh"

#if defined(WITH_OPENCOLORIO)
#  include "opencolorio.hh"
#endif

struct CurveMapping;
struct GPUUniformBuf;
struct GPUShader;
struct GPUTexture;

namespace blender::ocio {

struct GPUDisplayParameters;

namespace internal {

/* Namespaces mnemonic index for texture slot that can be passed as integer argument. */
struct TextureSlot {
  enum {
    IMAGE = 0,
    OVERLAY = 1,
    CURVE_MAPPING = 2,
    LUTS_OFFSET = 3,
  };
};

/* Namespaces mnemonic index for uniform buffer slot that can be passed as integer argument. */
struct UniformBufferSlot {
  enum {
    DISPLAY = 0,
    CURVE_MAPPING = 1,
    LUTS = 2,
  };
};

struct GPULutTexture {
  GPUTexture *texture = nullptr;
  std::string sampler_name;
};

struct GPUUniform {
  std::string name;

  /* They are only required for processors generated by the OpenColorIO. For the simplicity of the
   * internal API use OpenColorIO type (avoiding making extra copies of the data coming from the
   * OpenColorIO library). */
#if defined(WITH_OPENCOLORIO)
  OCIO_NAMESPACE::GpuShaderDesc::UniformData data;
#endif
};

class GPUTextures : NonCopyable, NonMovable {
 public:
  Vector<GPULutTexture> luts;

  /* Dummy in case of no overlay. */
  GPUTexture *dummy = nullptr;

  /* Uniforms */
  Vector<GPUUniform> uniforms;
  GPUUniformBuf *uniforms_buffer = nullptr;

  ~GPUTextures();

  /**
   * Initialize common parts of this object: resources needed for both fall-back and OpenColorIO
   * implementations.
   *
   * Returns true if the resources have been successfully allocated.
   */
  bool initialize_common();
};

class GPUCurveMappping : NonCopyable, NonMovable {
 public:
  int lut_size = 0;
  float *lut = nullptr;

  GPUUniformBuf *buffer = nullptr;
  GPUTexture *texture = nullptr;
  size_t cache_id = 0;

  ~GPUCurveMappping();

  /**
   * Rasterize given curve mapping into a lookup table.
   */
  void rasterize(CurveMapping &curve_mapping);

  /**
   * Initialize common parts of this object: resources needed for both fall-back and OpenColorIO
   * implementations.
   *
   * Requires rasterization to happen prior to this call.
   *
   * Returns true if the resources have been successfully allocated.
   */
  bool initialize_common(bool use_curve_mapping);
};

class GPUDisplayShader : NonCopyable, NonMovable {
 public:
  /* Cache variables. */
  std::string from_colorspace;
  std::string view;
  std::string display;
  std::string look;
  bool use_curve_mapping = false;

  /* The shader is valid and can be bound.
   * Note that the cache might contain invalid shaders to prevent Blender from attempting to keep
   * re-trying to build the same failing shader. */
  bool is_valid = false;

  GPUShader *shader = nullptr;

  /* Uniform parameters. */
  OCIO_GPUParameters parameters = {};
  GPUUniformBuf *parameters_buffer = nullptr;

  GPUTextures textures;
  GPUCurveMappping curve_mapping;

  ~GPUDisplayShader();

  bool matches(const GPUDisplayParameters &display_parameters) const;

  /**
   * Initialize common parts of this object: resources needed for both fall-back and OpenColorIO
   * implementations.
   *
   * If the resources creation has failed sets is_valid to false, otherwise keeps is_valid
   * unchanged.
   *
   * Returns true if the resources have been successfully allocated.
   */
  bool initialize_common();
};

class GPUShaderCache {
  /* The maximum number of cached shaders. */
  static constexpr int MAX_SIZE = 4;

 public:
  ~GPUShaderCache();

  /**
   * Get shader from the cache if exists, tag it as the most recently used for a faster lookup in
   * the future.
   */
  GPUDisplayShader *get(const GPUDisplayParameters &display_parameters);

  /**
   * Create default-initialized GPUDisplayShader and put it to cache.
   * The function ensures the cache has up to SHADER_CACHE_MAX_SIZE entries.
   */
  GPUDisplayShader &create_default();

  /**
   * Remove all elements from the cache.
   */
  void clear();

 private:
  std::list<GPUDisplayShader> cache_;
};

}  // namespace internal
}  // namespace blender::ocio
