/*
 *  Copyright 2017 David Ganster
 *
 *  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
 *
 *  http://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 at.wirecube.additiveanimations.additive_animator;

import android.animation.TimeInterpolator;
import android.animation.TypeEvaluator;
import android.graphics.Path;
import android.util.Property;

import at.wirecube.additiveanimations.additive_animator.animation_set.AnimationState;
import at.wirecube.additiveanimations.helper.evaluators.PathEvaluator;

/**
 * This class is public for subclasses of AdditiveAnimator only, and should not be used outside of that.
 */
public class AdditiveAnimation<T> {

    private String mTag;
    private float mStartValue;
    private float mTargetValue;
    private Property<T, Float> mProperty;
    private Path mPath;
    private PathEvaluator.PathMode mPathMode;
    private PathEvaluator mSharedPathEvaluator;
    private TypeEvaluator<Float> mCustomTypeEvaluator;
    private T mTarget;
    private int mHashCode;
    private TimeInterpolator mCustomInterpolator; // each animation can have its own interpolator
    private AccumulatedAnimationValue mAccumulatedValue;
    private AnimationState<T> mAssociatedAnimationState;

    /**
     * The preferred constructor to use when animating properties. If you use this constructor, you
     * don't need to worry about the logic to apply the changes. This is taken care of by using the
     * Setter provided by `property`.
     */
    public AdditiveAnimation(T target, Property<T, Float> property, float startValue, float targetValue) {
        mTarget = target;
        mProperty = property;
        mTargetValue = targetValue;
        mStartValue = startValue;
        setTag(property.getName());
    }

    /**
     * Use this constructor for custom properties that have no simple getter or setter.
     * @param tag Name of the animated property. Must be unique.
     * @param startValue Start value of the animated property.
     * @param targetValue Target value of the animated property.
     */
    public AdditiveAnimation(T target, String tag, float startValue, float targetValue) {
        mTarget = target;
        mStartValue = startValue;
        mTargetValue = targetValue;
        setTag(tag);
    }

    public AdditiveAnimation(T target, String tag, float startValue, Path path, PathEvaluator.PathMode pathMode, PathEvaluator sharedEvaluator) {
        mTarget = target;
        mStartValue = startValue;
        mPath = path;
        mSharedPathEvaluator = sharedEvaluator;
        mPathMode = pathMode;
        mTargetValue = evaluateAt(1f);
        setTag(tag);
    }

    public AdditiveAnimation(T target, Property<T, Float> property, float startValue, Path path, PathEvaluator.PathMode pathMode, PathEvaluator sharedEvaluator) {
        mTarget = target;
        mProperty = property;
        mStartValue = startValue;
        mPath = path;
        mSharedPathEvaluator = sharedEvaluator;
        mPathMode = pathMode;
        mTargetValue = evaluateAt(1f);
        setTag(property.getName());
    }

    public void setAccumulatedValue(AccumulatedAnimationValue av) {
        mAccumulatedValue = av;
    }

    private void setTag(String tag) {
        mTag = tag;
        // TODO: find a good hash code that doesn't collide often
        mHashCode = mTag.hashCode() * ((2 << 17) - 1) + mTarget.hashCode();
    }

    public String getTag() {
        return mTag;
    }

    public float getStartValue() {
        return mStartValue;
    }

    public float getTargetValue() {
        return mTargetValue;
    }

    public void setStartValue(float startValue) {
        this.mStartValue = startValue;
    }

    public void setCustomTypeEvaluator(TypeEvaluator<Float> evaluator) {
        mCustomTypeEvaluator = evaluator;
    }

    public TypeEvaluator getCustomTypeEvaluator() {
        return mCustomTypeEvaluator;
    }

    public T getTarget() {
        return mTarget;
    }

    public Property<T, Float> getProperty() { return mProperty; }

    public Path getPath() {
        return mPath;
    }

    public void setCustomInterpolator(TimeInterpolator customInterpolator) {
        mCustomInterpolator = customInterpolator;
    }

    public float evaluateAt(float progress) {
        if(mCustomInterpolator != null) {
            progress = mCustomInterpolator.getInterpolation(progress);
        }
        if(mPath != null) {
            return mSharedPathEvaluator.evaluate(progress, mPathMode, mPath);
        } else {
            if(mCustomTypeEvaluator != null) {
                return mCustomTypeEvaluator.evaluate(progress, mStartValue, mTargetValue);
            } else {
                return mStartValue + (mTargetValue - mStartValue) * progress;
            }
        }
    }

    public AccumulatedAnimationValue getAccumulatedValue() {
        return mAccumulatedValue;
    }

    public AdditiveAnimation<T> cloneWithTarget(T target, Float startValue) {
        final AdditiveAnimation<T> animation;
        if(this.getProperty() != null) {
            if (this.getPath() != null) {
                animation = new AdditiveAnimation<>(target, mProperty, startValue, getPath(), mPathMode, mSharedPathEvaluator);
            } else {
                animation = new AdditiveAnimation<>(target, mProperty, startValue, mTargetValue);
            }
        } else {
            if(this.getPath() != null) {
                animation = new AdditiveAnimation<>(target, mTag, startValue, getPath(), mPathMode, mSharedPathEvaluator);
            } else {
                animation = new AdditiveAnimation<>(target, mTag, startValue, mTargetValue);
            }
        }
        if(mCustomInterpolator != null) {
            animation.setCustomInterpolator(mCustomInterpolator);
        }
        if(mCustomTypeEvaluator != null) {
            animation.setCustomTypeEvaluator(mCustomTypeEvaluator);
        }
        if(mAssociatedAnimationState != null) {
            animation.setAssociatedAnimationState(mAssociatedAnimationState);
        }
        return animation;
    }

    public void setAssociatedAnimationState(AnimationState<T> associatedAnimationStateId) {
        this.mAssociatedAnimationState = associatedAnimationStateId;
    }

    public AnimationState<T> getAssociatedAnimationState() {
        return mAssociatedAnimationState;
    }

    @Override
    public int hashCode() {
        return mHashCode;
    }

    @Override
    public boolean equals(Object o) {
        if(this == o) {
            return true;
        }
        if(!(o instanceof AdditiveAnimation)) {
            return false;
        }
        AdditiveAnimation other = (AdditiveAnimation) o;
        return other.mTag.hashCode() == mTag.hashCode() && other.mTarget == mTarget;
    }
}