package me.payge.swipeadapterview;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.os.Build;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.FrameLayout;


import java.util.ArrayList;

/**
 * @author payge
 * 通过ViewDragHelper实现拖拽滑动
 */
public class SwipeAdapterView extends AdapterView {

    private ArrayList<View> cache = new ArrayList<>();

    //缩放层叠效果
    private int yOffsetStep; // view叠加垂直偏移量的步长
    private static final float SCALE_STEP = 0.08f; // view叠加缩放的步长
    //缩放层叠效果

    private int MAX_VISIBLE = 4; // 值建议最小为4
    private int MIN_ADAPTER_STACK = 6;
    private float ROTATION_DEGREES = 2f;
    private int LAST_VIEW_IN_STACK = 0;

    private Adapter mAdapter;
    private onFlingListener mFlingListener;
    private AdapterDataSetObserver mDataSetObserver;
    private boolean mInLayout = false;
    private View mActiveCard = null;
    private OnItemClickListener mOnItemClickListener;

    // 是否支持拖拽滑动
    public boolean isNeedSwipe = true;
    // 是否支持左右滑动飞出边界动画
    public boolean isNeedSwipeAnim = true;

    private int initTop;
    private int initLeft;

    private boolean isSwipeAnimRunning;
    private int cLeft, cTop;

    private ViewDragHelper viewDragHelper;
    //private GestureDetectorCompat detector;

    private int widthMeasureSpec;
    private int heightMeasureSpec;

    private float x, y;

    public SwipeAdapterView(Context context) {
        this(context, null);
    }

