package rjsv.morphos;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.os.Build;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;

import java.util.ArrayList;
import java.util.Collections;

import rjsv.morphos.data.ViewDefault;
import rjsv.morphos.data.animations.AlphaAnimation;
import rjsv.morphos.data.animations.Animation;
import rjsv.morphos.data.animations.AxisTranslationAnimation;
import rjsv.morphos.data.animations.DimensionsAnimation;
import rjsv.morphos.data.animations.RotationAnimation;
import rjsv.morphos.data.animations.RotationXYAnimation;
import rjsv.morphos.data.animations.ScaleAnimation;
import rjsv.morphos.data.animations.TranslationAnimation;
import rjsv.morphos.enumerators.AnimationTarget;
import rjsv.morphos.enumerators.AnimationType;

import static rjsv.morphos.data.animations.AxisTranslationAnimation.Axis;

/**
 * Description
 *
 * @author <a href="mailto:[email protected]">RJSV</a>
 * @version $Revision : 1 $
 */

public class Morpho {

    // Default Values
    public static final boolean atLeastLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
    private static final AnimationType defaultAnimationType = AnimationType.SEQUENTIAL;
    private static final Interpolator defaultInterpolator = new LinearInterpolator();

    // #1 - Initial View and respective default helper class
    private View viewToMorph;
    private ViewDefault viewDefault;
    // #2 - The reference to the Complete Animation
    private AnimatorSet completeAnimation;
    // #3 - The Complete Animation's sub-parts
    private ArrayList<Animation> animationParts;
    // #4 - The built-in configurations for the Complete Animation
    private int completeAnimationDuration = -1;
    private AnimationType completeAnimationAnimationType;
    private Interpolator completeAnimationInterpolator;
    // #5 - The Complete Animation Listener
    private Animator.AnimatorListener completeAnimationListener;

    // Constructor
    public Morpho(@NonNull View viewToMorph) {
        updateView(viewToMorph, true);
    }


    // Set & Get View
    public Morpho updateView(@NonNull View viewToMorph) {
        updateView(viewToMorph, true);
        return this;
    }

    private void updateView(@NonNull View viewToMorph, boolean shouldResetMorpho) {
        this.viewToMorph = viewToMorph;
        this.viewDefault = new ViewDefault(viewToMorph);
        this.completeAnimation = new AnimatorSet();
        if (shouldResetMorpho) {
            this.reset();
        }
    }

    public int getAnimationDuration() {
        int duration = 0;
        if (this.completeAnimationDuration >= 0) {
            duration = this.completeAnimationDuration;
        } else if (animationParts != null && animationParts.size() > 0) {
            for (Animation a : animationParts) {
                duration += a.getDuration();
            }
        }
        return duration;
    }

    // Alpha
    public Morpho alpha(double value) {
        return alpha(value, 0, defaultInterpolator);
    }

    public Morpho alpha(double value, int duration) {
        return alpha(value, duration, defaultInterpolator);
    }

    public Morpho alpha(double value, int duration, Interpolator interpolator) {
        animationParts.add(new AlphaAnimation((float) value, duration, interpolator));
        return this;
    }


    // Expansion
    public Morpho scale(double valueX, double valueY) {
        return scale(valueX, valueY, 0, defaultInterpolator);
    }

    public Morpho scale(double valueX, double valueY, int duration) {
        return scale(valueX, valueY, duration, defaultInterpolator);
    }

    public Morpho scale(double valueX, double valueY, int duration, Interpolator interpolator) {
        if (viewDefault != null) {
            animationParts.add(new ScaleAnimation(viewDefault.getExpansionScaleX(), viewDefault.getExpansionScaleY(), (float) valueX, (float) valueY, duration, interpolator));
        }
        return this;
    }


    // Translation X
    public Morpho translationX(AnimationTarget target, float valueX) {
        return translationX(target, valueX, 0, defaultInterpolator);
    }

    public Morpho translationX(AnimationTarget target, float valueX, int duration) {
        return translationX(target, valueX, duration, defaultInterpolator);
    }

    public Morpho translationX(AnimationTarget target, float valueX, int duration, Interpolator interpolator) {
        animationParts.add(new AxisTranslationAnimation(Axis.X, target, valueX, duration, interpolator));
        return this;
    }

    // Translation Y
    public Morpho translationY(AnimationTarget target, float valueY) {
        return translationY(target, valueY, 0, defaultInterpolator);
    }

    public Morpho translationY(AnimationTarget target, float valueY, int duration) {
        return translationY(target, valueY, duration, defaultInterpolator);
    }

