
package org.zarroboogs.weibo.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.view.animation.AnimationUtils;
import android.widget.AbsListView;

import java.util.ArrayList;
import java.util.List;

public class VelocityListView extends AutoScrollListView {

    /**
     * A callback to be notified the velocity has changed.
     *
     * @author Cyril Mottier
     */
    public interface OnVelocityListViewListener {

        void onVelocityChanged(int velocity);
    }

    public interface OnVelocityEqualZeroListener {

        void onZero();
    }

    private static final long INVALID_TIME = -1;

    /**
     * This value is really necessary to avoid weird velocity values. Indeed, in fly-wheel mode,
     * onScroll is called twice per-frame which results in having a delta divided by a value close
     * to zero. onScroll is usually being called 60 times per seconds (i.e. every 16ms) so 10ms is a
     * good threshold.
     */
    private static final long MINIMUM_TIME_DELTA = 10L;

    private final ForwardingOnScrollListener mForwardingOnScrollListener = new ForwardingOnScrollListener();

    private OnVelocityListViewListener mOnVelocityListViewListener;

    private OnVelocityEqualZeroListener onVelocityEqualZeroListener;

    private long mTime = INVALID_TIME;

    private int mVelocity;

    private int mFirstVisiblePosition;

    private int mFirstVisibleViewTop;

    private int mLastVisiblePosition;

    private int mLastVisibleViewTop;

    private int mLastItemCount;

    public static final int TOWARDS_BOTTOM = 0;

    public static final int TOWARDS_TOP = 1;

    private int towardsOrientation = TOWARDS_BOTTOM;

    public VelocityListView(Context context) {
        super(context);
        init();
    }

    public VelocityListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public VelocityListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        super.setOnScrollListener(mForwardingOnScrollListener);
        mForwardingOnScrollListener.selfListener = mOnScrollListener;
    }

    public void setOnVelocityEqualZeroListener(OnVelocityEqualZeroListener l) {
        onVelocityEqualZeroListener = l;
    }

    @Override
    public void setOnScrollListener(OnScrollListener l) {
        mForwardingOnScrollListener.clientListener.add(l);
    }

    public void setOnVelocityListener(OnVelocityListViewListener l) {
        mOnVelocityListViewListener = l;
    }

    /**
     * Return an approximative value of the ListView's current velocity on the Y-axis. A negative
     * value indicates the ListView is currently being scrolled towards the bottom (i.e items are
     * moving from bottom to top) while a positive value indicates it is currently being scrolled
     * towards the top (i.e. items are moving from top to bottom).
     *
     * @return An approximative value of the ListView's velocity on the Y-axis
     */
    public int getVelocity() {
        return mVelocity;
    }

    private void setVelocity(int velocity) {
        if (mVelocity != velocity) {
            mVelocity = velocity;
            if (mOnVelocityListViewListener != null) {
                mOnVelocityListViewListener.onVelocityChanged(velocity);
            }

            if (onVelocityEqualZeroListener != null && (mTime == INVALID_TIME)) {
                onVelocityEqualZeroListener.onZero();
            }
            if (velocity < 0) {
                towardsOrientation = TOWARDS_BOTTOM;
            } else if (velocity > 0) {
                towardsOrientation = TOWARDS_TOP;

            }
        }
    }

    public int getTowardsOrientation() {
        return towardsOrientation;
    }

    /**
     * @author Cyril Mottier
     */
    private static class ForwardingOnScrollListener implements OnScrollListener {

        private OnScrollListener selfListener;

        private List<OnScrollListener> clientListener = new ArrayList<OnScrollListener>();

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

            if (selfListener != null) {
                selfListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
            }

            for (OnScrollListener l : clientListener) {
                l.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
            }

        }

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if (selfListener != null) {
                selfListener.onScrollStateChanged(view, scrollState);
            }
            for (OnScrollListener l : clientListener) {
                l.onScrollStateChanged(view, scrollState);
            }
        }
    }

    private OnScrollListener mOnScrollListener = new OnScrollListener() {
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            switch (scrollState) {
                case SCROLL_STATE_IDLE:
                    mTime = INVALID_TIME;
                    setVelocity(0);
                    break;

                default:
                    break;
            }
        }

        @Override
        public void onScroll(AbsListView view, int firstVisiblePosition, int visibleItemCount, int totalItemCount) {

            if (visibleItemCount == 0) {
                return;
            }

            final long now = AnimationUtils.currentAnimationTimeMillis();
            final int lastVisiblePosition = firstVisiblePosition + visibleItemCount - 1;

            // calc position,because ListView may has new item because adapter
            // has something new
            int currentItemCount = totalItemCount;
            int v = currentItemCount - mLastItemCount;

            mFirstVisiblePosition += v;
            mLastVisiblePosition += v;

            if (mTime != INVALID_TIME) {

                final long delta = now - mTime;
                if (now - mTime > MINIMUM_TIME_DELTA) {
                    int distance = 0;
                    // @formatter:off
                    if (mFirstVisiblePosition >= firstVisiblePosition && mFirstVisiblePosition <= lastVisiblePosition) {
                        distance = getChildAt(mFirstVisiblePosition - firstVisiblePosition).getTop() - mFirstVisibleViewTop;

                    } else if (mLastVisiblePosition >= firstVisiblePosition && mLastVisiblePosition <= lastVisiblePosition) {
                        distance = getChildAt(mLastVisiblePosition - firstVisiblePosition).getTop() - mLastVisibleViewTop;
                        // @formatter:on
                    } else {
                        // We're in a case were the item we were previously
                        // referencing has moved out of the visible window.
                        // Let's compute an approximative distance
                        int heightSum = 0;
                        for (int i = 0; i < visibleItemCount; i++) {
                            heightSum += getChildAt(i).getHeight();
                        }

                        distance = heightSum / visibleItemCount * (mFirstVisiblePosition - firstVisiblePosition);
                    }

                    setVelocity((int) (1000d * distance / delta));
                }
            }

            mFirstVisiblePosition = firstVisiblePosition;
            mFirstVisibleViewTop = getChildAt(0).getTop();
            mLastVisiblePosition = lastVisiblePosition;
            mLastVisibleViewTop = getChildAt(visibleItemCount - 1).getTop();
            mLastItemCount = totalItemCount;
            mTime = now;
        }
    };

}
