// Aseprite
// Copyright (C) 2019-2024  Igara Studio S.A.
// Copyright (C) 2001-2018  David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.

#ifndef APP_UI_EDITOR_PIXELS_MOVEMENT_H_INCLUDED
#define APP_UI_EDITOR_PIXELS_MOVEMENT_H_INCLUDED
#pragma once

#include "app/context_access.h"
#include "app/extra_cel.h"
#include "app/site.h"
#include "app/transformation.h"
#include "app/tx.h"
#include "app/ui/editor/handle_type.h"
#include "doc/algorithm/flip_type.h"
#include "doc/frame.h"
#include "doc/image_ref.h"
#include "gfx/size.h"
#include "obs/connection.h"

#include <memory>

namespace doc {
class Image;
class Mask;
class Sprite;
} // namespace doc

namespace app {
class Doc;
class TiledModeHelper;

namespace cmd {
class SetMask;
}

class PixelsMovementDelegate {
public:
  virtual ~PixelsMovementDelegate() {}
  virtual void onPivotChange() = 0;
};

// Helper class to move pixels interactively and control undo history
// correctly.  The extra cel of the sprite is temporally used to show
// feedback, drag, and drop the specified image in the constructor
// (which generally would be the selected region or the clipboard
// content).
class PixelsMovement {
public:
  enum MoveModifier {
    NormalMovement = 1,
    SnapToGridMovement = 2,
    AngleSnapMovement = 4,
    MaintainAspectRatioMovement = 8,
    LockAxisMovement = 16,
    ScaleFromPivot = 32,
    FineControl = 64,
  };

  enum CommitChangesOption {
    DontCommitChanges,
    CommitChanges,
  };

  enum KeepMaskOption {
    DontKeepMask,
    KeepMask,
  };

  PixelsMovement(Context* context,
                 Site site,
                 const Image* moveThis,
                 const Mask* mask,
                 const char* operationName,
                 const TiledModeHelper* tiledModeHelper = nullptr);
  ~PixelsMovement();

  const Site& site() { return m_site; }

  HandleType handle() const { return m_handle; }
  bool canHandleFrameChange() const { return m_canHandleFrameChange; }

  void setDelegate(PixelsMovementDelegate* delegate);
  void setFastMode(const bool fastMode);

  void trim();
  void cutMask();
  void copyMask();
  void catchImage(const gfx::PointF& pos, HandleType handle);
  void catchImageAgain(const gfx::PointF& pos, HandleType handle);

  // Moves the image to the new position (relative to the start
  // position given in the ctor).
  void moveImage(const gfx::PointF& pos, MoveModifier moveModifier);

  // Returns a copy of the current image being dragged with the
  // current transformation.
  void getDraggedImageCopy(std::unique_ptr<Image>& outputImage, std::unique_ptr<Mask>& outputMask);

  // Copies the image being dragged in the current position.
  void stampImage();

  void dropImageTemporarily();
  void dropImage();
  void discardImage(const CommitChangesOption commit = CommitChanges,
                    const KeepMaskOption keepMask = DontKeepMask);
  bool isDragging() const;

  gfx::Rect getImageBounds();
  gfx::Size getInitialImageSize() const;

  void setMaskColor(bool opaque, color_t mask_color);

  // Flips the image and mask in the given direction in "flipType".
  // Flip Horizontally/Vertically commands are replaced calling this
  // function, so they work more as the user would expect (flip the
  // current selection instead of dropping and flipping it).
  void flipImage(doc::algorithm::FlipType flipType);

  // Rotates the image and the mask the given angle. It's used to
  // simulate RotateCommand when we're inside MovingPixelsState.
  void rotate(double angle);

  void shift(int dx, int dy);

  // Navigate frames
  bool gotoFrame(const doc::frame_t deltaFrame);

