// Aseprite Document Library
// Copyright (C) 2018-2025  Igara Studio S.A.
// Copyright (C) 2001-2018  David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.

#ifndef DOC_SPRITE_H_INCLUDED
#define DOC_SPRITE_H_INCLUDED
#pragma once

#include "base/disable_copying.h"
#include "doc/cel_data.h"
#include "doc/cel_list.h"
#include "doc/color.h"
#include "doc/fit_criteria.h"
#include "doc/frame.h"
#include "doc/image_buffer.h"
#include "doc/image_ref.h"
#include "doc/image_spec.h"
#include "doc/layer_list.h"
#include "doc/object.h"
#include "doc/pixel_format.h"
#include "doc/pixel_ratio.h"
#include "doc/rgbmap_algorithm.h"
#include "doc/slices.h"
#include "doc/tags.h"
#include "doc/tile.h"
#include "doc/with_user_data.h"
#include "gfx/rect.h"

#include <memory>
#include <string>
#include <vector>

#define DOC_SPRITE_MAX_WIDTH  65535
#define DOC_SPRITE_MAX_HEIGHT 65535

namespace doc {
class CelsRange;
class Document;
class Image;
class Layer;
class LayerGroup;
class LayerImage;
class Mask;
class Palette;
class Remap;
class RenderPlan;
class RgbMap;
class RgbMapRGB5A3;
class SelectedFrames;
class Tileset;
class Tilesets;

typedef std::vector<Palette*> PalettesList;

// The main structure used in the whole program to handle a sprite.
class Sprite : public WithUserData {
public:
  enum class RgbMapFor { OpaqueLayer, TransparentLayer };

  ////////////////////////////////////////
  // Constructors/Destructor

  Sprite(const ImageSpec& spec, int ncolors = 256);
  virtual ~Sprite();

  // Creates a new sprite with one transparent layer and one cel
  // with an image of the size of the sprite.
  static Sprite* MakeStdSprite(const ImageSpec& spec,
                               const int ncolors = 256,
                               const ImageBufferPtr& imageBuf = ImageBufferPtr());
  // Creates a new sprite with one tilemap layer and one cel
  // with a tilemap's image of the size specified by tilemapspec and the
  // given tileset.
  static Sprite* MakeStdTilemapSpriteWithTileset(const ImageSpec& spec,
                                                 const ImageSpec& tilemapspec,
                                                 const Tileset& tileset,
                                                 const int ncolors = 256,
                                                 const ImageBufferPtr& imageBuf = ImageBufferPtr());

  ////////////////////////////////////////
  // Main properties

  const ImageSpec& spec() const { return m_spec; }

  Document* document() const { return m_document; }
  void setDocument(Document* doc) { m_document = doc; }

  PixelFormat pixelFormat() const { return (PixelFormat)m_spec.colorMode(); }
  ColorMode colorMode() const { return m_spec.colorMode(); }
  const PixelRatio& pixelRatio() const { return m_pixelRatio; }
  bool hasPixelRatio() const;
  gfx::Size size() const { return m_spec.size(); }
  gfx::Rect bounds() const { return m_spec.bounds(); }
  int width() const { return m_spec.width(); }
  int height() const { return m_spec.height(); }
  const gfx::ColorSpaceRef& colorSpace() const { return m_spec.colorSpace(); }

  void setPixelFormat(PixelFormat format);
  void setPixelRatio(const PixelRatio& pixelRatio);
  void setSize(int width, int height);
  void setColorSpace(const gfx::ColorSpaceRef& colorSpace);

  // This method is only required/used for the template functions
  // app::script::UserData_set_text/color.
  Sprite* sprite() const { return const_cast<Sprite*>(this); }

  // Returns true if the sprite has a background layer and it's visible
  bool isOpaque() const;

  // Returns true if the rendered images will contain alpha values less
  // than 255. Only RGBA and Grayscale images without background needs
  // alpha channel in the render.
  bool needAlpha() const;
  bool supportAlpha() const;

  color_t transparentColor() const { return m_spec.maskColor(); }
  void setTransparentColor(color_t color);

  // Defaults
  static gfx::Rect DefaultGridBounds();
  static void SetDefaultGridBounds(const gfx::Rect& defGridBounds);
  static RgbMapAlgorithm DefaultRgbMapAlgorithm();
  static void SetDefaultRgbMapAlgorithm(const RgbMapAlgorithm mapAlgo);

  const gfx::Rect& gridBounds() const { return m_gridBounds; }
  void setGridBounds(const gfx::Rect& rc)
  {
    m_gridBounds = rc;
    // Prevent setting an empty grid bounds
    if (m_gridBounds.w <= 0)
      m_gridBounds.w = 1;
    if (m_gridBounds.h <= 0)
      m_gridBounds.h = 1;
  }

  virtual int getMemSize() const override;

  ////////////////////////////////////////
  // Layers

  LayerGroup* root() const { return m_root; }
  LayerImage* backgroundLayer() const;
  Layer* firstBrowsableLayer() const;
  Layer* firstLayer() const;
  layer_t allLayersCount() const;
  bool hasVisibleReferenceLayers() const;

