package com.jiang.android.lib.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.RelativeLayout;

import com.jiang.android.lib.R;

import java.lang.reflect.Method;


public class SwipeItemLayout extends RelativeLayout {

    private static final String TAG = SwipeItemLayout.class.getSimpleName();
    private static final String INSTANCE_STATUS = "instance_status";
    private static final String STATUS_OPEN_CLOSE = "status_open_close";
    private static final int VEL_THRESHOLD = 400;
    private ViewDragHelper mDragHelper;
    // 顶部视图
    private View mTopView;
    // 底部视图
    private View mBottomView;
    // 拖动的弹簧距离
    private int mSpringDistance = 0;
    // 允许拖动的距离【注意:最终允许拖动的距离是 (mDragRange + mSpringDistance)】
    private int mDragRange;
    // 控件滑动方向(向左,向右),默认向左滑动
    private SwipeDirection mSwipeDirection = SwipeDirection.Left;
    // 移动过程中,底部视图的移动方式(拉出,被顶部视图遮住),默认是被顶部视图遮住
    private BottomModel mBottomModel = BottomModel.PullOut;
    // 滑动控件当前的状态(打开,关闭,正在移动),默认是关闭状态
    private Status mCurrentStatus = Status.Closed;
    // 滑动控件滑动前的状态
    private Status mPreStatus = mCurrentStatus;
    // 顶部视图下一次layout时的left
    private int mTopLeft;
    // 顶部视图外边距
    private MarginLayoutParams mTopLp;
    // 底部视图外边距
    private MarginLayoutParams mBottomLp;
    // 滑动比例,【关闭->展开  =>  0->1】
    private float mDragRatio;
    // 手动拖动打开和关闭代理
    private SwipeItemLayoutDelegate mDelegate;

    private GestureDetectorCompat mGestureDetectorCompat;
    private OnLongClickListener mOnLongClickListener;
    private OnClickListener mOnClickListener;
    /**
     * 是否可滑动
     */
    private boolean mSwipeable = true;

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