  const Transformation& getTransformation() const { return m_currentData; }
  void setTransformation(const Transformation& t);

private:
  void setTransformationBase(const Transformation& t);
  void adjustPivot();
  bool editMultipleCels() const;
  void stampImage(bool finalStamp);
  void stampExtraCelImage();
  void onPivotChange();
  void onRotationAlgorithmChange();
  void redrawExtraImage(Transformation* transformation = nullptr);
  void redrawCurrentMask();
  void drawImage(const Transformation& transformation,
                 doc::Image* dst,
                 const gfx::PointF& pt,
                 const bool renderOriginalLayer);
  void drawMask(doc::Mask* dst, bool shrink);
  void drawParallelogram(const Transformation& transformation,
                         doc::Image* dst,
                         const doc::Image* src,
                         const doc::Mask* mask,
                         const Transformation::Corners& corners,
                         const gfx::PointF& leftTop);
  void drawTransformedTilemap(const Transformation& transformation,
                              doc::Image* dst,
                              const doc::Image* src,
                              const doc::Mask* mask);
  void updateDocumentMask();
  void hideDocumentMask();

  void flipOriginalImage(const doc::algorithm::FlipType flipType);
  void shiftOriginalImage(const int dx, const int dy, const double angle);
  CelList getEditableCels();
  void reproduceAllTransformationsWithInnerCmds();

  void alignMasksAndTransformData(const Mask* initialMask0,
                                  const Mask* initialMask,
                                  const Mask* currentMask,
                                  const Transformation* initialData,
                                  const Transformation* currentData,
                                  const doc::Grid& grid,
                                  const gfx::Size& deltaA,
                                  const gfx::Size& deltaB);

#if _DEBUG
  void dumpInnerCmds();
#endif

  PixelsMovementDelegate* m_delegate = nullptr;
  const ContextReader m_reader;
  Site m_site;
  Doc* m_document;
  Tx m_tx;
  bool m_isDragging;
  bool m_adjustPivot;
  HandleType m_handle;
  doc::ImageRef m_originalImage;
  gfx::PointF m_catchPos;
  Transformation m_initialData;
  Transformation m_currentData;
  std::unique_ptr<Mask> m_initialMask, m_initialMask0;
  std::unique_ptr<Mask> m_currentMask;
  bool m_opaque;
  color_t m_maskColor;
  const TiledModeHelper* m_tiledModeHelper = nullptr;
  obs::scoped_connection m_pivotVisConn;
  obs::scoped_connection m_pivotPosConn;
  obs::scoped_connection m_rotAlgoConn;
  ExtraCelRef m_extraCel;
  bool m_canHandleFrameChange;

  // Fast mode is used to give a faster feedback to the user
  // avoiding RotSprite on each mouse movement.
  bool m_fastMode;
  bool m_needsRotSpriteRedraw;

  // Commands used in the interaction with the transformed pixels.
  // This is used to re-create the whole interaction on each
  // modified cel when we are modifying multiples cels at the same
  // time, or also to re-create it when we switch to another frame.
  struct InnerCmd {
    enum Type { None, Clear, Flip, Shift, Stamp } type;
    union {
      struct {
        doc::algorithm::FlipType type;
      } flip;
      struct {
        int dx, dy;
        double angle;
      } shift;
      struct {
        Transformation* transformation;
      } stamp;
    } data;
    InnerCmd() : type(None) {}
    InnerCmd(InnerCmd&&);
    ~InnerCmd();
    InnerCmd(const InnerCmd&) = delete;
    InnerCmd& operator=(const InnerCmd&) = delete;
    static InnerCmd MakeClear();
    static InnerCmd MakeFlip(const doc::algorithm::FlipType flipType);
    static InnerCmd MakeShift(const int dx, const int dy, const double angle);
    static InnerCmd MakeStamp(const Transformation& t);
  };
  std::vector<InnerCmd> m_innerCmds;
};

inline PixelsMovement::MoveModifier& operator|=(PixelsMovement::MoveModifier& a,
                                                const PixelsMovement::MoveModifier& b)
{
  a = static_cast<PixelsMovement::MoveModifier>(a | b);
  return a;
}

typedef std::shared_ptr<PixelsMovement> PixelsMovementPtr;

} // namespace app

#endif
