package com.navigation.androidx;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.customview.widget.ViewDragHelper;

/**
 * Created by Listen on 2018/7/1.
 * <p>
 * modified from https://github.com/ikew0ng/SwipeBackLayout
 */
public class SwipeBackLayout extends FrameLayout {

    private static String TAG = "SwipeBackLayout";

    /**
     * Minimum velocity that will be detected as a fling
     */
    private static final int MIN_FLING_VELOCITY = 400; // dips per second

    private static final int DEFAULT_SCRIM_COLOR = 0x10000000;

    private static final int FULL_ALPHA = 255;

    /**
     * A view is not currently being dragged or animating as a result of a
     * fling/snap.
     */
    public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE;

    /**
     * A view is currently being dragged. The position is currently changing as
     * a result of user input or simulated user input.
     */
    public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING;

    /**
     * A view is currently settling into place as a result of a fling or
     * predefined non-interactive motion.
     */
    public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING;

    /**
     * Default threshold of scroll
     */
    private static final float DEFAULT_SCROLL_THRESHOLD = 0.5f;

    private static final float DEFAULT_PARALLAX = 0.25f;

    /**
     * Threshold of scroll, we will close the activity, when scrollPercent over
     * this value;
     */
    private float mScrollThreshold = DEFAULT_SCROLL_THRESHOLD;

    private float mParallaxOffset = DEFAULT_PARALLAX;

    private View mCapturedView;

    private ViewDragHelper mDragHelper;

    private float mScrollPercent;

    private int mLeft;

    /**
     * The set of listeners to be sent events through.
     */
    private SwipeListener mListener;

    private Drawable mTabBar;

    private Rect mTabBarOriginBounds;

    private float mScrimOpacity;

    private int mScrimColor = DEFAULT_SCRIM_COLOR;

    private boolean mInLayout;

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

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

    public SwipeBackLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs);
        mDragHelper = ViewDragHelper.create(this, new ViewDragCallback());
        final float density = getResources().getDisplayMetrics().density;
        final float minVelocity = MIN_FLING_VELOCITY * density;
        mDragHelper.setMinVelocity(minVelocity);
        mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
    }

    public void setTabBar(Drawable drawable) {
        this.mTabBar = drawable;
        if (drawable != null) {
            mTabBarOriginBounds = drawable.copyBounds();
        }
    }

    public void setParallaxOffset(float offset) {
        mParallaxOffset = offset;
    }

    /**
     * Add a callback to be invoked when a swipe event is sent to this view.
     *
     * @param listener the swipe listener to attach to this view
     */
    public void setSwipeListener(SwipeListener listener) {
        mListener = listener;
    }

    public interface SwipeListener {

        void onViewDragStateChanged(int state, float scrollPercent);

        boolean shouldSwipeBack();
    }

    public void setScrollThresHold(float threshold) {
        if (threshold >= 1.0f || threshold <= 0) {
            throw new IllegalArgumentException("Threshold value should be between 0 and 1.0");
        }
        mScrollThreshold = threshold;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (!mListener.shouldSwipeBack()) {
            return super.onInterceptTouchEvent(event);
        }

        try {
            return mDragHelper.shouldInterceptTouchEvent(event);
        } catch (ArrayIndexOutOfBoundsException e) {
            return false;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!mListener.shouldSwipeBack()) {
            return super.onTouchEvent(event);
        }
        mDragHelper.processTouchEvent(event);
        return mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mInLayout = true;
        if (mCapturedView != null)
            mCapturedView.layout(mLeft, 0,
                    mLeft + mCapturedView.getMeasuredWidth(),
                    mCapturedView.getMeasuredHeight());
        mInLayout = false;
    }

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

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        final boolean drawContent = child == mCapturedView;
        int index = indexOfChild(child);
        if (mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE && index == getChildCount() - 2) {
            View lastChild = getChildAt(getChildCount() - 1);
            canvas.save();
            canvas.clipRect(0, 0, lastChild.getLeft(), getHeight());
        }

        boolean ret = super.drawChild(canvas, child, drawingTime);

        if (mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE && index == getChildCount() - 2) {
            canvas.restore();
        }

        if (mTabBar != null && drawContent) {
            drawTabBar(canvas, child);
        }

        if (mScrimOpacity > 0 && drawContent
                && mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) {
            drawScrim(canvas, child);
        }
        return ret;
    }

    private void drawScrim(Canvas canvas, View child) {
        final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
        final int alpha = (int) (baseAlpha * mScrimOpacity);
        final int color = alpha << 24 | (mScrimColor & 0xffffff);
        canvas.clipRect(0, 0, child.getLeft(), getHeight());
        canvas.drawColor(color);
    }

    private void drawTabBar(Canvas canvas, View child) {
        canvas.save();
        canvas.clipRect(0, 0, child.getLeft(), getHeight());
        int leftOffset = (int) ((mCapturedView.getLeft() - getWidth()) * mParallaxOffset * mScrimOpacity);
        mTabBar.setBounds(leftOffset, mTabBarOriginBounds.top, mTabBarOriginBounds.right + leftOffset, mTabBarOriginBounds.bottom);
        mTabBar.draw(canvas);
        canvas.restore();
    }

    @Override
    public void computeScroll() {
        mScrimOpacity = 1 - mScrollPercent;
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }

        int count = getChildCount();
        if (mScrimOpacity >= 0 && mCapturedView != null && count > 1) {
            int leftOffset = (int) ((mCapturedView.getLeft() - getWidth()) * mParallaxOffset * mScrimOpacity);
            View underlying = getChildAt(count - 2);
            underlying.setX(leftOffset > 0 ? 0 : leftOffset);
        }
    }

    private class ViewDragCallback extends ViewDragHelper.Callback {

        @Override
        public boolean tryCaptureView(@NonNull View view, int pointerId) {
            if (view.getAnimation() != null) {
                return false;
            }

            boolean ret = mDragHelper.isEdgeTouched(ViewDragHelper.EDGE_LEFT, pointerId);
            boolean directionCheck = !mDragHelper.checkTouchSlop(ViewDragHelper.DIRECTION_VERTICAL, pointerId);
            return mDragHelper.getViewDragState() != ViewDragHelper.STATE_SETTLING && (ret & directionCheck);
        }

        @Override
        public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {
            mCapturedView = capturedChild;
        }

        @Override
        public int getViewHorizontalDragRange(@NonNull View child) {
            return child.getWidth();
        }

        @Override
        public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {
            mScrollPercent = Math.abs((float) left / mCapturedView.getWidth());
            mLeft = left;
            invalidate();
        }

        @Override
        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
            final int childWidth = releasedChild.getWidth();
            int left = xvel > 0 || (xvel == 0 && mScrollPercent > mScrollThreshold) ? childWidth : 0;
            mDragHelper.settleCapturedViewAt(left, 0);
            invalidate();
        }

        @Override
        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
            return Math.min(child.getWidth(), Math.max(left, 0));
        }

        @Override
        public void onViewDragStateChanged(int state) {
            if (state == ViewDragHelper.STATE_IDLE) {
                mCapturedView = null;
                mLeft = 0;
                int count = getChildCount();
                if (count > 1) {
                    View underlying = getChildAt(count - 2);
                    underlying.setX(0);
                }
            }
            mListener.onViewDragStateChanged(state, mScrollPercent);
        }
    }
}