/* -*- 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/. */

/* Utilities for animation of computed style values */

#include "mozilla/StyleAnimationValue.h"

#include "mozilla/ArrayUtils.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/PresShell.h"
#include "mozilla/PresShellInlines.h"
#include "mozilla/ServoStyleSet.h"

#include "mozilla/UniquePtr.h"
#include "nsCOMArray.h"
#include "nsString.h"
#include "mozilla/ComputedStyle.h"
#include "nsComputedDOMStyle.h"
#include "nsCSSPseudoElements.h"
#include "mozilla/dom/Element.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Likely.h"
#include "mozilla/ServoBindings.h"  // StyleLockedDeclarationBlock
#include "mozilla/ServoCSSParser.h"
#include "gfxMatrix.h"
#include "gfxQuaternion.h"
#include "mozilla/dom/Document.h"
#include "nsIFrame.h"
#include "gfx2DGlue.h"
#include "mozilla/ComputedStyleInlines.h"
#include "mozilla/layers/LayersMessages.h"

using namespace mozilla;
using namespace mozilla::css;
using namespace mozilla::dom;
using namespace mozilla::gfx;

bool AnimationValue::operator==(const AnimationValue& aOther) const {
  if (mServo && aOther.mServo) {
    return Servo_AnimationValue_DeepEqual(mServo, aOther.mServo);
  }
  if (!mServo && !aOther.mServo) {
    return true;
  }
  return false;
}

bool AnimationValue::operator!=(const AnimationValue& aOther) const {
  return !operator==(aOther);
}

float AnimationValue::GetOpacity() const {
  MOZ_ASSERT(mServo);
  return Servo_AnimationValue_GetOpacity(mServo);
}

nscolor AnimationValue::GetColor(nscolor aForegroundColor) const {
  MOZ_ASSERT(mServo);
  return Servo_AnimationValue_GetColor(mServo, aForegroundColor);
}

bool AnimationValue::IsCurrentColor() const {
  MOZ_ASSERT(mServo);
  return Servo_AnimationValue_IsCurrentColor(mServo);
}

const StyleTranslate& AnimationValue::GetTranslateProperty() const {
  MOZ_ASSERT(mServo);
  return *Servo_AnimationValue_GetTranslate(mServo);
}

const StyleRotate& AnimationValue::GetRotateProperty() const {
  MOZ_ASSERT(mServo);
  return *Servo_AnimationValue_GetRotate(mServo);
}

const StyleScale& AnimationValue::GetScaleProperty() const {
  MOZ_ASSERT(mServo);
  return *Servo_AnimationValue_GetScale(mServo);
}

const StyleTransform& AnimationValue::GetTransformProperty() const {
  MOZ_ASSERT(mServo);
  return *Servo_AnimationValue_GetTransform(mServo);
}

void AnimationValue::GetOffsetPathProperty(StyleOffsetPath& aOffsetPath) const {
  MOZ_ASSERT(mServo);
  Servo_AnimationValue_GetOffsetPath(mServo, &aOffsetPath);
}

const mozilla::LengthPercentage& AnimationValue::GetOffsetDistanceProperty()
    const {
  MOZ_ASSERT(mServo);
  return *Servo_AnimationValue_GetOffsetDistance(mServo);
}

const mozilla::StyleOffsetRotate& AnimationValue::GetOffsetRotateProperty()
    const {
  MOZ_ASSERT(mServo);
  return *Servo_AnimationValue_GetOffsetRotate(mServo);
}

const mozilla::StylePositionOrAuto& AnimationValue::GetOffsetAnchorProperty()
    const {
  MOZ_ASSERT(mServo);
  return *Servo_AnimationValue_GetOffsetAnchor(mServo);
}

const mozilla::StyleOffsetPosition& AnimationValue::GetOffsetPositionProperty()
    const {
  MOZ_ASSERT(mServo);
  return *Servo_AnimationValue_GetOffsetPosition(mServo);
}

bool AnimationValue::IsOffsetPathUrl() const {
  return mServo && Servo_AnimationValue_IsOffsetPathUrl(mServo);
}

MatrixScales AnimationValue::GetScaleValue(const nsIFrame* aFrame) const {
  using namespace nsStyleTransformMatrix;

  AnimatedPropertyID property(eCSSProperty_UNKNOWN);
  Servo_AnimationValue_GetPropertyId(mServo, &property);
  switch (property.mID) {
    case eCSSProperty_scale: {
      const StyleScale& scale = GetScaleProperty();
      return scale.IsNone()
                 ? MatrixScales()
                 : MatrixScales(scale.AsScale()._0, scale.AsScale()._1);
    }
    case eCSSProperty_rotate:
    case eCSSProperty_translate:
      return MatrixScales();
    case eCSSProperty_transform:
      break;
    default:
      MOZ_ASSERT_UNREACHABLE(
          "Should only need to check in transform properties");
      return MatrixScales();
  }

  TransformReferenceBox refBox(aFrame);
  Matrix4x4 t =
      ReadTransforms(StyleTranslate::None(), StyleRotate::None(),
                     StyleScale::None(), nullptr, GetTransformProperty(),
                     refBox, aFrame->PresContext()->AppUnitsPerDevPixel());
  Matrix transform2d;
  bool canDraw2D = t.CanDraw2D(&transform2d);
  if (!canDraw2D) {
    return MatrixScales(0, 0);
  }
  return transform2d.ScaleFactors();
}