    public Morpho translationY(AnimationTarget target, float valueY, int duration, Interpolator interpolator) {
        animationParts.add(new AxisTranslationAnimation(Axis.Y, target, valueY, duration, interpolator));
        return this;
    }

    // Translation Z
    public Morpho translationZ(AnimationTarget target, float valueZ) {
        return translationZ(target, valueZ, 0, defaultInterpolator);
    }

    public Morpho translationZ(AnimationTarget target, float valueZ, int duration) {
        return translationZ(target, valueZ, duration, defaultInterpolator);
    }

    public Morpho translationZ(AnimationTarget target, float valueZ, int duration, Interpolator interpolator) {
        animationParts.add(new AxisTranslationAnimation(Axis.Z, target, valueZ, duration, interpolator));
        return this;
    }


    // Global translation
    public Morpho translation(AnimationTarget target, float valueX, float valueY, float valueZ) {
        return translation(target, valueX, valueY, valueZ, 0, defaultInterpolator);
    }

    public Morpho translation(AnimationTarget target, float valueX, float valueY, float valueZ, int duration) {
        return translation(target, valueX, valueY, valueZ, duration, defaultInterpolator);
    }

    public Morpho translation(AnimationTarget target, float valueX, float valueY, float valueZ, int duration, Interpolator interpolator) {
        animationParts.add(new TranslationAnimation(target, valueX, valueY, valueZ, duration, interpolator));
        return this;
    }


    // Dimensions
    public Morpho dimensions(float width, float height) {
        return dimensions(width, height, 0, null);
    }

    public Morpho dimensions(float width, float height, int duration) {
        return dimensions(width, height, duration, defaultInterpolator);
    }

    public Morpho dimensions(float width, float height, int duration, Interpolator interpolator) {
        if (width >= 0 && height >= 0) {
            animationParts.add(new DimensionsAnimation(width, height, duration, interpolator));
        }
        return this;
    }


    // Rotation XY
    public Morpho rotationXY(AnimationTarget rotationType, double degreesX, double degreesY) {
        return rotationXY(rotationType, degreesX, degreesY, 0, defaultInterpolator);
    }

    public Morpho rotationXY(AnimationTarget rotationType, double degreesX, double degreesY, int duration) {
        return rotationXY(rotationType, degreesX, degreesY, duration, defaultInterpolator);
    }

    public Morpho rotationXY(AnimationTarget rotationType, double degreesX, double degreesY, int duration, Interpolator interpolator) {
        animationParts.add(new RotationXYAnimation(rotationType, (float) degreesX, (float) degreesY, duration, interpolator));
        return this;
    }


    // Rotation
    public Morpho rotation(AnimationTarget rotationType, double degrees) {
        return rotation(rotationType, degrees, 0, defaultInterpolator);
    }

    public Morpho rotation(AnimationTarget rotationType, double degrees, int duration) {
        return rotation(rotationType, degrees, duration, defaultInterpolator);
    }

    public Morpho rotation(AnimationTarget rotationType, double degrees, int duration, Interpolator interpolator) {
        animationParts.add(new RotationAnimation(rotationType, (float) degrees, duration, interpolator));
        return this;
    }


    // Build Animation Set
    public Morpho build() {
        return build(defaultAnimationType, -1, defaultInterpolator);
    }

    public Morpho build(AnimationType animationType, int duration) {
        return build(animationType, duration, defaultInterpolator);
    }

    public Morpho build(AnimationType animationType, int duration, Interpolator interpolator) {
        if (animationType != null) {
            this.completeAnimationAnimationType = animationType;
        }
        this.completeAnimationDuration = duration;
        if (interpolator != null) {
            this.completeAnimationInterpolator = interpolator;
        }
        return this;
    }


    // Animate Animator Set - User's accessible 'play' method
    public Morpho animate() {
        return animate(defaultAnimationType, -1, defaultInterpolator);
    }

    public Morpho animate(AnimationType animationType, int duration) {
        return animate(animationType, duration, defaultInterpolator);
    }

    public Morpho animate(AnimationType type, int duration, Interpolator interpolator) {
        build(type, duration, interpolator);
        // Method parameter call
        // #1 - forcePlay - this would start the animation regardless of it being running or not
        // #2 - listener - used by Swarm to chain the sequential animations
        return play(false, null);
    }


