/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.gecko;

import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Transformation;

import java.lang.ref.WeakReference;
import java.util.WeakHashMap;

public class AnimatorProxy {
    private static final WeakHashMap<View, AnimatorProxy> PROXIES =
            new WeakHashMap<View, AnimatorProxy>();

    private static interface AnimatorProxyImpl {
        public float getAlpha();
        public void setAlpha(float alpha);

        public float getTranslationX();
        public void setTranslationX(float translationX);

        public float getTranslationY();
        public void setTranslationY(float translationY);

        public View getView();
    }

    private AnimatorProxyImpl mImpl;

    private AnimatorProxy(AnimatorProxyImpl impl) {
        mImpl = impl;
    }

    public static AnimatorProxy create(View view) {
        AnimatorProxy proxy = PROXIES.get(view);
        boolean needsAnimationProxy = (Build.VERSION.SDK_INT < 11);

        // If the view's animation proxy has been overridden from somewhere else, we need to
        // create a new AnimatorProxy for the view.
        if (proxy == null || (needsAnimationProxy && proxy.mImpl != view.getAnimation())) {
            AnimatorProxyImpl impl = (needsAnimationProxy ? new AnimatorProxyPreHC(view) :
                                                            new AnimatorProxyPostHC(view));

            proxy = new AnimatorProxy(impl);
            PROXIES.put(view, proxy);
        }

        return proxy;
    }

    public int getHeight() {
        View view = mImpl.getView();
        if (view != null)
            return view.getHeight();

        return 0;
    }

    public void setHeight(int height) {
        View view = mImpl.getView();
        if (view != null) {
            ViewGroup.LayoutParams lp = view.getLayoutParams();
            lp.height = height;
            view.setLayoutParams(lp);
        }
    }

    public int getScrollX() {
        View view = mImpl.getView();
        if (view != null)
            return view.getScrollX();

        return 0;
    }

    public int getScrollY() {
        View view = mImpl.getView();
        if (view != null)
            return view.getScrollY();

        return 0;
    }

    public void scrollTo(int scrollX, int scrollY) {
        View view = mImpl.getView();
        if (view != null)
            view.scrollTo(scrollX, scrollY);
    }

    public float getAlpha() {
        return mImpl.getAlpha();
    }

    public void setAlpha(float alpha) {
        mImpl.setAlpha(alpha);
    }

    public float getTranslationX() {
        return mImpl.getTranslationX();
    }

    public void setTranslationX(float translationX) {
        mImpl.setTranslationX(translationX);
    }

    public float getTranslationY() {
        return mImpl.getTranslationY();
    }

    public void setTranslationY(float translationY) {
        mImpl.setTranslationY(translationY);
    }

    /*
     * AnimatorProxyPreHC uses the technique used by the NineOldAndroids described here:
     * http://jakewharton.com/advanced-pre-honeycomb-animation/
     *
     * Some of this code is based on Jake Wharton's AnimatorProxy released as part of
     * the NineOldAndroids library under the Apache License 2.0.
     */
    private static class AnimatorProxyPreHC extends Animation implements AnimatorProxyImpl {
        private WeakReference<View> mViewRef;

        private final RectF mBefore;
        private final RectF mAfter;
        private final Matrix mTempMatrix;

        private float mAlpha;
        private float mTranslationX;
        private float mTranslationY;

        public AnimatorProxyPreHC(View view) {
            mBefore = new RectF();
            mAfter = new RectF();
            mTempMatrix = new Matrix();

            mAlpha = 1;
            mTranslationX = 0;
            mTranslationY = 0;

            loadCurrentTransformation(view);

            setDuration(0);
            setFillAfter(true);
            view.setAnimation(this);

            mViewRef = new WeakReference<View>(view);
        }

        private void loadCurrentTransformation(View view) {
            Animation animation = view.getAnimation();
            if (animation == null)
                return;

            Transformation transformation = new Transformation();
            float[] matrix = new float[9];

            animation.getTransformation(AnimationUtils.currentAnimationTimeMillis(), transformation);
            transformation.getMatrix().getValues(matrix);

            mAlpha = transformation.getAlpha();
            mTranslationX = matrix[Matrix.MTRANS_X];
            mTranslationY = matrix[Matrix.MTRANS_Y];
        }

        private void prepareForUpdate() {
            View view = mViewRef.get();
            if (view != null)
                computeRect(mBefore, view);
        }

        private void computeRect(final RectF r, View view) {
            final float w = view.getWidth();
            final float h = view.getHeight();

            r.set(0, 0, w, h);

            final Matrix m = mTempMatrix;
            m.reset();
            transformMatrix(m, view);
            mTempMatrix.mapRect(r);

            r.offset(view.getLeft(), view.getTop());
        }

        private void transformMatrix(Matrix m, View view) {
            m.postTranslate(mTranslationX, mTranslationY);
        }

        private void invalidateAfterUpdate() {
            View view = mViewRef.get();
            if (view == null || view.getParent() == null)
                return;

            final RectF after = mAfter;
            computeRect(after, view);
            after.union(mBefore);

            ((View)view.getParent()).invalidate(
                    (int) Math.floor(after.left),
                    (int) Math.floor(after.top),
                    (int) Math.ceil(after.right),
                    (int) Math.ceil(after.bottom));
        }

        @Override
        public float getAlpha() {
            return mTranslationX;
        }

        @Override
        public void setAlpha(float alpha) {
            if (alpha == alpha)
                return;

            mAlpha = alpha;

            View view = mViewRef.get();
            if (view != null)
                view.invalidate();
        }

        @Override
        public float getTranslationX() {
            return mTranslationX;
        }

        @Override
        public void setTranslationX(float translationX) {
            if (mTranslationX == translationX)
                return;

            prepareForUpdate();
            mTranslationX = translationX;
            invalidateAfterUpdate();
        }

        @Override
        public float getTranslationY() {
            return mTranslationY;
        }

        @Override
        public void setTranslationY(float translationY) {
            if (mTranslationY == translationY)
                return;

            prepareForUpdate();
            mTranslationY = translationY;
            invalidateAfterUpdate();
        }

        @Override
        public View getView() {
            return mViewRef.get();
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            View view = mViewRef.get();
            if (view != null) {
                t.setAlpha(mAlpha);
                transformMatrix(t.getMatrix(), view);
            }
        }
    }

    private static class AnimatorProxyPostHC implements AnimatorProxyImpl {
        private WeakReference<View> mViewRef;

        public AnimatorProxyPostHC(View view) {
            mViewRef = new WeakReference<View>(view);
        }

        @Override
        public float getAlpha() {
            View view = mViewRef.get();
            if (view != null)
                return view.getAlpha();

            return 1;
        }

        @Override
        public void setAlpha(float alpha) {
            View view = mViewRef.get();
            if (view != null)
                view.setAlpha(alpha);
        }

        @Override
        public float getTranslationX() {
            View view = mViewRef.get();
            if (view != null)
                return view.getTranslationX();

            return 0;
        }

        @Override
        public void setTranslationX(float translationX) {
            View view = mViewRef.get();
            if (view != null)
                view.setTranslationX(translationX);
        }

        @Override
        public float getTranslationY() {
            View view = mViewRef.get();
            if (view != null)
                return view.getTranslationY();

            return 0;
        }

        @Override
        public void setTranslationY(float translationY) {
            View view = mViewRef.get();
            if (view != null)
                view.setTranslationY(translationY);
        }

        @Override
        public View getView() {
            return mViewRef.get();
        }
    }
}

