/*
 * Copyright (C) 2016 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
 *
 *   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 com.android.calculator2;

import android.animation.ArgbEvaluator;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.widget.TextView;

/**
 * Contains the logic for animating the recyclerview elements on drag.
 */
public final class DragController {

    private static final String TAG = "DragController";

    private static final ArgbEvaluator mColorEvaluator = new ArgbEvaluator();

    // References to views from the Calculator Display.
    private CalculatorFormula mDisplayFormula;
    private CalculatorResult mDisplayResult;
    private View mToolbar;

    private int mFormulaTranslationY;
    private int mFormulaTranslationX;
    private float mFormulaScale;
    private float mResultScale;

    private float mResultTranslationY;
    private int mResultTranslationX;

    private int mDisplayHeight;

    private int mFormulaStartColor;
    private int mFormulaEndColor;

    private int mResultStartColor;
    private int mResultEndColor;

    // The padding at the bottom of the RecyclerView itself.
    private int mBottomPaddingHeight;

    private boolean mAnimationInitialized;

    private boolean mOneLine;
    private boolean mIsDisplayEmpty;

    private AnimationController mAnimationController;

    private Evaluator mEvaluator;

    public void setEvaluator(Evaluator evaluator) {
        mEvaluator = evaluator;
    }

    public void initializeController(boolean isResult, boolean oneLine, boolean isDisplayEmpty) {
        mOneLine = oneLine;
        mIsDisplayEmpty = isDisplayEmpty;
        if (mIsDisplayEmpty) {
            // Empty display
            mAnimationController = new EmptyAnimationController();
        } else if (isResult) {
            // Result
            mAnimationController = new ResultAnimationController();
        } else {
            // There is something in the formula field. There may or may not be
            // a quick result.
            mAnimationController = new AnimationController();
        }
    }

    public void setDisplayFormula(CalculatorFormula formula) {
        mDisplayFormula = formula;
    }

    public void setDisplayResult(CalculatorResult result) {
        mDisplayResult = result;
    }

    public void setToolbar(View toolbar) {
        mToolbar = toolbar;
    }

    public void animateViews(float yFraction, RecyclerView recyclerView) {
        if (mDisplayFormula == null
                || mDisplayResult == null
                || mToolbar == null
                || mEvaluator == null) {
            // Bail if we aren't yet initialized.
            return;
        }

        final HistoryAdapter.ViewHolder vh =
                (HistoryAdapter.ViewHolder) recyclerView.findViewHolderForAdapterPosition(0);
        if (yFraction > 0 && vh != null) {
            recyclerView.setVisibility(View.VISIBLE);
        }
        if (vh != null && !mIsDisplayEmpty
                && vh.getItemViewType() == HistoryAdapter.HISTORY_VIEW_TYPE) {
            final AlignedTextView formula = vh.getFormula();
            final CalculatorResult result = vh.getResult();
            final TextView date = vh.getDate();
            final View divider = vh.getDivider();

            if (!mAnimationInitialized) {
                mBottomPaddingHeight = recyclerView.getPaddingBottom();

                mAnimationController.initializeScales(formula, result);

                mAnimationController.initializeColorAnimators(formula, result);

                mAnimationController.initializeFormulaTranslationX(formula);

                mAnimationController.initializeFormulaTranslationY(formula, result);

                mAnimationController.initializeResultTranslationX(result);

                mAnimationController.initializeResultTranslationY(result);

                mAnimationInitialized = true;
            }

            result.setScaleX(mAnimationController.getResultScale(yFraction));
            result.setScaleY(mAnimationController.getResultScale(yFraction));

            formula.setScaleX(mAnimationController.getFormulaScale(yFraction));
            formula.setScaleY(mAnimationController.getFormulaScale(yFraction));

            formula.setPivotX(formula.getWidth() - formula.getPaddingEnd());
            formula.setPivotY(formula.getHeight() - formula.getPaddingBottom());

            result.setPivotX(result.getWidth() - result.getPaddingEnd());
            result.setPivotY(result.getHeight() - result.getPaddingBottom());

            formula.setTranslationX(mAnimationController.getFormulaTranslationX(yFraction));
            formula.setTranslationY(mAnimationController.getFormulaTranslationY(yFraction));

            result.setTranslationX(mAnimationController.getResultTranslationX(yFraction));
            result.setTranslationY(mAnimationController.getResultTranslationY(yFraction));

            formula.setTextColor((int) mColorEvaluator.evaluate(yFraction, mFormulaStartColor,
                    mFormulaEndColor));

            result.setTextColor((int) mColorEvaluator.evaluate(yFraction, mResultStartColor,
                    mResultEndColor));

            date.setTranslationY(mAnimationController.getDateTranslationY(yFraction));
            divider.setTranslationY(mAnimationController.getDateTranslationY(yFraction));
        } else if (mIsDisplayEmpty) {
            // There is no current expression but we still need to collect information
            // to translate the other viewholders.
            if (!mAnimationInitialized) {
                mAnimationController.initializeDisplayHeight();
                mAnimationInitialized = true;
            }
        }

        // Move up all ViewHolders above the current expression; if there is no current expression,
        // we're translating all the viewholders.
        for (int i = recyclerView.getChildCount() - 1;
             i >= mAnimationController.getFirstTranslatedViewHolderIndex();
             --i) {
            final RecyclerView.ViewHolder vh2 =
                    recyclerView.getChildViewHolder(recyclerView.getChildAt(i));
            if (vh2 != null) {
                final View view = vh2.itemView;
                if (view != null) {
                    view.setTranslationY(
                        mAnimationController.getHistoryElementTranslationY(yFraction));
                }
            }
        }
    }

