/* -*- 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 "nsError.h"
#include "nsSVGTransform.h"
#include "nsContentUtils.h" // for NS_ENSURE_FINITE
#include "nsTextFormatter.h"

namespace {
  const double kRadPerDegree = 2.0 * M_PI / 360.0;
} // namespace

namespace mozilla {

void
nsSVGTransform::GetValueAsString(nsAString& aValue) const
{
  char16_t buf[256];

  switch (mType) {
    case SVG_TRANSFORM_TRANSLATE:
      // The spec say that if Y is not provided, it is assumed to be zero.
      if (mMatrix._32 != 0)
        nsTextFormatter::snprintf(buf, sizeof(buf)/sizeof(char16_t),
            u"translate(%g, %g)",
            mMatrix._31, mMatrix._32);
      else
        nsTextFormatter::snprintf(buf, sizeof(buf)/sizeof(char16_t),
            u"translate(%g)",
            mMatrix._31);
      break;
    case SVG_TRANSFORM_ROTATE:
      if (mOriginX != 0.0f || mOriginY != 0.0f)
        nsTextFormatter::snprintf(buf, sizeof(buf)/sizeof(char16_t),
            u"rotate(%g, %g, %g)",
            mAngle, mOriginX, mOriginY);
      else
        nsTextFormatter::snprintf(buf, sizeof(buf)/sizeof(char16_t),
            u"rotate(%g)", mAngle);
      break;
    case SVG_TRANSFORM_SCALE:
      if (mMatrix._11 != mMatrix._22)
        nsTextFormatter::snprintf(buf, sizeof(buf)/sizeof(char16_t),
            u"scale(%g, %g)", mMatrix._11, mMatrix._22);
      else
        nsTextFormatter::snprintf(buf, sizeof(buf)/sizeof(char16_t),
            u"scale(%g)", mMatrix._11);
      break;
    case SVG_TRANSFORM_SKEWX:
      nsTextFormatter::snprintf(buf, sizeof(buf)/sizeof(char16_t),
                                u"skewX(%g)", mAngle);
      break;
    case SVG_TRANSFORM_SKEWY:
      nsTextFormatter::snprintf(buf, sizeof(buf)/sizeof(char16_t),
                                u"skewY(%g)", mAngle);
      break;
    case SVG_TRANSFORM_MATRIX:
      nsTextFormatter::snprintf(buf, sizeof(buf)/sizeof(char16_t),
          u"matrix(%g, %g, %g, %g, %g, %g)",
                            mMatrix._11, mMatrix._12,
                            mMatrix._21, mMatrix._22,
                            mMatrix._31, mMatrix._32);
      break;
    default:
      buf[0] = '\0';
      NS_ERROR("unknown transformation type");
      break;
  }

  aValue.Assign(buf);
}

void
nsSVGTransform::SetMatrix(const gfxMatrix& aMatrix)
{
  mType    = SVG_TRANSFORM_MATRIX;
  mMatrix  = aMatrix;
  // We set the other members here too, since operator== requires it and
  // the DOM requires it for mAngle.
  mAngle   = 0.f;
  mOriginX = 0.f;
  mOriginY = 0.f;
}

void
nsSVGTransform::SetTranslate(float aTx, float aTy)
{
  mType    = SVG_TRANSFORM_TRANSLATE;
  mMatrix.Reset();
  mMatrix._31 = aTx;
  mMatrix._32 = aTy;
  mAngle   = 0.f;
  mOriginX = 0.f;
  mOriginY = 0.f;
}

void
nsSVGTransform::SetScale(float aSx, float aSy)
{
  mType    = SVG_TRANSFORM_SCALE;
  mMatrix.Reset();
  mMatrix._11 = aSx;
  mMatrix._22 = aSy;
  mAngle   = 0.f;
  mOriginX = 0.f;
  mOriginY = 0.f;
}

void
nsSVGTransform::SetRotate(float aAngle, float aCx, float aCy)
{
  mType    = SVG_TRANSFORM_ROTATE;
  mMatrix.Reset();
  mMatrix.Translate(aCx, aCy);
  mMatrix.Rotate(aAngle*kRadPerDegree);
  mMatrix.Translate(-aCx, -aCy);
  mAngle   = aAngle;
  mOriginX = aCx;
  mOriginY = aCy;
}

nsresult
nsSVGTransform::SetSkewX(float aAngle)
{
  double ta = tan(aAngle*kRadPerDegree);
  NS_ENSURE_FINITE(ta, NS_ERROR_RANGE_ERR);

  mType    = SVG_TRANSFORM_SKEWX;
  mMatrix.Reset();
  mMatrix._21 = ta;
  mAngle   = aAngle;
  mOriginX = 0.f;
  mOriginY = 0.f;
  return NS_OK;
}

nsresult
nsSVGTransform::SetSkewY(float aAngle)
{
  double ta = tan(aAngle*kRadPerDegree);
  NS_ENSURE_FINITE(ta, NS_ERROR_RANGE_ERR);

  mType    = SVG_TRANSFORM_SKEWY;
  mMatrix.Reset();
  mMatrix._12 = ta;
  mAngle   = aAngle;
  mOriginX = 0.f;
  mOriginY = 0.f;
  return NS_OK;
}

SVGTransformSMILData::SVGTransformSMILData(const nsSVGTransform& aTransform)
  : mTransformType(aTransform.Type())
{
  MOZ_ASSERT(mTransformType >= SVG_TRANSFORM_MATRIX &&
             mTransformType <= SVG_TRANSFORM_SKEWY,
             "Unexpected transform type");

  for (uint32_t i = 0; i < NUM_STORED_PARAMS; ++i) {
    mParams[i] = 0.f;
  }

  switch (mTransformType) {
    case SVG_TRANSFORM_MATRIX: {
      const gfxMatrix& mx = aTransform.GetMatrix();
      mParams[0] = static_cast<float>(mx._11);
      mParams[1] = static_cast<float>(mx._12);
      mParams[2] = static_cast<float>(mx._21);
      mParams[3] = static_cast<float>(mx._22);
      mParams[4] = static_cast<float>(mx._31);
      mParams[5] = static_cast<float>(mx._32);
      break;
    }
    case SVG_TRANSFORM_TRANSLATE: {
      const gfxMatrix& mx = aTransform.GetMatrix();
      mParams[0] = static_cast<float>(mx._31);
      mParams[1] = static_cast<float>(mx._32);
      break;
    }
    case SVG_TRANSFORM_SCALE: {
      const gfxMatrix& mx = aTransform.GetMatrix();
      mParams[0] = static_cast<float>(mx._11);
      mParams[1] = static_cast<float>(mx._22);
      break;
    }
    case SVG_TRANSFORM_ROTATE:
      mParams[0] = aTransform.Angle();
      aTransform.GetRotationOrigin(mParams[1], mParams[2]);
      break;

    case SVG_TRANSFORM_SKEWX:
    case SVG_TRANSFORM_SKEWY:
      mParams[0] = aTransform.Angle();
      break;

    default:
      NS_NOTREACHED("Unexpected transform type");
      break;
  }
}

nsSVGTransform
SVGTransformSMILData::ToSVGTransform() const
{
  nsSVGTransform result;

  switch (mTransformType) {
    case SVG_TRANSFORM_MATRIX:
      result.SetMatrix(gfxMatrix(mParams[0], mParams[1],
                                 mParams[2], mParams[3],
                                 mParams[4], mParams[5]));
      break;

    case SVG_TRANSFORM_TRANSLATE:
      result.SetTranslate(mParams[0], mParams[1]);
      break;

    case SVG_TRANSFORM_SCALE:
      result.SetScale(mParams[0], mParams[1]);
      break;

    case SVG_TRANSFORM_ROTATE:
      result.SetRotate(mParams[0], mParams[1], mParams[2]);
      break;

    case SVG_TRANSFORM_SKEWX:
      result.SetSkewX(mParams[0]);
      break;

    case SVG_TRANSFORM_SKEWY:
      result.SetSkewY(mParams[0]);
      break;

    default:
      NS_NOTREACHED("Unexpected transform type");
      break;
  }
  return result;
}

} // namespace mozilla