  ////////////////////////////////////////
  // Palettes

  Palette* palette(frame_t frame) const;
  const PalettesList& getPalettes() const;

  void setPalette(const Palette* pal, bool truncate);

  // Removes all palettes from the sprites except the first one.
  void resetPalettes();

  void deletePalette(frame_t frame);

  RgbMapFor rgbMapForSprite() const;
  RgbMap* rgbMap(const frame_t frame) const;
  RgbMap* rgbMap(const frame_t frame, const RgbMapFor forLayer) const;
  RgbMap* rgbMap(const frame_t frame,
                 const RgbMapFor forLayer,
                 const RgbMapAlgorithm mapAlgo,
                 const FitCriteria fitCriteria = FitCriteria::DEFAULT) const;

  ////////////////////////////////////////
  // Frames

  frame_t totalFrames() const { return m_frames; }
  frame_t lastFrame() const { return m_frames - 1; }

  void addFrame(frame_t newFrame);
  void removeFrame(frame_t frame);
  void setTotalFrames(frame_t frames);

  int frameDuration(frame_t frame) const;
  int totalAnimationDuration() const;
  void setFrameDuration(frame_t frame, int msecs);
  void setFrameRangeDuration(frame_t from, frame_t to, int msecs);
  void setDurationForAllFrames(int msecs);

  const Tags& tags() const { return m_tags; }
  Tags& tags() { return m_tags; }

  const Slices& slices() const { return m_slices; }
  Slices& slices() { return m_slices; }

  ////////////////////////////////////////
  // Shared Images and CelData (for linked Cels)

  ImageRef getImageRef(ObjectId imageId);
  CelDataRef getCelDataRef(ObjectId celDataId);

  ////////////////////////////////////////
  // Images

  void replaceImage(ObjectId curImageId, const ImageRef& newImage);
  void replaceTileset(tileset_index tsi, Tileset* newTileset);

  // Returns all sprite images (cel + tiles) that aren't tilemaps
  void getImages(std::vector<ImageRef>& images) const;

  // TODO replace this with a co-routine when we start using C++20 (std::generator<ImageRef>)
  void getTilemapsByTileset(const Tileset* tileset, std::vector<ImageRef>& images) const;

  void remapImages(const Remap& remap);
  void remapTilemaps(const Tileset* tileset, const Remap& remap);
  void pickCels(const gfx::PointF& pos,
                const int opacityThreshold,
                const RenderPlan& plan,
                CelList& cels) const;

  ////////////////////////////////////////
  // Iterators

  LayerList allLayers() const;
  LayerList allVisibleLayers() const;
  LayerList allVisibleReferenceLayers() const;
  LayerList allBrowsableLayers() const;
  LayerList allTilemaps() const;
  std::string visibleLayerHierarchyAsString() const;

  CelsRange cels() const;
  CelsRange cels(frame_t frame) const;
  CelsRange cels(const SelectedFrames& selFrames) const;
  CelsRange uniqueCels() const;
  CelsRange uniqueCels(const SelectedFrames& selFrames) const;

  ////////////////////////////////////////
  // Tilesets

  bool hasTilesets() const { return m_tilesets != nullptr; }
  Tilesets* tilesets() const;

  const std::string& tileManagementPlugin() const { return m_tileManagementPlugin; }

  bool hasTileManagementPlugin() const { return !m_tileManagementPlugin.empty(); }

  void setTileManagementPlugin(const std::string& plugin) { m_tileManagementPlugin = plugin; }

  void useLayerUuids(bool value) { m_useLayerUuids = value; }
  bool useLayerUuids() const { return m_useLayerUuids; }

private:
  Document* m_document;
  ImageSpec m_spec;
  PixelRatio m_pixelRatio;
  frame_t m_frames;          // how many frames has this sprite
  std::vector<int> m_frlens; // duration per frame
  PalettesList m_palettes;   // list of palettes
  LayerGroup* m_root;        // main group of layers
  gfx::Rect m_gridBounds;    // grid settings

  // Current rgb map
  mutable std::unique_ptr<RgbMap> m_rgbMap;

  Tags m_tags;
  Slices m_slices;

  // Tilesets
  mutable Tilesets* m_tilesets;

  // Custom tile management plugin. This can be an ID that specifies
  // a custom plugin that will be used to handle tilesets and
  // tilemaps for this specific sprite. This property is saved
  // inside .aseprite files (ASE_EXTERNAL_FILE_TILE_MANAGEMENT), and
  // it's used by the UI to disable the standard tileset/tilemap UX
  // (e.g. drag & drop tiles, or TilesetMode::Auto mode, etc.),
  // giving the possibility to handle tiles exclusively to a plugin.
  std::string m_tileManagementPlugin;

  // This setting indicates if the layers of this sprite are using UUIDs.
  bool m_useLayerUuids = false;

  // Disable default constructor and copying
  Sprite();
  DISABLE_COPYING(Sprite);
};

} // namespace doc

#endif
