/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */

#include "mozilla/dom/WebKitCSSMatrix.h"

#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/WebKitCSSMatrixBinding.h"
#include "nsCSSParser.h"
#include "nsStyleTransformMatrix.h"

namespace mozilla {
namespace dom {

static const double sRadPerDegree = 2.0 * M_PI / 360.0;

bool
WebKitCSSMatrix::FeatureEnabled(JSContext* aCx, JSObject* aObj)
{
  return Preferences::GetBool("layout.css.DOMMatrix.enabled", false) &&
         Preferences::GetBool("layout.css.prefixes.webkit", false);
}

already_AddRefed<WebKitCSSMatrix>
WebKitCSSMatrix::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
{
  RefPtr<WebKitCSSMatrix> obj = new WebKitCSSMatrix(aGlobal.GetAsSupports());
  return obj.forget();
}

already_AddRefed<WebKitCSSMatrix>
WebKitCSSMatrix::Constructor(const GlobalObject& aGlobal,
                             const nsAString& aTransformList, ErrorResult& aRv)
{
  RefPtr<WebKitCSSMatrix> obj = new WebKitCSSMatrix(aGlobal.GetAsSupports());
  obj = obj->SetMatrixValue(aTransformList, aRv);
  return obj.forget();
}

already_AddRefed<WebKitCSSMatrix>
WebKitCSSMatrix::Constructor(const GlobalObject& aGlobal,
                             const DOMMatrixReadOnly& aOther, ErrorResult& aRv)
{
  RefPtr<WebKitCSSMatrix> obj = new WebKitCSSMatrix(aGlobal.GetAsSupports(),
                                                    aOther);
  return obj.forget();
}

JSObject*
WebKitCSSMatrix::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
  return WebKitCSSMatrixBinding::Wrap(aCx, this, aGivenProto);
}

WebKitCSSMatrix*
WebKitCSSMatrix::SetMatrixValue(const nsAString& aTransformList,
                                ErrorResult& aRv)
{
  // An empty string is a no-op.
  if (aTransformList.IsEmpty()) {
    return this;
  }

  nsCSSValue value;
  nsCSSParser parser;
  bool parseSuccess = parser.ParseTransformProperty(aTransformList,
                                                    true,
                                                    value);
  if (!parseSuccess) {
    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
    return nullptr;
  }

  // A value of "none" results in a 2D identity matrix.
  if (value.GetUnit() == eCSSUnit_None) {
    mMatrix3D = nullptr;
    mMatrix2D = new gfx::Matrix();
    return this;
  }

  // A value other than a transform-list is a syntax error.
  if (value.GetUnit() != eCSSUnit_SharedList) {
    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
    return nullptr;
  }

  RuleNodeCacheConditions dummy;
  nsStyleTransformMatrix::TransformReferenceBox dummyBox;
  bool contains3dTransform = false;
  gfx::Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms(
                               value.GetSharedListValue()->mHead,
                               nullptr, nullptr, dummy, dummyBox,
                               nsPresContext::AppUnitsPerCSSPixel(),
                               &contains3dTransform);

  if (!contains3dTransform) {
    mMatrix3D = nullptr;
    mMatrix2D = new gfx::Matrix();

    SetA(transform._11);
    SetB(transform._12);
    SetC(transform._21);
    SetD(transform._22);
    SetE(transform._41);
    SetF(transform._42);
  } else {
    mMatrix3D = new gfx::Matrix4x4(transform);
    mMatrix2D = nullptr;
  }

  return this;
}

already_AddRefed<WebKitCSSMatrix>
WebKitCSSMatrix::Multiply(const WebKitCSSMatrix& other) const
{
  RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
  retval->MultiplySelf(other);

  return retval.forget();
}

already_AddRefed<WebKitCSSMatrix>
WebKitCSSMatrix::Inverse(ErrorResult& aRv) const
{
  RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
  retval->InvertSelfThrow(aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  return retval.forget();
}

WebKitCSSMatrix*
WebKitCSSMatrix::InvertSelfThrow(ErrorResult& aRv)
{
  if (mMatrix3D) {
    if (!mMatrix3D->Invert()) {
      aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
      return nullptr;
    }
  } else if (!mMatrix2D->Invert()) {
    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
    return nullptr;
  }

  return this;
}

already_AddRefed<WebKitCSSMatrix>
WebKitCSSMatrix::Translate(double aTx,
                           double aTy,
                           double aTz) const
{
  RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
  retval->TranslateSelf(aTx, aTy, aTz);

  return retval.forget();
}

already_AddRefed<WebKitCSSMatrix>
WebKitCSSMatrix::Scale(double aScaleX,
                       const Optional<double>& aScaleY,
                       double aScaleZ) const
{
  double scaleX = aScaleX;
  double scaleY = aScaleY.WasPassed() ? aScaleY.Value() : scaleX;
  double scaleZ = aScaleZ;

  RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
  retval->ScaleNonUniformSelf(scaleX, scaleY, scaleZ);

  return retval.forget();
}

already_AddRefed<WebKitCSSMatrix>
WebKitCSSMatrix::Rotate(double aRotX,
                        const Optional<double>& aRotY,
                        const Optional<double>& aRotZ) const
{
  double rotX = aRotX;
  double rotY;
  double rotZ;

  if (!aRotY.WasPassed() && !aRotZ.WasPassed()) {
    rotZ = rotX;
    rotX = 0;
    rotY = 0;
  } else {
    rotY = aRotY.WasPassed() ? aRotY.Value() : 0;
    rotZ = aRotZ.WasPassed() ? aRotZ.Value() : 0;
  }

  RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
  retval->Rotate3dSelf(rotX, rotY, rotZ);

  return retval.forget();
}

WebKitCSSMatrix*
WebKitCSSMatrix::Rotate3dSelf(double aRotX,
                              double aRotY,
                              double aRotZ)
{
  if (aRotX != 0 || aRotY != 0) {
    Ensure3DMatrix();
  }

  if (mMatrix3D) {
    if (fmod(aRotZ, 360) != 0) {
      mMatrix3D->RotateZ(aRotZ * sRadPerDegree);
    }
    if (fmod(aRotY, 360) != 0) {
      mMatrix3D->RotateY(aRotY * sRadPerDegree);
    }
    if (fmod(aRotX, 360) != 0) {
      mMatrix3D->RotateX(aRotX * sRadPerDegree);
    }
  } else if (fmod(aRotZ, 360) != 0) {
    mMatrix2D->PreRotate(aRotZ * sRadPerDegree);
  }

  return this;
}

already_AddRefed<WebKitCSSMatrix>
WebKitCSSMatrix::RotateAxisAngle(double aX,
                                 double aY,
                                 double aZ,
                                 double aAngle) const
{
  RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
  retval->RotateAxisAngleSelf(aX, aY, aZ, aAngle);

  return retval.forget();
}

already_AddRefed<WebKitCSSMatrix>
WebKitCSSMatrix::SkewX(double aSx) const
{
  RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
  retval->SkewXSelf(aSx);

  return retval.forget();
}

already_AddRefed<WebKitCSSMatrix>
WebKitCSSMatrix::SkewY(double aSy) const
{
  RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
  retval->SkewYSelf(aSy);

  return retval.forget();
}

} // namespace dom
} // namespace mozilla
