/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef MOZILLA_GFX_SCALEFACTORS2D_H_
#define MOZILLA_GFX_SCALEFACTORS2D_H_

#include <ostream>
#include <tuple>

#include "mozilla/FloatingPoint.h"
#include "mozilla/gfx/ScaleFactor.h"
#include "mozilla/gfx/Point.h"

#include "gfxPoint.h"

namespace mozilla {
namespace gfx {

/*
 * This class is like ScaleFactor, but allows different scales on the x and
 * y axes.
 */
template <class Src, class Dst, class T>
struct BaseScaleFactors2D {
  T xScale;
  T yScale;

  constexpr BaseScaleFactors2D() : xScale(1.0), yScale(1.0) {}
  constexpr BaseScaleFactors2D(const BaseScaleFactors2D& aCopy)
      : xScale(aCopy.xScale), yScale(aCopy.yScale) {}
  constexpr BaseScaleFactors2D(T aXScale, T aYScale)
      : xScale(aXScale), yScale(aYScale) {}
  // Layout code often uses gfxSize to represent a pair of x/y scales.
  explicit constexpr BaseScaleFactors2D(const gfxSize& aSize)
      : xScale(aSize.width), yScale(aSize.height) {}

  // "Upgrade" from a ScaleFactor.
  // This is deliberately 'explicit' so that the treatment of a single scale
  // number as both the x- and y-scale in a context where they are allowed to
  // be different, is more visible.
  explicit constexpr BaseScaleFactors2D(const ScaleFactor<Src, Dst>& aScale)
      : xScale(aScale.scale), yScale(aScale.scale) {}

  bool AreScalesSame() const {
    return FuzzyEqualsMultiplicative(xScale, yScale);
  }

  // Convert the underlying floating point type storing the scale factors
  // to that of NewT.
  template <typename NewT>
  BaseScaleFactors2D<Src, Dst, NewT> ConvertTo() const {
    return BaseScaleFactors2D<Src, Dst, NewT>(NewT(xScale), NewT(yScale));
  }

  // Convert to a ScaleFactor. Asserts that the scales are, in fact, equal.
  ScaleFactor<Src, Dst> ToScaleFactor() const {
    // Avoid implicit narrowing from double to float. An explicit conversion
    // may be done with `scales.ConvertTo<float>().ToScaleFactor()` if desired.
    static_assert(std::is_same_v<T, float>);
    MOZ_ASSERT(AreScalesSame());
    return ScaleFactor<Src, Dst>(xScale);
  }

  // Convert to a SizeTyped. Eventually, we should replace all uses of SizeTyped
  // to represent scales with ScaleFactors2D, and remove this function.
  SizeTyped<UnknownUnits, T> ToSize() const {
    return SizeTyped<UnknownUnits, T>(xScale, yScale);
  }

  BaseScaleFactors2D& operator=(const BaseScaleFactors2D&) = default;

  bool operator==(const BaseScaleFactors2D& aOther) const {
    return xScale == aOther.xScale && yScale == aOther.yScale;
  }

  bool operator!=(const BaseScaleFactors2D& aOther) const {
    return !(*this == aOther);
  }

  friend std::ostream& operator<<(std::ostream& aStream,
                                  const BaseScaleFactors2D& aScale) {
    if (aScale.AreScalesSame()) {
      return aStream << aScale.xScale;
    } else {
      return aStream << '(' << aScale.xScale << ',' << aScale.yScale << ')';
    }
  }

  template <class Other>
  BaseScaleFactors2D<Other, Dst, T> operator/(
      const BaseScaleFactors2D<Src, Other, T>& aOther) const {
    return BaseScaleFactors2D<Other, Dst, T>(xScale / aOther.xScale,
                                             yScale / aOther.yScale);
  }

  template <class Other>
  BaseScaleFactors2D<Src, Other, T> operator/(
      const BaseScaleFactors2D<Other, Dst, T>& aOther) const {
    return BaseScaleFactors2D<Src, Other, T>(xScale / aOther.xScale,
                                             yScale / aOther.yScale);
  }

