package com.kaichunlin.transition.internal;

import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;

import com.kaichunlin.transition.TransitionConfig;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ValueAnimator;

import java.util.ArrayList;

/**
 * NineOldAndroids' ObjectAnimator is used to provide required transition behavior.
 * <p>
 * TODO may be possible to switch to use NineOldDroid's ViewPropertyAnimator for better performance
 * <p>
 */
public class DefaultTransitionController extends TransitionController<DefaultTransitionController> implements Cloneable {
    protected AnimatorSet mAnimSet;
    private int mUpdateCount;

    /**
     * Wraps an Animator as a DefaultTransitionController
     *
     * @param anim
     * @return
     */
    public static DefaultTransitionController wrapAnimator(@NonNull Animator anim) {
        AnimatorSet set = new AnimatorSet();
        set.play(anim);
        return new DefaultTransitionController(set);
    }

    /**
     * Wraps an AnimatorSet as a DefaultTransitionController
     *
     * @param animSet
     * @return
     */
    public static DefaultTransitionController wrapAnimatorSet(@NonNull AnimatorSet animSet) {
        return new DefaultTransitionController(animSet);
    }

    public DefaultTransitionController(@NonNull AnimatorSet mAnimSet) {
        this(null, mAnimSet);
    }

    /**
     * @param target   the View that should be transitioned
     * @param mAnimSet
     */
    public DefaultTransitionController(@Nullable View target, @NonNull AnimatorSet mAnimSet) {
        super(target);
        this.mAnimSet = mAnimSet;
        mStartDelay = mAnimSet.getStartDelay();

        ArrayList<Animator> animators = mAnimSet.getChildAnimations();
        final int size = animators.size();
        Animator animator;
        for (int i = 0; i < size; i++) {
            animator = animators.get(i);
            if (!(animator instanceof ValueAnimator)) {
                throw new UnsupportedOperationException("Only ValueAnimator and its subclasses are supported: " + animator);
            }
        }
        mDuration = mAnimSet.getDuration();
        if (mAnimSet.getDuration() >= 0) {
            long duration = mAnimSet.getDuration();
            for (int i = 0; i < size; i++) {
                animators.get(i).setDuration(duration);
            }
        } else {
            for (int i = 0; i < size; i++) {
                animator = animators.get(i);
                long endTime = animator.getStartDelay() + animator.getDuration();
                if (mDuration < endTime) {
                    mDuration = endTime;
                }
            }
        }
        mTotalDuration = mStartDelay + mDuration;
        updateProgressWidth();
    }

    @Override
    public void start() {
        super.start();
        if (TransitionConfig.isDebug()) {
            getTransitionStateHolder().clear();
        }
        if (mTarget == null && mInterpolator == null) {
            return;
        }
        ArrayList<Animator> animators = mAnimSet.getChildAnimations();
        final int size = animators.size();
        Animator animator;
        for (int i = 0; i < size; i++) {
            animator = animators.get(i);
            if (mTarget != null) {
                animator.setTarget(mTarget);
            }
            if (mInterpolator != null) {
                animator.setInterpolator(mInterpolator);
            }
        }
    }

    @Override
    public void updateProgress(float progress) {
        String debug = "";
        final boolean DEBUG = TransitionConfig.isDebug();

        long time = 0;
        if (mStart < mEnd && progress >= mStart && progress <= mEnd || mStart > mEnd && progress >= mEnd && progress <= mStart) {
            //forward progression
            if (mStart < mEnd) {
                time = (long) (mTotalDuration * (progress - mStart) / mProgressWidth);
                //backward
            } else {
                time = (long) (mTotalDuration - mTotalDuration * (progress - mEnd) / mProgressWidth);
            }
            time -= mStartDelay;

            if (time > 0) {
                mStarted = true;
            }
            if (DEBUG) {
                debug = "forward progression: [" + mStart + ".." + mEnd + "], mStarted=" + mStarted;
            }
            mUpdateCount++;
        } else {
            //forward
            if (mStart < mEnd) {
                if (progress < mStart) {
                    time = 0;
                    if (DEBUG) {
                        debug = "forward progression: [" + mStart + ".." + mEnd + "], pre-start, progress=" + progress;
                    }
                } else if (progress > mEnd) {
                    time = mTotalDuration;
                    if (mUpdateCount == 1) {
                        mUpdateCount = -1;
                    }
                    if (DEBUG) {
                        debug = "forward progression: [" + mStart + ".." + mEnd + "], post-finish, progress=" + progress;
                    }
                }
                //backward
            } else if (mStart > mEnd) {
                if (progress > mStart) {
                    time = 0;
                    if (DEBUG) {
                        debug = "forward progression: [" + mStart + ".." + mEnd + "], pre-start, progress=" + progress;
                    }
                } else if (progress < mEnd) {
                    time = mTotalDuration;
                    if (mUpdateCount == 1) {
                        mUpdateCount = -1;
                    }
                    if (DEBUG) {
                        debug = "forward progression: [" + mStart + ".." + mEnd + "], post-finish, progress=" + progress;
                    }
                }
            }
        }

        //TODO hack to make it work for ViewPager, removing mUpdateStateAfterUpdateProgress would break it for everything else
//        if (mSetup && mUpdateStateAfterUpdateProgress) {
        updateState(time);
//        }

        if (DEBUG) {
            appendLog("updateProgress: \t" + debug);
        }
    }

    private void updateState(long time) {
        if ((time == mLastTime || (!mStarted && !mSetup)) && mUpdateCount != -1) {
            return;
        }

        if (TransitionConfig.isDebug()) {
            appendLog("updateState: \t\ttime=" + time);
        }

        mSetup = false;
        mLastTime = time;
        ArrayList<Animator> animators = mAnimSet.getChildAnimations();
        final int size = animators.size();
        for (int i = 0; i < size; i++) {
            ValueAnimator va = (ValueAnimator) animators.get(i);
            long absTime = time - va.getStartDelay();
            if (absTime >= 0) {
                va.setCurrentPlayTime(absTime);
            }
        }
    }

    private void appendLog(String msg) {
        getTransitionStateHolder().append(getId() + "->View" + mTarget.hashCode(), this, msg);
    }

    @CheckResult
    @Override
    public DefaultTransitionController clone() {
        return (DefaultTransitionController) super.clone();
    }

    protected DefaultTransitionController self() {
        return this;
    }
}