    public SwipeAdapterView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SwipeAdapterView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeAdapterView, defStyle, 0);
        MAX_VISIBLE = a.getInt(R.styleable.SwipeAdapterView_max_visible, MAX_VISIBLE);
        MIN_ADAPTER_STACK = a.getInt(R.styleable.SwipeAdapterView_min_adapter_stack, MIN_ADAPTER_STACK);
        ROTATION_DEGREES = a.getFloat(R.styleable.SwipeAdapterView_rotation_degrees, ROTATION_DEGREES);
        yOffsetStep = a.getDimensionPixelOffset(R.styleable.SwipeAdapterView_y_offset_step, 0);
        a.recycle();

        viewDragHelper = ViewDragHelper.create(this, 4f, callback);
    }

    public void setIsNeedSwipe(boolean isNeedSwipe) {
        this.isNeedSwipe = isNeedSwipe;
    }

    public void setIsNeedSwipeAnim(boolean isNeedSwipeAnim) {
        this.isNeedSwipeAnim = isNeedSwipeAnim;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        this.widthMeasureSpec = widthMeasureSpec;
        this.heightMeasureSpec = heightMeasureSpec;
    }

    @Override
    public View getSelectedView() {
        return mActiveCard;
    }

    @Override
    public void setSelection(int position) {
        throw new UnsupportedOperationException("Not supported");
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (viewDragHelper.continueSettling(false)) {
            ViewCompat.postInvalidateOnAnimation(this);
        } else {
            if (isSwipeAnimRunning) {
                isSwipeAnimRunning = false;
                //Log.i("tag", "computeScroll");
                //adjustChildrenOfUnderTopView(1f);
                if (mFlingListener != null) {
                    if (mActiveCard.getLeft() > 0) {
                        mFlingListener.onRightCardExit(mAdapter.getItem(0));
                    } else {
                        mFlingListener.onLeftCardExit(mAdapter.getItem(0));
                    }
                    mFlingListener.removeFirstObjectInAdapter(mActiveCard);
                }
                mActiveCard = null;
            }
            cLeft = 0;
            cTop = 0;
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean b = viewDragHelper.shouldInterceptTouchEvent(ev);
        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
            viewDragHelper.processTouchEvent(ev);
        }
        float dx = 0, dy = 0;
        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
            x = ev.getX();
            y = ev.getY();
        } else if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
            dx = Math.abs(ev.getX() - x);
            dy = Math.abs(ev.getY() - y);
            x = ev.getX();
            y = ev.getY();
        } else if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
            x = 0;
            y = 0;
        }
        boolean scroll = dx > 4 || dy > 4;
        //Log.i("tag", String.format("onScroll: %s %s %s", scroll, dx, dy));
        return b && isNeedSwipe && scroll;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        viewDragHelper.processTouchEvent(event);
        return isNeedSwipe;
    }

    @Override
    public void requestLayout() {
        if (!mInLayout) {
            super.requestLayout();
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        // if we don't have an adapter, we don't need to do anything
        if (mAdapter == null) {
            return;
        }

        mInLayout = true;
        final int adapterCount = mAdapter.getCount();
        if (adapterCount == 0) {
//            removeAllViewsInLayout();
            removeViewToCache(0);
        } else {
            View topCard = getChildAt(LAST_VIEW_IN_STACK);
            if(mActiveCard != null && topCard != null && topCard == mActiveCard) {
//                removeViewsInLayout(0, LAST_VIEW_IN_STACK);
                removeViewToCache(1);
                layoutChildren(1, adapterCount);
            }else{
//                Reset the UI and set top view listener
//                removeAllViewsInLayout();
                removeViewToCache(0);
                layoutChildren(0, adapterCount);
                setTopView();
            }
        }
        mInLayout = false;

        if (initTop == 0 && initLeft == 0 && mActiveCard != null) {
            initTop = mActiveCard.getTop();
            initLeft = mActiveCard.getLeft();
        }

        if(adapterCount < MIN_ADAPTER_STACK) {
        	if(mFlingListener != null){
        		mFlingListener.onAdapterAboutToEmpty(adapterCount);
        	}
        }
    }

    private void removeViewToCache(int saveCount) {
        View child;
        for (int i = 0; i < getChildCount() - saveCount; ) {
            child = getChildAt(i);
            removeViewInLayout(child);
            cache.add(child);
        }
    }

    private void layoutChildren(int startingIndex, int adapterCount){
        while (startingIndex < Math.min(adapterCount, MAX_VISIBLE) ) {
            View cacheView = null;
            if (cache.size() > 0) {
                cacheView = cache.get(0);
                cache.remove(cacheView);
            }
            View newUnderChild = mAdapter.getView(startingIndex, cacheView, this);
            if (newUnderChild.getVisibility() != GONE) {
                makeAndAddView(newUnderChild, startingIndex);
                LAST_VIEW_IN_STACK = startingIndex;
            }
            startingIndex++;
        }
    }

    private void makeAndAddView(View child, int index) {
        child.setAlpha(1F);
        child.setTranslationX(0);
        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams();
        addViewInLayout(child, 0, lp, true);

        final boolean needToMeasure = child.isLayoutRequested();
        if (needToMeasure) {
            int childWidthSpec = getChildMeasureSpec(widthMeasureSpec,
                    getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin,
                    lp.width);
            int childHeightSpec = getChildMeasureSpec(heightMeasureSpec,
                    getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin,
                    lp.height);
            child.measure(childWidthSpec, childHeightSpec);
        } else {
            cleanupLayoutState(child);
        }

        int w = child.getMeasuredWidth();
        int h = child.getMeasuredHeight();

        int gravity = lp.gravity;
        if (gravity == -1) {
            gravity = Gravity.TOP | Gravity.START;
        }

        int layoutDirection = 0;
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN)
            layoutDirection = getLayoutDirection();
        final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
        final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

        int childLeft;
        int childTop;
        switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
            case Gravity.CENTER_HORIZONTAL:
                childLeft = (getWidth() + getPaddingLeft() - getPaddingRight()  - w) / 2 +
                        lp.leftMargin - lp.rightMargin;
                break;
            case Gravity.END:
                childLeft = getWidth() + getPaddingRight() - w - lp.rightMargin;
                break;
            case Gravity.START:
            default:
                childLeft = getPaddingLeft() + lp.leftMargin;
                break;
        }
        switch (verticalGravity) {
            case Gravity.CENTER_VERTICAL:
                childTop = (getHeight() + getPaddingTop() - getPaddingBottom()  - h) / 2 +
                        lp.topMargin - lp.bottomMargin;
                break;
            case Gravity.BOTTOM:
                childTop = getHeight() - getPaddingBottom() - h - lp.bottomMargin;
                break;
            case Gravity.TOP:
            default:
                childTop = getPaddingTop() + lp.topMargin;
                break;
        }
        child.layout(childLeft, childTop, childLeft + w, childTop + h);
        // 缩放层叠效果
        adjustChildView(child, index);
    }

    private void adjustChildView(View child, int index) {
        if (index > -1 && index < MAX_VISIBLE) {
            int multiple;
            if (index > 2) multiple = 2;
            else multiple = index;
            child.offsetTopAndBottom(yOffsetStep * multiple);
            child.setScaleX(1 - SCALE_STEP * multiple);
            child.setScaleY(1 - SCALE_STEP * multiple);
        }
    }

    private void adjustChildrenOfUnderTopView(float scrollRate) {
        int count = getChildCount();
        if (count > 1) {
            int i;
            int multiple;
            if (count == 2) {
                i = LAST_VIEW_IN_STACK - 1;
                multiple = 1;
            } else {
                i = LAST_VIEW_IN_STACK - 2;
                multiple = 2;
            }
            float rate = Math.abs(scrollRate);
            for (; i < LAST_VIEW_IN_STACK; i++, multiple--) {
                View underTopView = getChildAt(i);
                int offset = (int) (yOffsetStep * (multiple - rate));
                underTopView.offsetTopAndBottom(offset - underTopView.getTop() + initTop);
                underTopView.setScaleX(1 - SCALE_STEP * multiple + SCALE_STEP * rate);
                underTopView.setScaleY(1 - SCALE_STEP * multiple + SCALE_STEP * rate);
            }
        }
    }

    /**
    *  Set the top view and add the fling listener
    */
    private void setTopView() {
        if(getChildCount() > 0){
            mActiveCard = getChildAt(LAST_VIEW_IN_STACK);
            if(mActiveCard != null) {
                mActiveCard.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //Log.i("tag", "onClick: top view");
                        if (mOnItemClickListener != null)
                            mOnItemClickListener.onItemClicked(v, mAdapter.getItem(0));
                    }
                });
            }
        }
    }

    public void setMaxVisible(int MAX_VISIBLE){
        this.MAX_VISIBLE = MAX_VISIBLE;
    }

    public void setMinStackInAdapter(int MIN_ADAPTER_STACK){
        this.MIN_ADAPTER_STACK = MIN_ADAPTER_STACK;
    }

    @Override
    public Adapter getAdapter() {
        return mAdapter;
    }


    @Override
    public void setAdapter(Adapter adapter) {
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
            mDataSetObserver = null;
        }

        mAdapter = adapter;

        if (mAdapter != null  && mDataSetObserver == null) {
            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);
        }
    }

    public void setFlingListener(onFlingListener onFlingListener) {
        this.mFlingListener = onFlingListener;
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener){
        this.mOnItemClickListener = onItemClickListener;
    }


    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new FrameLayout.LayoutParams(getContext(), attrs);
    }


    private class AdapterDataSetObserver extends DataSetObserver {
        @Override
        public void onChanged() {
            requestLayout();
        }

        @Override
        public void onInvalidated() {
            requestLayout();
        }

    }


    public interface OnItemClickListener {
        void onItemClicked(View v, Object dataObject);
    }

    public interface onFlingListener {
        void removeFirstObjectInAdapter(View topView);
        void onLeftCardExit(Object dataObject);
        void onRightCardExit(Object dataObject);
        void onAdapterAboutToEmpty(int itemsInAdapter);
        void onScroll(float progress, float scrollXProgress);
    }

    public static class FlingListenerAdapter implements onFlingListener {

        @Override
        public void removeFirstObjectInAdapter(View topView) {
        }

        @Override
        public void onLeftCardExit(Object dataObject) {
        }

        @Override
        public void onRightCardExit(Object dataObject) {
        }

        @Override
        public void onAdapterAboutToEmpty(int itemsInAdapter) {
        }

        @Override
        public void onScroll(float progress, float scrollXProgress) {
        }
    }

    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {

//        int disX, disY;

        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            cLeft = child.getLeft();
            cTop = child.getTop();
            return child == mActiveCard;
        }

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return left;
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            return top;
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            int offsetX = left - cLeft;
            int offsetY = top - cTop;
            float progress = 1f * (Math.abs(offsetX) + Math.abs(offsetY)) / 400;
            float scrollX = 1f * Math.abs(offsetX) / 400;
            progress = Math.min(progress, 1f);
            scrollX = Math.min(scrollX, 1f);
            //Log.i("tag", "onViewPositionChanged: " + progress);
            adjustChildrenOfUnderTopView(progress);
            if (mFlingListener != null)
                mFlingListener.onScroll(progress, scrollX);
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            int disX = releasedChild.getLeft() - cLeft;
            int disY = releasedChild.getTop() - cTop;
            int endTop = releasedChild.getTop();
            int offsetTop = getHeight() / 6;
            if (disY > offsetTop) {
                endTop += 2 * offsetTop;
            } else if (disY < -offsetTop){
                endTop -= 2 * offsetTop;
            }
            if (isNeedSwipeAnim) {
                if (disX > getWidth() / 4) {
                    isSwipeAnimRunning = true;
                    viewDragHelper.smoothSlideViewTo(releasedChild, getWidth() + 200, endTop);
                    invalidate();
                } else if (disX < -(getWidth() / 4)) {
                    isSwipeAnimRunning = true;
                    viewDragHelper.smoothSlideViewTo(releasedChild, -getWidth(), endTop);
                    invalidate();
                } else {
                    viewDragHelper.smoothSlideViewTo(releasedChild, initLeft, initTop);
                    invalidate();
                }
            } else {
                viewDragHelper.smoothSlideViewTo(releasedChild, initLeft, initTop);
                invalidate();
            }
        }
    };

    public void swipeLeft() {
        swipeTopView(1, 280);
    }

    public void swipeRight() {
        swipeTopView(0, 280);
    }

    private void swipeTopView(int direction, int duration) {
        final View activeView = mActiveCard;
        if (activeView == null) return;
        AnimatorListenerAdapter listenerAdapter = new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                if (mFlingListener != null) {
                    mFlingListener.removeFirstObjectInAdapter(activeView);
                }
                mActiveCard = null;
            }
        };
        final int maxTranslationX = (int) Math.min(activeView.getWidth() / 4, 200 * getResources().getDisplayMetrics().density);
        ValueAnimator.AnimatorUpdateListener updateListener = new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float offsetX = Math.abs(activeView.getTranslationX());
                //Log.i("tag", "onAnimationUpdate: " + offsetX);
                float progress = offsetX / maxTranslationX;
                adjustChildrenOfUnderTopView(progress);
            }
        };
        ViewPropertyAnimator animator = activeView.animate();
        //right
        if (direction == 0) {
            animator.alpha(0)
                    .translationX(maxTranslationX)
                    .setDuration(duration)
                    .setListener(listenerAdapter);

        //left
        } else if (direction == 1) {
            animator.alpha(0)
                    .translationX(-maxTranslationX)
                    .setDuration(duration)
                    .setListener(listenerAdapter);
        }
        if (Build.VERSION.SDK_INT >= 19) {
            animator.setUpdateListener(updateListener);
        }
        animator.start();
    }

}