    /**
     * Reset all initialized values.
     */
    public void initializeAnimation(boolean isResult, boolean oneLine, boolean isDisplayEmpty) {
        mAnimationInitialized = false;
        initializeController(isResult, oneLine, isDisplayEmpty);
    }

    public interface AnimateTextInterface {

        void initializeDisplayHeight();

        void initializeColorAnimators(AlignedTextView formula, CalculatorResult result);

        void initializeScales(AlignedTextView formula, CalculatorResult result);

        void initializeFormulaTranslationX(AlignedTextView formula);

        void initializeFormulaTranslationY(AlignedTextView formula, CalculatorResult result);

        void initializeResultTranslationX(CalculatorResult result);

        void initializeResultTranslationY(CalculatorResult result);

        float getResultTranslationX(float yFraction);

        float getResultTranslationY(float yFraction);

        float getResultScale(float yFraction);

        float getFormulaScale(float yFraction);

        float getFormulaTranslationX(float yFraction);

        float getFormulaTranslationY(float yFraction);

        float getDateTranslationY(float yFraction);

        float getHistoryElementTranslationY(float yFraction);

        // Return the lowest index of the first Viewholder to be translated upwards.
        // If there is no current expression, we translate all the viewholders; otherwise,
        // we start at index 1.
        int getFirstTranslatedViewHolderIndex();
    }

    // The default AnimationController when Display is in INPUT state and DisplayFormula is not
    // empty. There may or may not be a quick result.
    public class AnimationController implements DragController.AnimateTextInterface {

        public void initializeDisplayHeight() {
            // no-op
        }

        public void initializeColorAnimators(AlignedTextView formula, CalculatorResult result) {
            mFormulaStartColor = mDisplayFormula.getCurrentTextColor();
            mFormulaEndColor = formula.getCurrentTextColor();

            mResultStartColor = mDisplayResult.getCurrentTextColor();
            mResultEndColor = result.getCurrentTextColor();
        }

        public void initializeScales(AlignedTextView formula, CalculatorResult result) {
            // Calculate the scale for the text
            mFormulaScale = mDisplayFormula.getTextSize() / formula.getTextSize();
        }

        public void initializeFormulaTranslationY(AlignedTextView formula,
                CalculatorResult result) {
            if (mOneLine) {
                // Disregard result since we set it to GONE in the one-line case.
                mFormulaTranslationY =
                        mDisplayFormula.getPaddingBottom() - formula.getPaddingBottom()
                        - mBottomPaddingHeight;
            } else {
                // Baseline of formula moves by the difference in formula bottom padding and the
                // difference in result height.
                mFormulaTranslationY =
                        mDisplayFormula.getPaddingBottom() - formula.getPaddingBottom()
                                + mDisplayResult.getHeight() - result.getHeight()
                                - mBottomPaddingHeight;
            }
        }

        public void initializeFormulaTranslationX(AlignedTextView formula) {
            // Right border of formula moves by the difference in formula end padding.
            mFormulaTranslationX = mDisplayFormula.getPaddingEnd() - formula.getPaddingEnd();
        }

        public void initializeResultTranslationY(CalculatorResult result) {
            // Baseline of result moves by the difference in result bottom padding.
            mResultTranslationY = mDisplayResult.getPaddingBottom() - result.getPaddingBottom()
            - mBottomPaddingHeight;
        }

        public void initializeResultTranslationX(CalculatorResult result) {
            mResultTranslationX = mDisplayResult.getPaddingEnd() - result.getPaddingEnd();
        }

        public float getResultTranslationX(float yFraction) {
            return mResultTranslationX * (yFraction - 1f);
        }

        public float getResultTranslationY(float yFraction) {
            return mResultTranslationY * (yFraction - 1f);
        }

        public float getResultScale(float yFraction) {
            return 1f;
        }

        public float getFormulaScale(float yFraction) {
            return mFormulaScale + (1f - mFormulaScale) * yFraction;
        }

        public float getFormulaTranslationX(float yFraction) {
            return mFormulaTranslationX * (yFraction - 1f);
        }

        public float getFormulaTranslationY(float yFraction) {
            // Scale linearly between -FormulaTranslationY and 0.
            return mFormulaTranslationY * (yFraction - 1f);
        }

