/**
 * Copyright (c) 2016-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.keyframes.model;

import java.util.Comparator;
import java.util.List;

import com.facebook.keyframes.model.keyframedmodels.KeyFramedAnchorPoint;
import com.facebook.keyframes.model.keyframedmodels.KeyFramedMatrixAnimation;
import com.facebook.keyframes.model.keyframedmodels.KeyFramedObject;
import com.facebook.keyframes.model.keyframedmodels.KeyFramedOpacity;
import com.facebook.keyframes.model.keyframedmodels.KeyFramedStrokeWidth;
import com.facebook.keyframes.util.ArgCheckUtil;
import com.facebook.keyframes.util.ListHelper;

/**
 * The base class for describing an animated property.  This can range from matrix based properties
 * such as scale, rotation, position, to others like stroke width.
 */
public class KFAnimation {

  public static final Comparator<KFAnimation> ANIMATION_PROPERTY_COMPARATOR =
      new Comparator<KFAnimation>() {
        @Override
        public int compare(
            KFAnimation lhs, KFAnimation rhs) {
          return lhs.getPropertyType().compareTo(rhs.getPropertyType());
        }
      };

  /**
   * Enum for the different supported animation types, listed in order of transform application.
   * Standard transform ordering is SCALE * ROTATION * TRANSLATION.
   */
  public enum PropertyType {
    SCALE (true),
    ROTATION (true),
    POSITION (true),
    X_POSITION (true),
    Y_POSITION (true),
    ANCHOR_POINT (false),
    STROKE_WIDTH (false),
    OPACITY (false);

    /**
     * Whether this animation is matrix based or not.  Currently, the only non-matrix based
     * animation is stroke width, which is specific to {@link KFFeature}.
     */
    final boolean mIsMatrixBased;

    PropertyType(boolean isMatrixBased) {
      mIsMatrixBased = isMatrixBased;
    }

    /**
     * Returns whether this animation is matrix based or not.  Currently, the only non-matrix based
     * animation is stroke width, which is specific to {@link KFFeature}.
     */
    public boolean isMatrixBased() {
      return mIsMatrixBased;
    }
  }

  /**
   * The property animated by this KFAnimation instance.
   */
  public static final String PROPERTY_TYPE_JSON_FIELD = "property";
  private final PropertyType mPropertyType;

  /**
   * A list of {@link KFAnimationFrame}s, each describing timing and value data for a
   * specific key frame.
   */
  public static final String ANIMATION_FRAMES_JSON_FIELD = "key_values";
  private final List<KFAnimationFrame> mAnimationFrames;

  /**
   * An array of timing curve data.  This array describes transition values between each key frame
   * in mAnimationFrames.  For each timing curve, there are two sets of points which are the control
   * points describing how to draw the cubic curve from (0,0) to (1,1).
   */
  public static final String TIMING_CURVES_JSON_FIELD = "timing_curves";
  private final float[][][] mTimingCurves;

  /**
   * An anchor point, which changes the origin of a matrix based property.
   * Deprecated in favor of the ANCHOR_POINT animation.
   */
  @Deprecated
  public static final String ANCHOR_JSON_FIELD = "anchor";
  @Deprecated
  private final float[] mAnchor;

  /**
   * A post-processed data structure containing cached information for this key frame animation.
   */
  private final KeyFramedObject mKeyFramedAnimation;

  public static class Builder {
    public PropertyType propertyType;
    public List<KFAnimationFrame> animationFrames;
    public float[][][] timingCurves;
    public float[] anchor;

    public KFAnimation build() {
      return new KFAnimation(propertyType, animationFrames, timingCurves, anchor);
    }
  }

  public KFAnimation(
      PropertyType propertyType,
      List<KFAnimationFrame> animationFrames,
      float[][][] timingCurves,
      float[] anchor) {
    mPropertyType = ArgCheckUtil.checkArg(
        propertyType,
        propertyType != null,
        PROPERTY_TYPE_JSON_FIELD);
    mAnimationFrames = ArgCheckUtil.checkArg(
        ListHelper.immutableOrEmpty(animationFrames),
        animationFrames != null && animationFrames.size() > 0,
        ANIMATION_FRAMES_JSON_FIELD);
    mTimingCurves = ArgCheckUtil.checkArg(
        timingCurves,
        ArgCheckUtil.checkTimingCurveObjectValidity(timingCurves, mAnimationFrames.size()),
        TIMING_CURVES_JSON_FIELD);
    mAnchor = ArgCheckUtil.checkArg(
        anchor,
        anchor == null || anchor.length == 2,
        ANCHOR_JSON_FIELD);
    if (mPropertyType.isMatrixBased()) {
      mKeyFramedAnimation = KeyFramedMatrixAnimation.fromAnimation(this);
    } else if (mPropertyType == PropertyType.STROKE_WIDTH){
      mKeyFramedAnimation = KeyFramedStrokeWidth.fromAnimation(this);
    } else if (mPropertyType == PropertyType.ANCHOR_POINT) {
      mKeyFramedAnimation = KeyFramedAnchorPoint.fromAnchorPoint(this);
    } else if (mPropertyType == PropertyType.OPACITY) {
      mKeyFramedAnimation = KeyFramedOpacity.fromAnimation(this);
    } else {
      throw new IllegalArgumentException(
          "Unknown property type for animation post processing: " + mPropertyType);
    }
  }

  public PropertyType getPropertyType() {
    return mPropertyType;
  }

  public List<KFAnimationFrame> getAnimationFrames() {
    return mAnimationFrames;
  }

  public float[][][] getTimingCurves() {
    return mTimingCurves;
  }

  /**
   * Deprecated in favor of the ANCHOR_POINT animation.
   */
  @Deprecated
  public float[] getAnchor() {
    return mAnchor;
  }

  public KeyFramedObject getAnimation() {
    return mKeyFramedAnimation;
  }
}
