package com.kaichunlin.transition;

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Provides common implementations for all transitions.
 */
public abstract class AbstractTransition<T extends AbstractTransition, S extends AbstractTransition.Setup> implements Transition<S> {
    List<S> mSetupList = new ArrayList<>();
    String mId;
    boolean mReverse;
    Interpolator mInterpolator;
    View mTarget;
    boolean mUpdateStateAfterUpdateProgress;

    public AbstractTransition(@Nullable String id) {
        this.mId = id;
    }

    @Override
    public T setId(@Nullable String id) {
        mId = id;
        return self();
    }

    @Override
    public String getId() {
        return mId == null ? (mId = toString()) : mId;
    }

    @Override
    public boolean startTransition(float progress) {
        if (startTransition()) {
            updateProgress(progress);
            return true;
        }
        return false;
    }

    @Override
    public void setProgress(float progress) {
        //TODO optimize
        startTransition();
        updateProgress(progress);
        stopTransition();
    }

    @Override
    public T reverse() {
        String id = getId();
        String REVERSE = "_REVERSE";
        if (id.endsWith(REVERSE)) {
            setId(id.substring(0, id.length() - REVERSE.length()));
        } else {
            setId(id + REVERSE);
        }

        mReverse = !mReverse;
        return self();
    }

    @Override
    public T setSetup(@NonNull S setup) {
        if (setup != null) {
            mSetupList.add(setup);
        }
        return self();
    }

    /**
     * Only true if it has merged another Transition
     *
     * @return
     */
    boolean hasMultipleSetup() {
        return mSetupList.size() > 1;
    }

    @Override
    public void setTarget(@Nullable View target) {
        mTarget = target;
        invalidate();
    }

    @Override
    public View getTarget() {
        return mTarget;
    }

    @Override
    public T setUpdateStateAfterUpdateProgress(boolean updateStateAfterUpdateProgress) {
        mUpdateStateAfterUpdateProgress = updateStateAfterUpdateProgress;
        return self();
    }

    /**
     * Invalidates the current transition, which may mean the currently running transition is stopped.
     */
    protected abstract void invalidate();

    // Returns the correct type.
    protected abstract T self();

    @Override
    public T setInterpolator(@Nullable Interpolator interpolator) {
        mInterpolator = interpolator;
        return self();
    }

    @CheckResult
    @Override
    public AbstractTransition clone() {
        AbstractTransition newClone = null;
        try {
            newClone = (AbstractTransition) super.clone();
            newClone.setId(newClone.getId() + "_CLONE");
            newClone.mSetupList = new ArrayList<>(mSetupList.size());
            newClone.mSetupList.addAll(mSetupList);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return newClone;
    }

    /**
     * Checks to see if another AbstractTransition's states is isCompatible for merging.
     *
     * @param another
     * @return
     */
    @CheckResult
    public boolean isCompatible(AbstractTransition another) {
        if (getClass().equals(another.getClass()) && mTarget == another.mTarget && mReverse == another.mReverse && ((mInterpolator == null && another.mInterpolator == null) ||
                mInterpolator.getClass().equals(another.mInterpolator.getClass()))) {
            return true;
        }
        return false;
    }

    /**
     * Merge another AbstractTransition's states into this object, such that the other AbstractTransition
     * can be discarded.
     *
     * @param another
     * @return true if the merge is successful.
     */
    @CheckResult
    public boolean merge(AbstractTransition another) {
        if (!isCompatible(another)) {
            return false;
        }
        if (another.mId != null) {
            if (mId == null) {
                mId = another.mId;
            } else {
                StringBuilder sb = new StringBuilder(mId.length() + another.mId.length());
                sb.append(mId);
                sb.append("_MERGED_");
                sb.append(another.mId);
                mId = sb.toString();
            }
        }
        mUpdateStateAfterUpdateProgress |= another.mUpdateStateAfterUpdateProgress;
        mSetupList.addAll(another.mSetupList);
        Collections.sort(mSetupList, new Comparator<S>() {
            @Override
            public int compare(S lhs, S rhs) {
                if (lhs instanceof AbstractTransitionBuilder && rhs instanceof AbstractTransitionBuilder) {
                    AbstractTransitionBuilder left = (AbstractTransitionBuilder) lhs;
                    AbstractTransitionBuilder right = (AbstractTransitionBuilder) rhs;
                    float startLeft = left.mReverse ? left.mEnd : left.mStart;
                    float startRight = right.mReverse ? right.mEnd : right.mStart;
                    return (int) ((startRight - startLeft) * 1000);
                }
                return 0;
            }
        });

        return true;
    }

    /**
     * Represents an object that configures how a Transition for a specific effect.
     */
    public interface Setup {
    }
}

@@ -4101,33 +4101,16 @@
      */%0A
-    @CheckResult%0A
     publ

Remove @CheckResult annotation for AbstractTransition.merge() as it's not strictly necessary