package nico.styTool.plus;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.Application;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.AbsListView;
import android.widget.FrameLayout;
import android.widget.ScrollView;

import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.view.ViewHelper;

/**
 * Created by NeXT on 15/8/3.
 */
public class SlideBottomPanel extends FrameLayout {

    private static final int TAG_BACKGROUND = 1;
    private static final int TAG_PANEL = 2;

    private static final int DEFAULT_BACKGROUND_ID = -1;
    private static final int DEFAULT_TITLE_HEIGHT_NO_DISPLAY = 60;
    private static final int DEFAULT_PANEL_HEIGHT = 380;
    private static final int DEFAULT_MOVE_DISTANCE_TO_TRIGGER = 30;
    private static final int DEFAULT_ANIMATION_DURATION = 250;
    private static final int MAX_CLICK_TIME = 300;
    private static final boolean DEFAULT_FADE = true;
    private static final boolean DEFAULT_BOUNDARY = true;
    private static final boolean DEFAULT_HIDE_PANEL_TITLE = false;

    private static float MAX_CLICK_DISTANCE = 5;

    private int mChildCount;
    private float mDensity;
    private boolean isAnimating = false;
    private boolean isPanelShowing = false;

    private float xVelocity;
    private float yVelocity;
    private float mTouchSlop;
    private int mMaxVelocity;
    private int mMinVelocity;
    private VelocityTracker mVelocityTracker;

    private int mMeasureHeight;
    private float firstDownX;
    private float firstDownY;
    private float downY;
    private float deltaY;
    private long mPressStartTime;
    private boolean isDragging = false;

    private int mBackgroundId;
    private float mPanelHeight;
    private float mTitleHeightNoDisplay;
    private float mMoveDistanceToTrigger;
    private int mAnimationDuration;
    private boolean mIsFade = true;
    private boolean mBoundary = true;
    private boolean mHidePanelTitle = false;
    private boolean isPanelOnTouch = false;

    private Interpolator mOpenAnimationInterpolator = new AccelerateInterpolator();
    private Interpolator mCloseAnimationInterpolator = new AccelerateInterpolator();

    private Context mContext;
    private DarkFrameLayout mDarkFrameLayout;

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

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

    public SlideBottomPanel(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mContext = context;
        mDensity = getResources().getDisplayMetrics().density;

        ViewConfiguration vc = ViewConfiguration.get(mContext);
        mMaxVelocity = vc.getScaledMaximumFlingVelocity();
        mMinVelocity = vc.getScaledMinimumFlingVelocity();
        mTouchSlop = vc.getScaledTouchSlop();

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlideBottomPanel, defStyleAttr, 0);

        mBackgroundId = a.getResourceId(R.styleable.SlideBottomPanel_sbp_background_layout, DEFAULT_BACKGROUND_ID);
        mPanelHeight = a.getDimension(R.styleable.SlideBottomPanel_sbp_panel_height, dp2px(DEFAULT_PANEL_HEIGHT));
        mBoundary = a.getBoolean(R.styleable.SlideBottomPanel_sbp_boundary, DEFAULT_BOUNDARY);
        MAX_CLICK_DISTANCE = mTitleHeightNoDisplay = a.getDimension(R.styleable.SlideBottomPanel_sbp_title_height_no_display,dp2px(DEFAULT_TITLE_HEIGHT_NO_DISPLAY));
        mMoveDistanceToTrigger = a.getDimension(R.styleable.SlideBottomPanel_sbp_move_distance_trigger, dp2px(DEFAULT_MOVE_DISTANCE_TO_TRIGGER));
        mAnimationDuration = a.getInt(R.styleable.SlideBottomPanel_sbp_animation_duration, DEFAULT_ANIMATION_DURATION);
        mHidePanelTitle = a.getBoolean(R.styleable.SlideBottomPanel_sbp_hide_panel_title, DEFAULT_HIDE_PANEL_TITLE);
        mIsFade = a.getBoolean(R.styleable.SlideBottomPanel_sbp_fade, DEFAULT_FADE);

        a.recycle();