        public float getDateTranslationY(float yFraction) {
            // We also want the date to start out above the visible screen with
            // this distance decreasing as it's pulled down.
            // Account for the scaled formula height.
            return -mToolbar.getHeight() * (1f - yFraction)
                    + getFormulaTranslationY(yFraction)
                    - mDisplayFormula.getHeight() /getFormulaScale(yFraction) * (1f - yFraction);
        }

        public float getHistoryElementTranslationY(float yFraction) {
            return getDateTranslationY(yFraction);
        }

        public int getFirstTranslatedViewHolderIndex() {
            return 1;
        }
    }

    // The default AnimationController when Display is in RESULT state.
    public class ResultAnimationController extends AnimationController
            implements DragController.AnimateTextInterface {
        @Override
        public void initializeScales(AlignedTextView formula, CalculatorResult result) {
            final float textSize = mDisplayResult.getTextSize() * mDisplayResult.getScaleX();
            mResultScale = textSize / result.getTextSize();
            mFormulaScale = 1f;
        }

        @Override
        public void initializeFormulaTranslationY(AlignedTextView formula,
                CalculatorResult result) {
            // Baseline of formula moves by the difference in formula bottom padding and the
            // difference in the result height.
            mFormulaTranslationY = mDisplayFormula.getPaddingBottom() - formula.getPaddingBottom()
                            + mDisplayResult.getHeight() - result.getHeight()
                            - mBottomPaddingHeight;
        }

        @Override
        public void initializeFormulaTranslationX(AlignedTextView formula) {
            // Right border of formula moves by the difference in formula end padding.
            mFormulaTranslationX = mDisplayFormula.getPaddingEnd() - formula.getPaddingEnd();
        }

        @Override
        public void initializeResultTranslationY(CalculatorResult result) {
            // Baseline of result moves by the difference in result bottom padding.
            mResultTranslationY =  mDisplayResult.getPaddingBottom() - result.getPaddingBottom()
                    - mDisplayResult.getTranslationY()
                    - mBottomPaddingHeight;
        }

        @Override
        public void initializeResultTranslationX(CalculatorResult result) {
            mResultTranslationX = mDisplayResult.getPaddingEnd() - result.getPaddingEnd();
        }

        @Override
        public float getResultTranslationX(float yFraction) {
            return (mResultTranslationX * yFraction) - mResultTranslationX;
        }

        @Override
        public float getResultTranslationY(float yFraction) {
            return (mResultTranslationY * yFraction) - mResultTranslationY;
        }

        @Override
        public float getFormulaTranslationX(float yFraction) {
            return (mFormulaTranslationX * yFraction) -
                    mFormulaTranslationX;
        }

        @Override
        public float getFormulaTranslationY(float yFraction) {
            return getDateTranslationY(yFraction);
        }

        @Override
        public float getResultScale(float yFraction) {
            return mResultScale - (mResultScale * yFraction) + yFraction;
        }

        @Override
        public float getFormulaScale(float yFraction) {
            return 1f;
        }

        @Override
        public float getDateTranslationY(float yFraction) {
            // We also want the date to start out above the visible screen with
            // this distance decreasing as it's pulled down.
            return -mToolbar.getHeight() * (1f - yFraction)
                    + (mResultTranslationY * yFraction) - mResultTranslationY
                    - mDisplayFormula.getPaddingTop() +
                    (mDisplayFormula.getPaddingTop() * yFraction);
        }

        @Override
        public int getFirstTranslatedViewHolderIndex() {
            return 1;
        }
    }

    // The default AnimationController when Display is completely empty.
    public class EmptyAnimationController extends AnimationController
            implements DragController.AnimateTextInterface {
        @Override
        public void initializeDisplayHeight() {
            mDisplayHeight = mToolbar.getHeight() + mDisplayResult.getHeight()
                    + mDisplayFormula.getHeight();
        }

        @Override
        public void initializeScales(AlignedTextView formula, CalculatorResult result) {
            // no-op
        }

        @Override
        public void initializeFormulaTranslationY(AlignedTextView formula,
                CalculatorResult result) {
            // no-op
        }

        @Override
        public void initializeFormulaTranslationX(AlignedTextView formula) {
            // no-op
        }

        @Override
        public void initializeResultTranslationY(CalculatorResult result) {
            // no-op
        }

        @Override
        public void initializeResultTranslationX(CalculatorResult result) {
            // no-op
        }

        @Override
        public float getResultTranslationX(float yFraction) {
            return 0f;
        }

        @Override
        public float getResultTranslationY(float yFraction) {
            return 0f;
        }

        @Override
        public float getFormulaScale(float yFraction) {
            return 1f;
        }

        @Override
        public float getDateTranslationY(float yFraction) {
            return 0f;
        }

        @Override
        public float getHistoryElementTranslationY(float yFraction) {
            return -mDisplayHeight * (1f - yFraction) - mBottomPaddingHeight;
        }

        @Override
        public int getFirstTranslatedViewHolderIndex() {
            return 0;
        }
    }
}
