// Aseprite Render Library
// Copyright (c) 2020-2025 Igara Studio S.A.
// Copyright (c) 2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.

#ifndef RENDER_PROJECTION_H_INCLUDED
#define RENDER_PROJECTION_H_INCLUDED
#pragma once

#include "doc/pixel_ratio.h"
#include "gfx/matrix.h"
#include "render/zoom.h"

namespace render {

class Projection {
public:
  Projection() : m_pixelRatio(1, 1), m_zoom(1, 1) {}

  Projection(const doc::PixelRatio& pixelRatio, const Zoom& zoom)
    : m_pixelRatio(pixelRatio)
    , m_zoom(zoom)
  {
  }

  const doc::PixelRatio& pixelRatio() const { return m_pixelRatio; }
  const Zoom& zoom() const { return m_zoom; }

  void setPixelRatio(const doc::PixelRatio& pixelRatio) { m_pixelRatio = pixelRatio; }
  void setZoom(const Zoom& zoom) { m_zoom = zoom; }

  // To identify simplest composite scale up cases'
  bool isSimpleScaleUpCase() const
  {
    return scaleX() >= 1.0 && scaleY() >= 1.0 && (m_pixelRatio.w == 1 || m_pixelRatio.w % 2 == 0) &&
           (m_pixelRatio.h == 1 || m_pixelRatio.h % 2 == 0);
  }

  // To identify simplest composite scale down cases
  bool isSimpleScaleDownCase() const
  {
    return scaleX() <= 1.0 && scaleY() <= 1.0 && std::fmod(1.0 / scaleX(), 1.0) == 0.0 &&
           std::fmod(1.0 / scaleY(), 1.0) == 0.0;
  }

  double scaleX() const { return m_zoom.scale() * m_pixelRatio.w; }
  double scaleY() const { return m_zoom.scale() * m_pixelRatio.h; }

  template<typename T>
  T applyX(T x) const
  {
    return m_zoom.apply<T>(x * T(m_pixelRatio.w));
  }

  template<typename T>
  T applyY(T y) const
  {
    return m_zoom.apply<T>(y * T(m_pixelRatio.h));
  }

  template<typename T>
  T removeX(T x) const
  {
    return m_zoom.remove<T>(x) / T(m_pixelRatio.w);
  }

  template<typename T>
  T removeY(T y) const
  {
    return m_zoom.remove<T>(y) / T(m_pixelRatio.h);
  }

  template<typename T>
  T removeXCeiling(T x) const
  {
    return T(m_zoom.removeCeiling(x)) / T(m_pixelRatio.w);
  }

  template<typename T>
  T removeYCeiling(T y) const
  {
    return T(m_zoom.removeCeiling(y)) / T(m_pixelRatio.h);
  }

  // Used in 'editor.cpp' to do some math between x,y values.
  // Useful for calculating diagonal symmetry axis positions when pixel ratio is other than 1:1
  template<typename T>
  T turnXinTermsOfY(T x) const
  {
    return x * T(m_pixelRatio.h) / T(m_pixelRatio.w);
  }

  template<typename T>
  T turnYinTermsOfX(T y) const
  {
    return y * T(m_pixelRatio.w) / T(m_pixelRatio.h);
  }

  gfx::Rect apply(const gfx::Rect& r) const
  {
    int u = applyX(r.x);
    int v = applyY(r.y);
    return gfx::Rect(u, v, applyX(r.x + r.w) - u, applyY(r.y + r.h) - v);
  }

  gfx::RectF apply(const gfx::RectF& r) const
  {
    double u = applyX(r.x);
    double v = applyY(r.y);
    return gfx::RectF(u, v, applyX(r.x + r.w) - u, applyY(r.y + r.h) - v);
  }

  gfx::Rect remove(const gfx::Rect& r) const
  {
    int u = removeX(r.x);
    int v = removeY(r.y);
    return gfx::Rect(u, v, removeX(r.x + r.w) - u, removeY(r.y + r.h) - v);
  }

  gfx::RectF remove(const gfx::RectF& r) const
  {
    double u = removeX(r.x);
    double v = removeY(r.y);
    return gfx::RectF(u, v, removeX(r.x + r.w) - u, removeY(r.y + r.h) - v);
  }

  gfx::Matrix scaleMatrix() const { return gfx::Matrix::MakeScale(scaleX(), scaleY()); }

private:
  doc::PixelRatio m_pixelRatio;
  Zoom m_zoom;
};

} // namespace render

#endif
