#ifndef f3d_scene_h
#define f3d_scene_h

#include "exception.h"
#include "export.h"
#include "types.h"

/// @cond
#include <cstddef>
#include <filesystem>
#include <string>
#include <vector>
/// @endcond

namespace f3d
{
/**
 * @class   scene
 * @brief   Class to load files into
 *
 * The scene where files and meshes can be added and loaded into.
 *
 * Example usage:
 * \code{.cpp}
 *  std::string path = ...
 *  f3d::engine eng(f3d::window::Type::NATIVE);
 *  f3d::scene& load = eng.getScene();
 *
 *  if (load.supports(path)
 *  {
 *    load.add(path);
 *  }
 * \endcode
 *
 */
class F3D_EXPORT scene
{
public:
  /**
   * An exception that can be thrown by the scene
   * when it failed to load a file for some reason.
   */
  struct load_failure_exception : public exception
  {
    explicit load_failure_exception(const std::string& what = "")
      : exception(what) {};
  };

  ///@{
  /**
   * Add and load provided files into the scene
   * Already added file will NOT be reloaded
   * If it fails to loads a file, it clears the scene and
   * throw a load_failure_exception.
   * On other failure, throw a load_failure_exception.
   */
  virtual scene& add(const std::filesystem::path& filePath) = 0;
  virtual scene& add(const std::vector<std::filesystem::path>& filePath) = 0;
  virtual scene& add(const std::vector<std::string>& filePathStrings) = 0;
  ///@}

  /**
   * Add and load provided mesh into the scene
   * If it fails to load the mesh, it clears the scene and
   * throw a load_failure_exception.
   * On other failure, throw a load_failure_exception.
   */
  virtual scene& add(const mesh_t& mesh) = 0;

  /**
   * Add and load provided buffer into the scene as it was file.
   * Automatically picks the right reader to use, unless you use
   * VTK < 9.6.20260128, then it requires the use of `scene.force_reader`.
   * If it fails to loads the buffer, it clears the scene and
   * throw a load_failure_exception.
   * On other failure, throw a load_failure_exception.
   */
  virtual scene& add(const std::byte* buffer, std::size_t size) = 0;

  ///@{
  /**
   * Convenience initializer list signature for add method
   */
  scene& add(std::initializer_list<std::string> list)
  {
    return this->add(std::vector<std::string>(list));
  }
  scene& add(std::initializer_list<std::filesystem::path> list)
  {
    return this->add(std::vector<std::filesystem::path>(list));
  }
  ///@}

  /**
   * Clear the scene of all added files
   */
  virtual scene& clear() = 0;

  /**
   * An exception that can be thrown by the scene
   * when it fails to index the light
   */
  struct light_exception : public exception
  {
    explicit light_exception(const std::string& what = "")
      : f3d::exception(what) {};
  };

  /**
   * Add a light based on a light state, returns the index of the added light.
   */
  virtual int addLight(const light_state_t& lightState) const = 0;

  /**
   * Get the number of lights.
   */
  [[nodiscard]] virtual int getLightCount() const = 0;

  /**
   * Get the light state at provided index.
   * light_exception is thrown if the index is invalid.
   */
  [[nodiscard]] virtual light_state_t getLight(int index) const = 0;

  /**
   * Update a light at provided index with the provided light state.
   * light_exception is thrown if the index is invalid.
   */
  virtual scene& updateLight(int index, const light_state_t& lightState) = 0;

  /**
   * Remove a light at provided index.
   * light_exception is thrown if the index is invalid.
   */
  virtual scene& removeLight(int index) = 0;

  /**
   * Remove all lights from the scene.
   */
  virtual scene& removeAllLights() = 0;

  /**
   * Return true if provided file in path uses a supported extension, exists and its content
   * correspond to a supported file format, false otherwise.
   * content validation is only performed with VTK >= 9.6.20260228
   * scene.force_reader is taken into account and plugin should be loaded for their readers to be
   * found.
   */
  [[nodiscard]] virtual bool supports(const std::filesystem::path& filePath) = 0;

  /**
   * Load added files at provided time value if they contain any animation
   * Providing a time value outside of the current animation time range will clamp
   * to the closest value in the range.
   * Does not do anything if there is no animations.
   */
  virtual scene& loadAnimationTime(double timeValue) = 0;

  /**
   * Get animation time range of currently added files.
   * Returns [0, 0] if there is no animations.
   */
  [[nodiscard]] virtual std::pair<double, double> animationTimeRange() = 0;

  /**
   * Get animation keyframe's time of currently added files.
   * Can be used in loadAnimationTime to request a specific keyframe.
   * Returns empty vector if there is no animations.
   */
  [[nodiscard]] virtual std::vector<double> getAnimationKeyFrames() = 0;

  /**
   * Return the number of animations available in the currently loaded files.
   */
  [[nodiscard]] virtual unsigned int availableAnimations() const = 0;

  /**
   * Return the animation name of a given animation index, if any.
   *
   * Specific animation (0..availableAnimations): Returns the name of the animation at that index
   * Current animation (-1):
   *   - Returns the name of the current animation
   *   - Returns "Multi animations" if more than one animation is current
   *   - Returns "All animations" if all animations are current
   *   - Returns "No animations" if no animations are current
   * Fallback: Returns "No animation" for out-of-bounds requests.
   *
   * Can be called before initialization safely
   */
  [[nodiscard]] virtual std::string getAnimationName(int index = -1) = 0;

  /**
   * Return all of the animation names, if any.
   * Returns a vector of length 0 if none.
   * Can be called before initialization safely
   */
  [[nodiscard]] virtual std::vector<std::string> getAnimationNames() = 0;

protected:
  //! @cond
  scene() = default;
  virtual ~scene() = default;
  scene(const scene& opt) = delete;
  scene(scene&& opt) = delete;
  scene& operator=(const scene& opt) = delete;
  scene& operator=(scene&& opt) = delete;
  //! @endcond
};
}

#endif
