/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.material.transition;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * A class that configures and is able to provide an {@link Animator} that scales a view.
 *
 * <p>{@code ScaleProvider}'s constructor optionally takes a {@code growing} parameter. By default,
 * this is set to true and will increase the size of the target both when appearing and
 * disappearing. This is useful when pairing two animating targets, one appearing and one
 * disappearing, that should both be either growing or shrinking to create a visual relationship.
 */
public final class ScaleProvider implements VisibilityAnimatorProvider {

  private float outgoingStartScale = 1f;
  private float outgoingEndScale = 1.1f;
  private float incomingStartScale = 0.8f;
  private float incomingEndScale = 1f;

  private boolean growing;
  private boolean scaleOnDisappear = true;

  public ScaleProvider() {
    this(true);
  }

  public ScaleProvider(boolean growing) {
    this.growing = growing;
  }

  /** Whether or not this animation's target will grow or shrink in size. */
  public boolean isGrowing() {
    return growing;
  }

  /** Set whether or not this animation's target will grow or shrink in size. */
  public void setGrowing(boolean growing) {
    this.growing = growing;
  }

  /**
   * Whether or not a scale animation will be run on this animation's target when disappearing.
   *
   * @see #setScaleOnDisappear(boolean)
   */
  public boolean isScaleOnDisappear() {
    return scaleOnDisappear;
  }

  /**
   * Set whether or not a scale animation will be run on this animation's target when disappearing.
   *
   * <p>This is useful when using a single {@code ScaleProvider} that runs on multiple targets and
   * only appearing targets should be animated.
   */
  public void setScaleOnDisappear(boolean scaleOnDisappear) {
    this.scaleOnDisappear = scaleOnDisappear;
  }

  /**
   * The scale x and scale y value which an appearing and shrinking target will scale to and a
   * disappearing and growing target will scale from.
   */
  public float getOutgoingStartScale() {
    return outgoingStartScale;
  }

  /**
   * Set the scale x and scale y value which an appearing and shrinking target should scale to and a
   * disappearing and growing target should scale from.
   */
  public void setOutgoingStartScale(float outgoingStartScale) {
    this.outgoingStartScale = outgoingStartScale;
  }

  /**
   * The scale x and scale y value which an appearing and shrinking target will scale from and a
   * disappearing and growing target will scale to.
   */
  public float getOutgoingEndScale() {
    return outgoingEndScale;
  }

  /**
   * Set the scale x and scale y value which an appearing and shrinking target should scale from and
   * a disappearing and growing target should scale to.
   */
  public void setOutgoingEndScale(float outgoingEndScale) {
    this.outgoingEndScale = outgoingEndScale;
  }

  /**
   * The scale x and scale y value which an appearing and growing target will scale from and a
   * disappearing and shrinking target will scale to.
   */
  public float getIncomingStartScale() {
    return incomingStartScale;
  }

  /**
   * Set the scale x and scale y value which an appearing and growing target should scale from and a
   * disappearing and shrinking target should scale to.
   */
  public void setIncomingStartScale(float incomingStartScale) {
    this.incomingStartScale = incomingStartScale;
  }

  /**
   * The scale x and scale y value which an appearing and growing target will scale to and a
   * disappearing and shrinking target will scale from.
   */
  public float getIncomingEndScale() {
    return incomingEndScale;
  }

  /**
   * Set the scale x and scale y value which an appearing and growing target should scale to and a
   * disappearing and shrinking target should scale from.
   */
  public void setIncomingEndScale(float incomingEndScale) {
    this.incomingEndScale = incomingEndScale;
  }

  @Nullable
  @Override
  public Animator createAppear(@NonNull ViewGroup sceneRoot, @NonNull View view) {
    if (growing) {
      return createScaleAnimator(view, incomingStartScale, incomingEndScale);
    } else {
      return createScaleAnimator(view, outgoingEndScale, outgoingStartScale);
    }
  }

  @Nullable
  @Override
  public Animator createDisappear(@NonNull ViewGroup sceneRoot, @NonNull View view) {
    if (!scaleOnDisappear) {
      return null;
    }

    if (growing) {
      return createScaleAnimator(view, outgoingStartScale, outgoingEndScale);
    } else {
      return createScaleAnimator(view, incomingEndScale, incomingStartScale);
    }
  }

  private static Animator createScaleAnimator(View view, float startScale, float endScale) {
    return ObjectAnimator.ofPropertyValuesHolder(
        view,
        PropertyValuesHolder.ofFloat(View.SCALE_X, startScale, endScale),
        PropertyValuesHolder.ofFloat(View.SCALE_Y, startScale, endScale));
  }
}