        initBackgroundView();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mChildCount = getChildCount();
        int t = (int) (mMeasureHeight - mTitleHeightNoDisplay);
        for (int i = 0; i < mChildCount; i++) {
            View childView = getChildAt(i);
            if (childView.getTag() == null || (int) childView.getTag() != TAG_BACKGROUND) {
                childView.layout(0, t, childView.getMeasuredWidth(), childView.getMeasuredHeight() + t);
                childView.setTag(TAG_PANEL);
//                if (childView instanceof ViewGroup) {
//                    ((ViewGroup)childView).setClipChildren(false);
//                }
            } else if (childView.getTag() == null){//空指计
                childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
                childView.setPadding(0, 0, 0, (int)mTitleHeightNoDisplay);
            }
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return isDragging;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return true;
    }

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        initVelocityTracker(ev);
        boolean isConsume = false;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isConsume = handleActionDown(ev);
                break;
            case MotionEvent.ACTION_MOVE:
                handleActionMove(ev);
                break;
            case MotionEvent.ACTION_UP:
                handleActionUp(ev);
                releaseVelocityTracker();
                break;
        }
        return isConsume || super.dispatchTouchEvent(ev);
    }

    private void initBackgroundView() {
        if (mBackgroundId != -1) {
            mDarkFrameLayout = new DarkFrameLayout(mContext);
            mDarkFrameLayout.addView(LayoutInflater.from(mContext).inflate(mBackgroundId, null));
            mDarkFrameLayout.setTag(TAG_BACKGROUND);
            mDarkFrameLayout.setSlideBottomPanel(this);
            addView(mDarkFrameLayout);
        }
    }

    private void handleActionUp(MotionEvent event) {
        if (!isPanelOnTouch) {
            return;
        }
        long pressDuration = System.currentTimeMillis() - mPressStartTime;
        computeVelocity();
        if (!isPanelShowing && ((event.getY() - firstDownY) < 0 && (Math.abs(event.getY() - firstDownY) > mMoveDistanceToTrigger))
                    || (yVelocity < 0 && Math.abs(yVelocity) > Math.abs(xVelocity) && Math.abs(yVelocity) > mMinVelocity)) {
            displayPanel();
        } else if (!isPanelShowing && pressDuration < MAX_CLICK_TIME &&
                distance(firstDownX, firstDownY, event.getX(), event.getY()) < MAX_CLICK_DISTANCE) {
            displayPanel();
        } else if (!isPanelShowing && isDragging && ((event.getY() - firstDownY > 0) ||
                Math.abs(event.getY() - firstDownY) < mMoveDistanceToTrigger)){
            hidePanel();
        }

        if (isPanelShowing) {
            View mPanel = findViewWithTag(TAG_PANEL);
            float currentY = ViewHelper.getY(mPanel);
            if (currentY < (mMeasureHeight - mPanelHeight) ||
                    currentY < (mMeasureHeight - mPanelHeight + mMoveDistanceToTrigger)) {
                ObjectAnimator.ofFloat(mPanel, "y", currentY, mMeasureHeight - mPanelHeight)
                        .setDuration(mAnimationDuration).start();
            } else if (currentY > mMeasureHeight - mPanelHeight + mMoveDistanceToTrigger){
                hidePanel();
            }
        }

        isPanelOnTouch = false;
        isDragging = false;
        deltaY = 0;
    }

    private void handleActionMove(MotionEvent event) {
        if (!isPanelOnTouch) {
            return;
        }
        if (isPanelShowing && supportScrollInView((int) (firstDownY - event.getY()))) {
            return;
        }
        computeVelocity();
        if (Math.abs(xVelocity) > Math.abs(yVelocity)) {
            return;
        }
        if (!isDragging && Math.abs(event.getY() - firstDownY) > mTouchSlop
                && Math.abs(event.getX() - firstDownX) < mTouchSlop) {
            isDragging = true;
            downY = event.getY();
        }
        if (isDragging) {
            deltaY = event.getY() - downY;
            downY = event.getY();

            View touchingView = findViewWithTag(TAG_PANEL);

            if (mHidePanelTitle && isPanelShowing) {
                hidePanelTitle(touchingView);
            }

            if (mDarkFrameLayout != null && mIsFade) {
                float currentY = ViewHelper.getY(touchingView);
                if (currentY > mMeasureHeight - mPanelHeight &&
                        currentY < mMeasureHeight - mTitleHeightNoDisplay) {
                    mDarkFrameLayout.fade(
                            (int) ((1 - currentY / (mMeasureHeight - mTitleHeightNoDisplay)) * DarkFrameLayout.MAX_ALPHA));
                }
            }
            if (!mBoundary) {
                touchingView.offsetTopAndBottom((int) deltaY);
            } else {
                float touchingViewY = ViewHelper.getY(touchingView);
                if (touchingViewY + deltaY <= mMeasureHeight - mPanelHeight) {
                    touchingView.offsetTopAndBottom((int) (mMeasureHeight - mPanelHeight - touchingViewY));
                } else if (touchingViewY + deltaY >= mMeasureHeight - mTitleHeightNoDisplay) {
                    touchingView.offsetTopAndBottom((int) (mMeasureHeight - mTitleHeightNoDisplay - touchingViewY));
                } else {
                    touchingView.offsetTopAndBottom((int) deltaY);
                }
            }
        }
    }

    private boolean handleActionDown(MotionEvent event) {
        boolean isConsume = false;
        mPressStartTime = System.currentTimeMillis();
        firstDownX = event.getX();
        firstDownY = downY = event.getY();
        if (!isPanelShowing && downY > mMeasureHeight - mTitleHeightNoDisplay) {
            isPanelOnTouch = true;
            isConsume = true;
        } else if (!isPanelShowing && downY <= mMeasureHeight - mTitleHeightNoDisplay) {
            isPanelOnTouch = false;
        } else if (isPanelShowing && downY > mMeasureHeight - mPanelHeight) {
            isPanelOnTouch = true;
        } else if (isPanelShowing && downY < mMeasureHeight - mPanelHeight) {
            hidePanel();
            isPanelOnTouch = false;
        }
        return isConsume;
    }

    private void hidePanel() {
        if (isAnimating) {
            return;
        }
        final View mPanel = findViewWithTag(TAG_PANEL);
        final int t = (int)(mMeasureHeight - mTitleHeightNoDisplay);
        ValueAnimator animator = ValueAnimator.ofFloat(
                ViewHelper.getY(mPanel), mMeasureHeight - mTitleHeightNoDisplay);
        animator.setInterpolator(mCloseAnimationInterpolator);
        animator.setTarget(mPanel);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                ViewHelper.setY(mPanel, value);
                if (mDarkFrameLayout != null && mIsFade && value < t) {
                    mDarkFrameLayout.fade((int) ((1 - value / t) * DarkFrameLayout.MAX_ALPHA));
                }
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                isAnimating = true;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                isAnimating = false;
                isPanelShowing = false;
                showPanelTitle(mPanel);
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                isAnimating = false;
                isPanelShowing = false;
                showPanelTitle(mPanel);
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
        animator.start();
    }

    public void displayPanel() {
        if (isPanelShowing || isAnimating) {
            return;
        }
        if (mIsFade || mDarkFrameLayout != null) {
            mDarkFrameLayout.fade(true);
        }
        final View mPanel = findViewWithTag(TAG_PANEL);
        ValueAnimator animator = ValueAnimator.ofFloat(ViewHelper.getY(mPanel), mMeasureHeight - mPanelHeight)
                .setDuration(mAnimationDuration);
        animator.setTarget(mPanel);
        animator.setInterpolator(mOpenAnimationInterpolator);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                ViewHelper.setY(mPanel, value);
                if (mDarkFrameLayout != null && mIsFade
                        && mDarkFrameLayout.getCurrentAlpha() != DarkFrameLayout.MAX_ALPHA) {
                    mDarkFrameLayout.fade(
                            (int) ((1 - value / (mMeasureHeight - mTitleHeightNoDisplay)) * DarkFrameLayout.MAX_ALPHA));
                }
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                isAnimating = true;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                isAnimating = false;
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                isAnimating = false;
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
        animator.start();
        isPanelShowing = true;
        hidePanelTitle(mPanel);
    }

    private void showPanelTitle(View panel) {
        if (panel instanceof ViewGroup && mHidePanelTitle) {
            try {
                View childView = ((ViewGroup) panel).getChildAt(1);
                if (childView.getVisibility() != View.VISIBLE) {
                    childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
                    childView.setVisibility(View.VISIBLE);
                }
            } catch (NullPointerException e) {
                e.printStackTrace();
            }
        }
    }

    private void hidePanelTitle(View panel) {
        if (panel instanceof ViewGroup && mHidePanelTitle) {
            try {
                ((ViewGroup) panel).getChildAt(1).setVisibility(View.INVISIBLE);
            } catch (NullPointerException e) {
                e.printStackTrace();
            }
        }
    }

    public void hide() {
        if(!isPanelShowing) return;
        hidePanel();
    }

    private void computeVelocity() {
        //units是单位表示, 1代表px/毫秒, 1000代表px/秒
        mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
        xVelocity = mVelocityTracker.getXVelocity();
        yVelocity = mVelocityTracker.getYVelocity();
    }

    private boolean supportScrollInView(int direction) {

        View view = findViewWithTag(TAG_PANEL);
        if (view instanceof ViewGroup) {
            View childView = findTopChildUnder((ViewGroup) view, firstDownX, firstDownY);
            if (childView == null) {
                return false;
            }
            if (childView instanceof AbsListView) {
                AbsListView absListView = (AbsListView) childView;
                if (Build.VERSION.SDK_INT >= 19) {
                    return absListView.canScrollList(direction);
                } else {
                    return absListViewCanScrollList(absListView,direction);
                }
            } else if (childView instanceof ScrollView) {
                ScrollView scrollView = (ScrollView) childView;
                if (Build.VERSION.SDK_INT >= 14) {
                    return scrollView.canScrollVertically(direction);
                } else {
                    return scrollViewCanScrollVertically(scrollView, direction);
                }

            } else if (childView instanceof ViewGroup){
                View grandchildView = findTopChildUnder((ViewGroup) childView, firstDownX, firstDownY);
                if (grandchildView == null) {
                    return false;
                }
                if (grandchildView instanceof ViewGroup) {
                    if (grandchildView instanceof AbsListView) {
                        AbsListView absListView = (AbsListView) grandchildView;
                        if (Build.VERSION.SDK_INT >= 19) {
                            return absListView.canScrollList(direction);
                        } else {
                            return absListViewCanScrollList(absListView,direction);
                        }
                    } else if (grandchildView instanceof ScrollView) {
                        ScrollView scrollView = (ScrollView) grandchildView;
                        if (Build.VERSION.SDK_INT >= 14) {
                            return scrollView.canScrollVertically(direction);
                        } else {
                            return scrollViewCanScrollVertically(scrollView, direction);
                        }
                    }
                }
            }


        }
        return false;
    }

    private View findTopChildUnder(ViewGroup parentView, float x, float y) {
        int childCount = parentView.getChildCount();
        for (int i = childCount - 1; i >= 0; i--) {
            final View child = parentView.getChildAt(i);
            if (x >= child.getLeft() && x < child.getRight() &&
                    y >= child.getTop() + mMeasureHeight - mPanelHeight &&
                    y < child.getBottom()  + mMeasureHeight - mPanelHeight) {
                return child;
            }
        }
        return null;
    }

    /**
     *  Copy From ScrollView (API Level >= 14)
     * @param direction Negative to check scrolling up, positive to check
     *                  scrolling down.
     *   @return true if the scrollView can be scrolled in the specified direction,
     *         false otherwise
     */
    private  boolean scrollViewCanScrollVertically(ScrollView scrollView,int direction) {
        final int offset = Math.max(0, scrollView.getScrollY());
        final int range = computeVerticalScrollRange(scrollView) - scrollView.getHeight();
        if (range == 0) return false;
        if (direction < 0) { //scroll up
            return offset > 0;
        } else {//scroll down
            return offset < range - 1;
        }
    }

    /**
     * Copy From ScrollView (API Level >= 14)
     * <p>The scroll range of a scroll view is the overall height of all of its
     * children.</p>
     */
    private int computeVerticalScrollRange(ScrollView scrollView) {
        final int count = scrollView.getChildCount();
        final int contentHeight = scrollView.getHeight() - scrollView.getPaddingBottom() - scrollView.getPaddingTop();
        if (count == 0) {
            return contentHeight;
        }

        int scrollRange = scrollView.getChildAt(0).getBottom();
        final int scrollY = scrollView.getScrollY();
        final int overScrollBottom = Math.max(0, scrollRange - contentHeight);
        if (scrollY < 0) {
            scrollRange -= scrollY;
        } else if (scrollY > overScrollBottom) {
            scrollRange += scrollY - overScrollBottom;
        }

        return scrollRange;
    }

    /**
     * Copy From AbsListView (API Level >= 19)
     * @param absListView AbsListView
     * @param direction Negative to check scrolling up, positive to check
     *                  scrolling down.
     * @return true if the list can be scrolled in the specified direction,
     *         false otherwise
     */
    private boolean absListViewCanScrollList(AbsListView absListView,int direction) {
        final int childCount = absListView.getChildCount();
        if (childCount == 0) {
            return false;
        }
        final int firstPosition = absListView.getFirstVisiblePosition();
        if (direction > 0) {//can scroll down
            final int lastBottom = absListView.getChildAt(childCount - 1).getBottom();
            final int lastPosition = firstPosition + childCount;
            return lastPosition < absListView.getCount() || lastBottom > absListView.getHeight() - absListView.getPaddingTop();
        } else {//can scroll  up
            final int firstTop = absListView.getChildAt(0).getTop();
            return firstPosition > 0 || firstTop < absListView.getPaddingTop();
        }
    }

    private void initVelocityTracker(MotionEvent event) {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
    }

    private void releaseVelocityTracker() {
        if (mVelocityTracker != null) {
            mVelocityTracker.clear();
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    private double distance(float x1, float y1, float x2, float y2) {
        float deltaX = x1 - x2;
        float deltaY = y1 - y2;
        return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
    }

    private int px2dp(int pxValue) {
        return (int) (pxValue / mDensity + 0.5f);
    }

    private int dp2px(int dpValue) {
        return (int) (dpValue * mDensity + 0.5f);
    }

    public boolean isPanelShowing() {
        return isPanelShowing;
    }
}