  template <class Other>
  BaseScaleFactors2D<Src, Other, T> operator*(
      const BaseScaleFactors2D<Dst, Other, T>& aOther) const {
    return BaseScaleFactors2D<Src, Other, T>(xScale * aOther.xScale,
                                             yScale * aOther.yScale);
  }

  template <class Other>
  BaseScaleFactors2D<Other, Dst, T> operator*(
      const BaseScaleFactors2D<Other, Src, T>& aOther) const {
    return BaseScaleFactors2D<Other, Dst, T>(xScale * aOther.xScale,
                                             yScale * aOther.yScale);
  }

  BaseScaleFactors2D<Src, Src, T> operator*(
      const BaseScaleFactors2D<Dst, Src, T>& aOther) const {
    return BaseScaleFactors2D<Src, Src, T>(xScale * aOther.xScale,
                                           yScale * aOther.yScale);
  }

  template <class Other>
  BaseScaleFactors2D<Src, Other, T> operator*(
      const ScaleFactor<Dst, Other>& aOther) const {
    return *this * BaseScaleFactors2D<Dst, Other, T>(aOther);
  }

  template <class Other>
  BaseScaleFactors2D<Other, Dst, T> operator*(
      const ScaleFactor<Other, Src>& aOther) const {
    return *this * BaseScaleFactors2D<Other, Src, T>(aOther);
  }

  BaseScaleFactors2D<Src, Src, T> operator*(
      const ScaleFactor<Dst, Src>& aOther) const {
    return *this * BaseScaleFactors2D<Dst, Src, T>(aOther);
  }

  template <class Other>
  BaseScaleFactors2D<Src, Other, T> operator/(
      const ScaleFactor<Other, Dst>& aOther) const {
    return *this / BaseScaleFactors2D<Other, Dst, T>(aOther);
  }

  template <class Other>
  BaseScaleFactors2D<Other, Dst, T> operator/(
      const ScaleFactor<Src, Other>& aOther) const {
    return *this / BaseScaleFactors2D<Src, Other, T>(aOther);
  }

  template <class Other>
  friend BaseScaleFactors2D<Other, Dst, T> operator*(
      const ScaleFactor<Other, Src>& aA, const BaseScaleFactors2D& aB) {
    return BaseScaleFactors2D<Other, Src, T>(aA) * aB;
  }

  template <class Other>
  friend BaseScaleFactors2D<Other, Src, T> operator/(
      const ScaleFactor<Other, Dst>& aA, const BaseScaleFactors2D& aB) {
    return BaseScaleFactors2D<Other, Src, T>(aA) / aB;
  }

  static BaseScaleFactors2D<Src, Dst, T> FromUnknownScale(
      const BaseScaleFactors2D<UnknownUnits, UnknownUnits, T>& scale) {
    return BaseScaleFactors2D<Src, Dst, T>(scale.xScale, scale.yScale);
  }

  BaseScaleFactors2D<UnknownUnits, UnknownUnits, T> ToUnknownScale() const {
    return BaseScaleFactors2D<UnknownUnits, UnknownUnits, T>(xScale, yScale);
  }

  friend BaseScaleFactors2D Min(const BaseScaleFactors2D& aA,
                                const BaseScaleFactors2D& aB) {
    return BaseScaleFactors2D(std::min(aA.xScale, aB.xScale),
                              std::min(aA.yScale, aB.yScale));
  }

  friend BaseScaleFactors2D Max(const BaseScaleFactors2D& aA,
                                const BaseScaleFactors2D& aB) {
    return BaseScaleFactors2D(std::max(aA.xScale, aB.xScale),
                              std::max(aA.yScale, aB.yScale));
  }

  auto MutTiedFields() { return std::tie(xScale, yScale); }
};

template <class Src, class Dst>
using ScaleFactors2D = BaseScaleFactors2D<Src, Dst, float>;

template <class Src, class Dst>
using ScaleFactors2DDouble = BaseScaleFactors2D<Src, Dst, double>;

}  // namespace gfx
}  // namespace mozilla

#endif /* MOZILLA_GFX_SCALEFACTORS2D_H_ */