    public SwipeItemLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs);
        initProperty();
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BGASwipeItemLayout);
        final int N = typedArray.getIndexCount();
        for (int i = 0; i < N; i++) {
            initAttr(typedArray.getIndex(i), typedArray);
        }
        typedArray.recycle();
    }

    private void initAttr(int attr, TypedArray typedArray) {
        if (attr == R.styleable.BGASwipeItemLayout_bga_sil_swipeDirection) {
            // 默认向左滑动
            int leftSwipeDirection = typedArray.getInt(attr, mSwipeDirection.ordinal());

            if (leftSwipeDirection == SwipeDirection.Right.ordinal()) {
                mSwipeDirection = SwipeDirection.Right;
            }
        } else if (attr == R.styleable.BGASwipeItemLayout_bga_sil_bottomMode) {
            // 默认是拉出
            int pullOutBottomMode = typedArray.getInt(attr, mBottomModel.ordinal());

            if (pullOutBottomMode == BottomModel.LayDown.ordinal()) {
                mBottomModel = BottomModel.LayDown;
            }
        } else if (attr == R.styleable.BGASwipeItemLayout_bga_sil_springDistance) {
            // 弹簧距离,不能小于0,默认值为0
            mSpringDistance = typedArray.getDimensionPixelSize(attr, mSpringDistance);
            if (mSpringDistance < 0) {
                throw new IllegalStateException("bga_sil_springDistance不能小于0");
            }
        } else if (attr == R.styleable.BGASwipeItemLayout_bga_sil_swipeAble) {
            mSwipeable = typedArray.getBoolean(attr, mSwipeable);
        }
    }

    private void initProperty() {
        mDragHelper = ViewDragHelper.create(this, mDragHelperCallback);
        mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
        mGestureDetectorCompat = new GestureDetectorCompat(getContext(), mSimpleOnGestureListener);
    }

    public void setDelegate(SwipeItemLayoutDelegate delegate) {
        mDelegate = delegate;
    }

    public void setSwipeAble(boolean swipeAble) {
        mSwipeable = swipeAble;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() != 2) {
            throw new IllegalStateException(SwipeItemLayout.class.getSimpleName() + "必须有且只有两个子控件");
        }
        mTopView = getChildAt(1);
        mBottomView = getChildAt(0);
        // 避免底部视图被隐藏时还能获取焦点被点击
        mBottomView.setVisibility(INVISIBLE);

        mTopLp = (MarginLayoutParams) mTopView.getLayoutParams();
        mBottomLp = (MarginLayoutParams) mBottomView.getLayoutParams();
        mTopLeft = getPaddingLeft() + mTopLp.leftMargin;
    }

    @Override
    public void computeScroll() {
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_CANCEL || ev.getAction() == MotionEvent.ACTION_UP) {
            mDragHelper.cancel();
        }
        return mDragHelper.shouldInterceptTouchEvent(ev) && mGestureDetectorCompat.onTouchEvent(ev);
    }

    @Override
    public void setOnClickListener(OnClickListener l) {
        super.setOnClickListener(l);
        mOnClickListener = l;
    }

    @Override
    public void setOnLongClickListener(OnLongClickListener l) {
        super.setOnLongClickListener(l);
        mOnLongClickListener = l;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        if (insideAdapterView()) {
            if (mOnClickListener == null) {
                setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        performAdapterViewItemClick();
                    }
                });
            }
            if (mOnLongClickListener == null) {
                setOnLongClickListener(new OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View v) {
                        performAdapterViewItemLongClick();
                        return true;
                    }
                });
            }
        }
    }

    private void performAdapterViewItemClick() {
        ViewParent t = getParent();
        if (t instanceof AdapterView) {
            AdapterView view = (AdapterView) t;
            int p = view.getPositionForView(SwipeItemLayout.this);
            if (p != AdapterView.INVALID_POSITION) {
                view.performItemClick(view.getChildAt(p - view.getFirstVisiblePosition()), p, view.getAdapter().getItemId(p));
            }
        }
    }

    private boolean performAdapterViewItemLongClick() {
        ViewParent t = getParent();
        if (t instanceof AdapterView) {
            AdapterView view = (AdapterView) t;
            int p = view.getPositionForView(SwipeItemLayout.this);
            if (p == AdapterView.INVALID_POSITION) return false;
            long vId = view.getItemIdAtPosition(p);
            boolean handled = false;
            try {
                Method m = AbsListView.class.getDeclaredMethod("performLongPress", View.class, int.class, long.class);
                m.setAccessible(true);
                handled = (boolean) m.invoke(view, SwipeItemLayout.this, p, vId);

            } catch (Exception e) {
                e.printStackTrace();

                if (view.getOnItemLongClickListener() != null) {
                    handled = view.getOnItemLongClickListener().onItemLongClick(view, SwipeItemLayout.this, p, vId);
                }
                if (handled) {
                    view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
                }
            }
            return handled;
        }
        return false;
    }

    private boolean insideAdapterView() {
        return getAdapterView() != null;
    }

    private AdapterView getAdapterView() {
        ViewParent t = getParent();
        if (t instanceof AdapterView) {
            return (AdapterView) t;
        }
        return null;
    }

    private void requestParentDisallowInterceptTouchEvent() {
        ViewParent parent = getParent();
        if (parent != null) {
            parent.requestDisallowInterceptTouchEvent(true);
        }
    }

    private GestureDetector.SimpleOnGestureListener mSimpleOnGestureListener = new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (Math.abs(distanceX) > Math.abs(distanceY)) {
                requestParentDisallowInterceptTouchEvent();
                return true;
            }
            return false;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            if (Math.abs(velocityX) > Math.abs(velocityY)) {
                requestParentDisallowInterceptTouchEvent();
                return true;
            }
            return false;
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            // 2
            setPressed(false);
            if (isClosed()) {
                return performClick();
            }
            return false;
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            // 1
            if (isClosed()) {
                setPressed(true);
                return true;
            }
            return false;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            if (isClosed()) {
                setPressed(true);
                postDelayed(mCancelPressedTask, 300);
                performLongClick();
            }
        }

        // 作为ListView或者RecyclerView的item,双击事件很少,这里就不处理双击事件了╮(╯_╰)╭
        public boolean onDoubleTap(MotionEvent e) {
            if (isClosed()) {
                setPressed(true);
                return true;
            }
            return false;
        }

        public boolean onDoubleTapEvent(MotionEvent e) {
            if (isClosed()) {
                setPressed(false);
                return true;
            }
            return false;
        }
    };

    private Runnable mCancelPressedTask = new Runnable() {
        @Override
        public void run() {
            setPressed(false);
        }
    };

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        mGestureDetectorCompat.onTouchEvent(event);
        return true;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mDragRange = mBottomView.getMeasuredWidth() + mBottomLp.leftMargin + mBottomLp.rightMargin;

        int topTop = getPaddingTop() + mTopLp.topMargin;
        int topBottom = topTop + mTopView.getMeasuredHeight();
        int topRight = mTopLeft + mTopView.getMeasuredWidth();

        int bottomTop = getPaddingTop() + mBottomLp.topMargin;
        int bottomBottom = bottomTop + mBottomView.getMeasuredHeight();
        int bottomLeft;
        int bottomRight;

        if (mSwipeDirection == SwipeDirection.Left) {
            // 向左滑动

            if (mBottomModel == BottomModel.LayDown) {
                // 遮罩,位置固定不变(先计算right,然后根据right计算left)

                bottomRight = r - getPaddingRight() - mBottomLp.rightMargin;
                bottomLeft = bottomRight - mBottomView.getMeasuredWidth();
            } else {
                // 拉出,位置随顶部视图的位置改变

                // 根据顶部视图的left计算底部视图的left
                bottomLeft = mTopLeft + mTopView.getMeasuredWidth() + mTopLp.rightMargin + mBottomLp.leftMargin;

                // 底部视图的left被允许的最小值
                int minBottomLeft = r - getPaddingRight() - mBottomView.getMeasuredWidth() - mBottomLp.rightMargin;
                // 获取最终的left
                bottomLeft = Math.max(bottomLeft, minBottomLeft);
                // 根据left计算right
                bottomRight = bottomLeft + mBottomView.getMeasuredWidth();
            }
        } else {
            // 向右滑动

            if (mBottomModel == BottomModel.LayDown) {
                // 遮罩,位置固定不变(先计算left,然后根据left计算right)

                bottomLeft = getPaddingLeft() + mBottomLp.leftMargin;
                bottomRight = bottomLeft + mBottomView.getMeasuredWidth();
            } else {
                // 拉出,位置随顶部视图的位置改变

                // 根据顶部视图的left计算底部视图的left
                bottomLeft = mTopLeft - mDragRange;
                // 底部视图的left被允许的最大值
                int maxBottomLeft = getPaddingLeft() + mBottomLp.leftMargin;
                // 获取最终的left
                bottomLeft = Math.min(maxBottomLeft, bottomLeft);
                // 根据left计算right
                bottomRight = bottomLeft + mBottomView.getMeasuredWidth();
            }
        }

        mBottomView.layout(bottomLeft, bottomTop, bottomRight, bottomBottom);
        mTopView.layout(mTopLeft, topTop, topRight, topBottom);
    }

    /**
     * 以动画方式打开
     */
    public void openWithAnim() {
        mPreStatus = Status.Moving;
        smoothSlideTo(1);
    }

    /**
     * 以动画方式关闭
     */
    public void closeWithAnim() {
        mPreStatus = Status.Moving;
        smoothSlideTo(0);
    }

    /**
     * 直接打开
     */
    public void open() {
        mPreStatus = Status.Moving;
        slideTo(1);
    }

    /**
     * 直接关闭。如果在AbsListView中删除已经打开的item时,请用该方法关闭item,否则重用item时有问题。RecyclerView中可以用该方法,也可以用closeWithAnim
     */
    public void close() {
        mPreStatus = Status.Moving;
        slideTo(0);
    }

    /**
     * 当前是否为打开状态
     *
     * @return
     */
    public boolean isOpened() {
        return (mCurrentStatus == Status.Opened) || (mCurrentStatus == Status.Moving && mPreStatus == Status.Opened);
    }

    /**
     * 当前是否为关闭状态
     *
     * @return
     */
    public boolean isClosed() {
        return mCurrentStatus == Status.Closed || (mCurrentStatus == Status.Moving && mPreStatus == Status.Closed);
    }

    /**
     * 获取顶部视图
     *
     * @return
     */
    public View getTopView() {
        return mTopView;
    }

    /**
     * 获取底部视图
     *
     * @return
     */
    public View getBottomView() {
        return mBottomView;
    }

    /**
     * 打开或关闭滑动控件
     *
     * @param isOpen 1表示打开,0表示关闭
     */
    private void smoothSlideTo(int isOpen) {
        if (mDragHelper.smoothSlideViewTo(mTopView, getCloseOrOpenTopViewFinalLeft(isOpen), getPaddingTop() + mTopLp.topMargin)) {
            if(isOpen == 1){
                mBottomView.setVisibility(VISIBLE);
            }

            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    /**
     * 打开或关闭滑动控件
     *
     * @param isOpen 1表示打开,0表示关闭
     */
    private void slideTo(int isOpen) {
        if (isOpen == 1) {
            mBottomView.setVisibility(VISIBLE);
            ViewCompat.setAlpha(mBottomView, 1.0f);
            mCurrentStatus = Status.Opened;
            if (mDelegate != null) {
                mDelegate.onSwipeItemLayoutOpened(this);
            }
        } else {
            mBottomView.setVisibility(INVISIBLE);
            mCurrentStatus = Status.Closed;
            if (mDelegate != null) {
                mDelegate.onSwipeItemLayoutClosed(this);
            }
        }
        mPreStatus = mCurrentStatus;
        mTopLeft = getCloseOrOpenTopViewFinalLeft(isOpen);
        requestLayout();
    }

    private int getCloseOrOpenTopViewFinalLeft(int isOpen) {
        int left = getPaddingLeft() + mTopLp.leftMargin;
        if (mSwipeDirection == SwipeDirection.Left) {
            left = left - isOpen * mDragRange;
        } else {
            left = left + isOpen * mDragRange;
        }
        return left;
    }

    private int getCloseOrOpenBottomViewFinalLeft(int isOpen) {
        int left = getWidth() - mBottomView.getWidth();
        if (mSwipeDirection == SwipeDirection.Left) {
            left = left - isOpen * mDragRange;
        } else {
            left = left + isOpen * mDragRange;
        }
        return left;
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable(INSTANCE_STATUS, super.onSaveInstanceState());
        bundle.putInt(STATUS_OPEN_CLOSE, mCurrentStatus.ordinal());
        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            if (bundle.getInt(STATUS_OPEN_CLOSE) == Status.Opened.ordinal()) {
                open();
            } else {
                close();
            }
            super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATUS));
        } else {
            super.onRestoreInstanceState(state);
        }
    }

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

        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return mSwipeable && child == mTopView;
        }

        @Override
        public int getViewVerticalDragRange(View child) {
            return 0;
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            // 这里要返回控件的getPaddingTop() + mTopLp.topMargin,否则有margin和padding快速滑动松手时会上下跳动
            return getPaddingTop() + mTopLp.topMargin;
        }

        @Override
        public int getViewHorizontalDragRange(View child) {
            return mDragRange + mSpringDistance;
        }

        /**
         *
         * @param child
         * @param left ViewDragHelper帮我们计算的当前所捕获的控件的left
         * @param dx
         * @return
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            int minTopLeft;
            int maxTopLeft;

            if (mSwipeDirection == SwipeDirection.Left) {
                // 向左滑动

                // 顶部视图的left被允许的最小值
                minTopLeft = getPaddingLeft() + mTopLp.leftMargin - (mDragRange + mSpringDistance);
                // 顶部视图的left被允许的最大值
                maxTopLeft = getPaddingLeft() + mTopLp.leftMargin;
            } else {
                // 向右滑动

                // 顶部视图的left被允许的最小值
                minTopLeft = getPaddingLeft() + mTopLp.leftMargin;
                // 顶部视图的left被允许的最大值
                maxTopLeft = getPaddingLeft() + mTopLp.leftMargin + (mDragRange + mSpringDistance);
            }

            return Math.min(Math.max(minTopLeft, left), maxTopLeft);
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            mTopLeft = left;

            // 此时顶部视图水平方向偏移量的绝对值
            int topViewHorizontalOffset = Math.abs(mTopLeft - (getPaddingLeft() + mTopLp.leftMargin));
            if (topViewHorizontalOffset > mDragRange) {
                mDragRatio = 1.0f;
            } else {
                mDragRatio = 1.0f * topViewHorizontalOffset / mDragRange;
            }

            // 处理底部视图的透明度
            float alpha = 0.1f + 0.9f * mDragRatio;
            ViewCompat.setAlpha(mBottomView, alpha);

            dispatchSwipeEvent();

            // 兼容低版本
            invalidate();

            requestLayout();
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            // 默认关闭,接下来再判断为打开时的条件
            int finalLeft = getPaddingLeft() + mTopLp.leftMargin;
            if (mSwipeDirection == SwipeDirection.Left) {
                // 向左滑动为打开,向右滑动为关闭

                if (xvel < -VEL_THRESHOLD || (mPreStatus == Status.Closed && xvel < VEL_THRESHOLD && mDragRatio >= 0.3f) || (mPreStatus == Status.Opened && xvel < VEL_THRESHOLD && mDragRatio >= 0.7f)) {
                    // 向左的速度达到条件
                    finalLeft -= mDragRange;
                }

            } else {
                // 向左滑动为关闭,向右滑动为打开

                if (xvel > VEL_THRESHOLD || (mPreStatus == Status.Closed && xvel > -VEL_THRESHOLD && mDragRatio >= 0.3f) || (mPreStatus == Status.Opened && xvel > -VEL_THRESHOLD && mDragRatio >= 0.7f)) {
                    finalLeft += mDragRange;
                }
            }
            mDragHelper.settleCapturedViewAt(finalLeft, getPaddingTop() + mTopLp.topMargin);

            // 要执行下面的代码,不然不会自动收缩完毕或展开完毕
            ViewCompat.postInvalidateOnAnimation(SwipeItemLayout.this);
        }

    };

    private void dispatchSwipeEvent() {
        Status preStatus = mCurrentStatus;
        updateCurrentStatus();
        if (mCurrentStatus != preStatus) {
            if (mCurrentStatus == Status.Closed) {
                mBottomView.setVisibility(INVISIBLE);
                if (mDelegate != null && mPreStatus != mCurrentStatus) {
                    mDelegate.onSwipeItemLayoutClosed(this);
                }
                mPreStatus = Status.Closed;
            } else if (mCurrentStatus == Status.Opened) {
                if (mDelegate != null && mPreStatus != mCurrentStatus) {
                    mDelegate.onSwipeItemLayoutOpened(this);
                }
                mPreStatus = Status.Opened;
            } else if (mPreStatus == Status.Closed) {
                mBottomView.setVisibility(VISIBLE);
                if (mDelegate != null) {
                    mDelegate.onSwipeItemLayoutStartOpen(this);
                }
            }
        }
    }

    private void updateCurrentStatus() {
        if (mSwipeDirection == SwipeDirection.Left) {
            // 向左滑动

            if (mTopLeft == getPaddingLeft() + mTopLp.leftMargin - mDragRange) {
                mCurrentStatus = Status.Opened;
            } else if (mTopLeft == getPaddingLeft() + mTopLp.leftMargin) {
                mCurrentStatus = Status.Closed;
            } else {
                mCurrentStatus = Status.Moving;
            }
        } else {
            // 向右滑动

            if (mTopLeft == getPaddingLeft() + mTopLp.leftMargin + mDragRange) {
                mCurrentStatus = Status.Opened;
            } else if (mTopLeft == getPaddingLeft() + mTopLp.leftMargin) {
                mCurrentStatus = Status.Closed;
            } else {
                mCurrentStatus = Status.Moving;
            }
        }
    }


    public enum SwipeDirection {
        Left, Right
    }

    public enum BottomModel {
        PullOut, LayDown
    }

    public enum Status {
        Opened, Closed, Moving
    }

    public interface SwipeItemLayoutDelegate {
        /**
         * 变为打开状态
         *
         * @param swipeItemLayout
         */
        void onSwipeItemLayoutOpened(SwipeItemLayout swipeItemLayout);

        /**
         * 变为关闭状态
         *
         * @param swipeItemLayout
         */
        void onSwipeItemLayoutClosed(SwipeItemLayout swipeItemLayout);

        /**
         * 从关闭状态切换到正在打开状态
         *
         * @param swipeItemLayout
         */
        void onSwipeItemLayoutStartOpen(SwipeItemLayout swipeItemLayout);
    }

}