    // Play Animator Set - Swarm's accessible 'play' method
    Morpho play(boolean forcePlay, Animator.AnimatorListener swarmListener) {
        boolean animationPartsIsConfigured = this.animationParts != null && this.animationParts.size() > 0;
        boolean coreViewIsInitialized = this.viewToMorph != null && this.viewDefault != null;
        boolean animationIsNotRunning = this.completeAnimation == null || (!this.completeAnimation.isRunning() || forcePlay);
        if (animationPartsIsConfigured && coreViewIsInitialized && animationIsNotRunning) {
            // Mandatory updateView call - ensures the view is updated and the animatorSet is created
            updateView(this.viewToMorph, false);
            ArrayList<Animator> animators = new ArrayList<>();
            for (Animation a : this.animationParts) {
                Animator anim = a.buildAnimation(this.viewDefault, this.viewToMorph, false);
                if (anim != null) {
                    animators.add(anim);
                }
            }
            // Type of Animation
            if (this.completeAnimationAnimationType == AnimationType.SEQUENTIAL) {
                this.completeAnimation.playSequentially(animators);
            } else {
                this.completeAnimation.playTogether(animators);
            }
            // Duration
            if (this.completeAnimationDuration >= 0) {
                this.completeAnimation.setDuration(this.completeAnimationDuration);
            }
            // Interpolator
            if (this.completeAnimationInterpolator != null) {
                this.completeAnimation.setInterpolator(this.completeAnimationInterpolator);
            }
            // Listener
            this.completeAnimation.removeAllListeners();
            if (swarmListener != null) {
                this.completeAnimation.addListener(swarmListener);
            }
            if (this.completeAnimationListener != null) {
                this.completeAnimation.addListener(this.completeAnimationListener);
            }
            this.completeAnimation.start();
        }
        return this;
    }


    // Animator Set's Listener
    public Morpho setListener(Animator.AnimatorListener listener) {
        this.completeAnimationListener = listener;
        return this;
    }


    // Reverse Animation Set
    public Morpho reverse() {
        return reverse(defaultAnimationType, -1, defaultInterpolator);
    }

    public Morpho reverse(AnimationType animationType, int duration) {
        return reverse(animationType, duration, defaultInterpolator);
    }

    public Morpho reverse(AnimationType type, int duration, Interpolator interpolator) {
        boolean animationPartsIsConfigured = this.animationParts != null && this.animationParts.size() > 0;
        boolean coreViewIsInitialized = this.viewToMorph != null && this.viewDefault != null;
        boolean animationIsNotRunning = this.completeAnimation != null && !this.completeAnimation.isRunning();
        if (animationPartsIsConfigured && coreViewIsInitialized && animationIsNotRunning) {
            this.completeAnimation = new AnimatorSet();
            ArrayList<Animation> reversedAnimationParts = new ArrayList<>();
            reversedAnimationParts.addAll(animationParts);
            Collections.reverse(reversedAnimationParts);
            ArrayList<Animator> animators = new ArrayList<>();
            for (Animation a : reversedAnimationParts) {
                animators.add(a.buildAnimation(this.viewDefault, this.viewToMorph, true));
            }
            // Type of Animation
            if (type == AnimationType.SEQUENTIAL) {
                this.completeAnimation.playSequentially(animators);
            } else {
                this.completeAnimation.playTogether(animators);
            }
            // Duration
            if (duration >= 0) {
                this.completeAnimation.setDuration(duration);
            }
            // Interpolator
            if (interpolator != null) {
                this.completeAnimation.setInterpolator(interpolator);
            }
            this.completeAnimation.start();
            this.completeAnimation = null;
        }
        return this;
    }


    // Reset Method
    public Morpho reset() {
        completeAnimationAnimationType = defaultAnimationType;
        completeAnimationInterpolator = defaultInterpolator;
        if (animationParts != null) {
            animationParts.clear();
        } else {
            animationParts = new ArrayList<>();
        }
        return this;
    }


    // Cancel Methods
    public void cancel() {
        if (completeAnimation != null) {
            completeAnimation.end();
        }
        reverse(defaultAnimationType, 0, defaultInterpolator);
    }


    // Dispose Methods
    public void dispose() {
        viewToMorph = null;
        viewDefault = null;
        if (animationParts != null) {
            for (Animation a : animationParts) {
                a.dispose();
            }
            animationParts.clear();
            animationParts = null;
        }
        if (completeAnimation != null) {
            completeAnimation.removeAllListeners();
            completeAnimation.cancel();
            completeAnimation = null;
        }
        completeAnimationAnimationType = null;
        completeAnimationInterpolator = null;
        completeAnimationListener = null;
    }

}