void AnimationValue::SerializeSpecifiedValue(
    const AnimatedPropertyID& aProperty,
    const StylePerDocumentStyleData* aRawData, nsACString& aString) const {
  MOZ_ASSERT(mServo);
  Servo_AnimationValue_Serialize(mServo, &aProperty, aRawData, &aString);
}

bool AnimationValue::IsInterpolableWith(const AnimatedPropertyID& aProperty,
                                        const AnimationValue& aToValue) const {
  if (IsNull() || aToValue.IsNull()) {
    return false;
  }

  MOZ_ASSERT(mServo);
  MOZ_ASSERT(aToValue.mServo);
  return Servo_AnimationValues_IsInterpolable(mServo, aToValue.mServo);
}

double AnimationValue::ComputeDistance(const AnimationValue& aOther) const {
  if (IsNull() || aOther.IsNull()) {
    return 0.0;
  }

  MOZ_ASSERT(mServo);
  MOZ_ASSERT(aOther.mServo);

  double distance =
      Servo_AnimationValues_ComputeDistance(mServo, aOther.mServo);
  return distance < 0.0 ? 0.0 : distance;
}

/* static */
AnimationValue AnimationValue::FromString(AnimatedPropertyID& aProperty,
                                          const nsACString& aValue,
                                          Element* aElement) {
  MOZ_ASSERT(aElement);

  AnimationValue result;

  nsCOMPtr<Document> doc = aElement->GetComposedDoc();
  if (!doc) {
    return result;
  }

  RefPtr<PresShell> presShell = doc->GetPresShell();
  if (!presShell) {
    return result;
  }

  // GetComputedStyle() flushes style, so we shouldn't assume that any
  // non-owning references we have are still valid.
  RefPtr<const ComputedStyle> computedStyle =
      nsComputedDOMStyle::GetComputedStyle(aElement);
  MOZ_ASSERT(computedStyle);

  RefPtr<StyleLockedDeclarationBlock> declarations =
      ServoCSSParser::ParseProperty(aProperty, aValue,
                                    ServoCSSParser::GetParsingEnvironment(doc),
                                    StyleParsingMode::DEFAULT);

  if (!declarations) {
    return result;
  }

  result.mServo = presShell->StyleSet()->ComputeAnimationValue(
      aElement, declarations, computedStyle);
  return result;
}

/* static */
already_AddRefed<StyleAnimationValue> AnimationValue::FromAnimatable(
    nsCSSPropertyID aProperty, const layers::Animatable& aAnimatable) {
  switch (aAnimatable.type()) {
    case layers::Animatable::Tnull_t:
      break;
    case layers::Animatable::TStyleTransform: {
      const StyleTransform& transform = aAnimatable.get_StyleTransform();
      MOZ_ASSERT(!transform.HasPercent(),
                 "Received transform operations should have been resolved.");
      return Servo_AnimationValue_Transform(&transform).Consume();
    }
    case layers::Animatable::Tfloat:
      return Servo_AnimationValue_Opacity(aAnimatable.get_float()).Consume();
    case layers::Animatable::Tnscolor:
      return Servo_AnimationValue_Color(aProperty, aAnimatable.get_nscolor())
          .Consume();
    case layers::Animatable::TStyleRotate:
      return Servo_AnimationValue_Rotate(&aAnimatable.get_StyleRotate())
          .Consume();
    case layers::Animatable::TStyleScale:
      return Servo_AnimationValue_Scale(&aAnimatable.get_StyleScale())
          .Consume();
    case layers::Animatable::TStyleTranslate:
      MOZ_ASSERT(
          aAnimatable.get_StyleTranslate().IsNone() ||
              (!aAnimatable.get_StyleTranslate()
                    .AsTranslate()
                    ._0.HasPercent() &&
               !aAnimatable.get_StyleTranslate().AsTranslate()._1.HasPercent()),
          "Should have been resolved already");
      return Servo_AnimationValue_Translate(&aAnimatable.get_StyleTranslate())
          .Consume();
    case layers::Animatable::TStyleOffsetPath:
      return Servo_AnimationValue_OffsetPath(&aAnimatable.get_StyleOffsetPath())
          .Consume();
    case layers::Animatable::TLengthPercentage:
      return Servo_AnimationValue_OffsetDistance(
                 &aAnimatable.get_LengthPercentage())
          .Consume();
    case layers::Animatable::TStyleOffsetRotate:
      return Servo_AnimationValue_OffsetRotate(
                 &aAnimatable.get_StyleOffsetRotate())
          .Consume();
    case layers::Animatable::TStylePositionOrAuto:
      return Servo_AnimationValue_OffsetAnchor(
                 &aAnimatable.get_StylePositionOrAuto())
          .Consume();
    case layers::Animatable::TStyleOffsetPosition:
      return Servo_AnimationValue_OffsetPosition(
                 &aAnimatable.get_StyleOffsetPosition())
          .Consume();
    default:
      MOZ_ASSERT_UNREACHABLE("Unsupported type");
  }
  return nullptr;
}
