// Copyright 2004-present Facebook. All Rights Reserved.

package com.facebook.react.uimanager.layoutanimation;

import javax.annotation.Nullable;

import java.util.Map;

import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;

import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.IllegalViewOperationException;

/**
 * Class responsible for parsing and converting layout animation data into native {@link Animation}
 * in order to animate layout when a valid configuration has been supplied by the application.
 */
/* package */ abstract class AbstractLayoutAnimation {

  // Forces animation to be playing 10x slower, used for debug purposes.
  private static final boolean SLOWDOWN_ANIMATION_MODE = false;

  abstract boolean isValid();

  /**
   * Create an animation object for the current animation type, based on the view and final screen
   * coordinates. If the application-supplied configuraiton does not specify an animation definition
   * for this types, or if the animation definition is invalid, returns null.
   */
  abstract @Nullable Animation createAnimationImpl(View view, int x, int y, int width, int height);

  private static final Map<InterpolatorType, Interpolator> INTERPOLATOR = MapBuilder.of(
      InterpolatorType.LINEAR, new LinearInterpolator(),
      InterpolatorType.EASE_IN, new AccelerateInterpolator(),
      InterpolatorType.EASE_OUT, new DecelerateInterpolator(),
      InterpolatorType.EASE_IN_EASE_OUT, new AccelerateDecelerateInterpolator(),
      InterpolatorType.SPRING, new SimpleSpringInterpolator());

  private @Nullable Interpolator mInterpolator;
  private int mDelayMs;

  protected @Nullable AnimatedPropertyType mAnimatedProperty;
  protected int mDurationMs;

  public void reset() {
    mAnimatedProperty = null;
    mDurationMs = 0;
    mDelayMs = 0;
    mInterpolator = null;
  }

  public void initializeFromConfig(ReadableMap data, int globalDuration) {
    mAnimatedProperty = data.hasKey("property") ?
        AnimatedPropertyType.fromString(data.getString("property")) : null;
    mDurationMs = data.hasKey("duration") ? data.getInt("duration") : globalDuration;
    mDelayMs = data.hasKey("delay") ? data.getInt("delay") : 0;
    if (!data.hasKey("type")) {
      throw new IllegalArgumentException("Missing interpolation type.");
    }
    mInterpolator = getInterpolator(InterpolatorType.fromString(data.getString("type")));

    if (!isValid()) {
      throw new IllegalViewOperationException("Invalid layout animation : " + data);
    }
  }

  /**
   * Create an animation object to be used to animate the view, based on the animation config
   * supplied at initialization time and the new view position and size.
   *
   * @param view the view to create the animation for
   * @param x the new X position for the view
   * @param y the new Y position for the view
   * @param width the new width value for the view
   * @param height the new height value for the view
   */
   public final @Nullable Animation createAnimation(
       View view,
       int x,
       int y,
       int width,
       int height) {
    if (!isValid()) {
      return null;
    }
    Animation animation = createAnimationImpl(view, x, y, width, height);
    if (animation != null) {
      int slowdownFactor = SLOWDOWN_ANIMATION_MODE ? 10 : 1;
      animation.setDuration(mDurationMs * slowdownFactor);
      animation.setStartOffset(mDelayMs * slowdownFactor);
      animation.setInterpolator(mInterpolator);
    }
    return animation;
  }

  private static Interpolator getInterpolator(InterpolatorType type) {
    Interpolator interpolator = INTERPOLATOR.get(type);
    if (interpolator == null) {
      throw new IllegalArgumentException("Missing interpolator for type : " + type);
    }
    return interpolator;
  }
}
