/*
 * Copyright 2016 Michael Bely
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.michaelbel.bottomsheet;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.ArrayRes;
import android.support.annotation.BoolRes;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.IntDef;
import android.support.annotation.IntRange;
import android.support.annotation.LayoutRes;
import android.support.annotation.MenuRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.annotation.StringRes;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ViewCompat;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.GridView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

import org.michaelbel.bottomsheet.annotation.Beta;
import org.michaelbel.bottomsheet.annotation.New;
import org.michaelbel.bottomsheet.menu.BottomSheetMenu;
import org.michaelbel.bottomsheetdialog.R;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;

/**
 * Date: 17 FEB 2018
 * Time: 00:33 MSK
 *
 * @author Michael Bel
 */

@SuppressWarnings("all")
public class BottomSheet extends Dialog {

    public static final int LIST = 1;
    public static final int GRID = 2;

    public static final int LIGHT_THEME = 10;
    public static final int DARK_THEME = 11;

    public static final int FAB_SLIDE_UP = 20;
    public static final int FAB_SHOW_HIDE = 21;

    @RestrictTo(LIBRARY_GROUP)
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({LIST, GRID})
    public @interface Type {}

    @RestrictTo(LIBRARY_GROUP)
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({LIGHT_THEME, DARK_THEME})
    public @interface Theme {}

    @RestrictTo(LIBRARY_GROUP)
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({FAB_SHOW_HIDE, FAB_SLIDE_UP})
    public @interface FabBehavior {}

    private boolean dividers;
    private boolean fullWidth;
    private boolean darkTheme;
    private boolean titleTextMultiline;

    private int cellHeight;
    private int itemSelector;
    private int dimmingValue = 80;

    private @Type int contentType = LIST;
    private @Theme int theme = LIGHT_THEME;
    private @FabBehavior int fabBehavior = FAB_SHOW_HIDE;

    private @ColorInt int titleTextColor;
    private @ColorInt int backgroundColor;
    private @ColorInt int iconColor;
    private @ColorInt int itemTextColor;

    private View customView;
    private TextView titleTextView;
    private ListView listView;
    private GridView gridView;
    private ContainerView container;
    private LinearLayout containerView;

    private List<Drawable> ICONS = new ArrayList<>();
    private List<CharSequence> ITEMS = new ArrayList<>();

    private CharSequence titleText;
    private ArrayList<BottomSheetItem> bottomsheetItems = new ArrayList<>();

    private WindowInsets lastInsets;
    private Runnable startAnimationRunnable;
    private int layoutCount;
    private boolean dismissed;

    private ColorDrawable backDrawable = new ColorDrawable(0xFF000000);
    private boolean allowCustomAnimation = true;
    private int touchSlop;
    private boolean useFastDismiss;
    private boolean focusable;
    private Drawable shadowDrawable;
    private int backgroundPaddingTop;
    private int backgroundPaddingLeft;
    private AnimatorSet currentSheetAnimation;

    private Point displaySize = new Point();
    private DisplayMetrics metrics = new DisplayMetrics();
    private Handler handler = new Handler(Looper.getMainLooper());

    private OnClickListener onClickListener;
    private BottomSheetCallback bottomSheetCallback;

    private DialogInterface.OnShowListener onShowListener;
    private DialogInterface.OnDismissListener onDismissListener;

    private boolean allowDrawContent = true;
    private boolean useHardwareLayer = true;

    private FloatingActionButton floatingActionButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (backgroundColor == 0) {
            backgroundColor = darkTheme ? 0xFF424242 : 0xFFFFFFFF;
        }

        if (titleTextColor == 0) {
            titleTextColor = darkTheme ? 0xB3FFFFFF : 0x8A000000;
        }

        if (itemTextColor == 0) {
            itemTextColor = darkTheme ? 0xFFFFFFFF : 0xDE000000;
        }

        if (iconColor == 0) {
            iconColor = darkTheme ? 0xFFFFFFFF : 0x8A000000;
        }

        if (itemSelector == 0) {
            itemSelector = darkTheme ? R.drawable.selectable_dark : R.drawable.selectable_light;
        }

        if (cellHeight == 0) {
            cellHeight = Utils.dp(getContext(), 48);
        }

        Window window = getWindow();
        if (window != null) {
            window.setWindowAnimations(R.style.DialogNoAnimation);
        }
        setContentView(container, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

        if (containerView == null) {
            containerView = new LinearLayout(getContext()) {
                @Override
                public boolean hasOverlappingRendering() {
                    return false;
                }

                @Override
                public void setTranslationY(float translationY) {
                    super.setTranslationY(translationY);
                    onContainerTranslationYChanged(translationY);
                }
            };
            containerView.setOrientation(LinearLayout.VERTICAL);
            containerView.setBackgroundDrawable(shadowDrawable);
            containerView.setPadding(0, backgroundPaddingTop, 0, Utils.dp(getContext(), 8));
        }

        if (Build.VERSION.SDK_INT >= 21) {
            containerView.setFitsSystemWindows(true);
        }

        containerView.setVisibility(View.INVISIBLE);
        containerView.setBackgroundColor(backgroundColor);

        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        params.gravity = Gravity.BOTTOM;

        containerView.setLayoutParams(params);
        container.addView(containerView, 0);

        if (customView != null) {
            if (customView.getParent() != null) {
                ViewGroup viewGroup = (ViewGroup) customView.getParent();
                viewGroup.removeView(customView);
            }

            FrameLayout.LayoutParams params1 = (FrameLayout.LayoutParams) containerView.getLayoutParams();
            params1.width = ViewGroup.LayoutParams.MATCH_PARENT;
            params1.height = ViewGroup.LayoutParams.WRAP_CONTENT;
            params1.gravity = Gravity.START | Gravity.TOP;
            containerView.addView(customView, params1);
        } else {
            if (titleText != null) {
                titleTextView = new TextView(getContext());
                titleTextView.setLines(1);
                titleTextView.setText(titleText);
                titleTextView.setTextColor(titleTextColor);
                titleTextView.setGravity(Gravity.CENTER_VERTICAL);
                titleTextView.setEllipsize(TextUtils.TruncateAt.END);
                titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);

                if (titleTextMultiline) {
                    titleTextView.setSingleLine(false);
                } else {
                    titleTextView.setMaxLines(1);
                    titleTextView.setSingleLine(true);
                }

                LinearLayout.LayoutParams params0 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                params0.gravity = Gravity.START | Gravity.TOP;
                params0.leftMargin = Utils.dp(getContext(), 16);
                params0.rightMargin = Utils.dp(getContext(), 16);
                params0.topMargin = Utils.dp(getContext(), 8);
                params0.bottomMargin = Utils.dp(getContext(), 16);

                titleTextView.setLayoutParams(params0);
                containerView.addView(titleTextView);
            }

            BottomSheetAdapter adapter = new BottomSheetAdapter();

            if (!ITEMS.isEmpty()) {
                if (contentType == LIST) {
                    LinearLayout.LayoutParams params2 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

                    listView = new ListView(getContext());
                    listView.setSelector(itemSelector);
                    listView.setDividerHeight(0);
                    listView.setAdapter(adapter);
                    listView.setDrawSelectorOnTop(true);
                    listView.setVerticalScrollBarEnabled(false);
                    listView.setLayoutParams(params2);
                    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                        @Override
                        public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                            dismissWithButtonClick(position);
                        }
                    });

                    containerView.addView(listView);
                } else if (contentType == GRID) {
                    LinearLayout.LayoutParams params3 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

                    gridView = new GridView(getContext());
                    gridView.setSelector(itemSelector);
                    gridView.setAdapter(adapter);
                    gridView.setNumColumns(3);
                    gridView.setVerticalScrollBarEnabled(false);
                    gridView.setVerticalSpacing(Utils.dp(getContext(), 16));
                    gridView.setPadding(Utils.dp(getContext(), 0), Utils.dp(getContext(),8), Utils.dp(getContext(), 0), Utils.dp(getContext(), 16));
                    gridView.setLayoutParams(params3);
                    gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                        @Override
                        public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                            dismissWithButtonClick(i);
                        }
                    });

                    containerView.addView(gridView);
                }

                if (!ITEMS.isEmpty()) {
                    for (int a = 0; a < ITEMS.size(); a++) {
                        bottomsheetItems.add(new BottomSheetItem(ITEMS.get(a), !ICONS.isEmpty() ? ICONS.get(a) : null));
                    }
                }

                adapter.notifyDataSetChanged();
            }
        }

        WindowManager.LayoutParams params4 = window.getAttributes();
        params4.width = ViewGroup.LayoutParams.MATCH_PARENT;
        params4.gravity = Gravity.TOP | Gravity.START;
        params4.dimAmount = 0;
        params4.flags &= ~WindowManager.LayoutParams.FLAG_DIM_BEHIND;
        if (!focusable) {
            params4.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
        }
        params4.height = ViewGroup.LayoutParams.MATCH_PARENT;
        window.setAttributes(params4);
    }

    @Override
    public void show() {
        super.show();
        if (focusable) {
            try {
                getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        dismissed = false;
        cancelSheetAnimation();
        containerView.measure(View.MeasureSpec.makeMeasureSpec(displaySize.x, View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(displaySize.y, View.MeasureSpec.AT_MOST));

        backDrawable.setAlpha(0);

        if (Build.VERSION.SDK_INT >= 18) {
            layoutCount = 2;
            containerView.setTranslationY(containerView.getMeasuredHeight());
            handler.postDelayed(startAnimationRunnable = new Runnable() {
                @Override
                public void run() {
                    if (startAnimationRunnable != this) {
                        return;
                    }

                    startAnimationRunnable = null;
                    startShowAnimation();
                }
            }, 150);
        } else {
            startShowAnimation();
        }

        if (bottomSheetCallback != null) {
            bottomSheetCallback.onShown();
        }

        if (onShowListener != null) {
            onShowListener.onShow(this);
        }
    }

    @Override
    public void dismiss() {
        dismissWithButtonClick(-1);
    }

    private void dismissWithButtonClick(final int viewId) {
        if (dismissed) {
            return;
        }

        dismissed = true;
        cancelSheetAnimation();

        AnimatorSet animatorSet = new AnimatorSet();
        if (floatingActionButton != null && fabBehavior == FAB_SLIDE_UP) {
            animatorSet.playTogether(
                ObjectAnimator.ofFloat(containerView, "translationY", containerView.getMeasuredHeight() + Utils.dp(getContext(), 10)),
                ObjectAnimator.ofInt(backDrawable, "alpha", 0),
                ObjectAnimator.ofFloat(floatingActionButton, "translationY", 0)
            );
        } else if (floatingActionButton == null || fabBehavior != FAB_SLIDE_UP) {
            animatorSet.playTogether(
                ObjectAnimator.ofFloat(containerView, "translationY", containerView.getMeasuredHeight() + Utils.dp(getContext(), 10)),
                ObjectAnimator.ofInt(backDrawable, "alpha", 0)
            );
        }
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationCancel(Animator animation) {
                super.onAnimationCancel(animation);
                if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) {
                    currentSheetAnimation = null;
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) {
                    currentSheetAnimation = null;

                    if (viewId != -1) {
                        if (onClickListener != null) {
                            onClickListener.onClick(BottomSheet.this, viewId);
                        }
                    }

                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                BottomSheet.super.dismiss();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
        });

        if (useFastDismiss) {
            int height = containerView.getMeasuredHeight();
            animatorSet.setDuration(Math.max(60, (int) (180 * (height - containerView.getTranslationY()) / (float) height)));
            useFastDismiss = false;
        } else {
            animatorSet.setDuration(180);
        }

        animatorSet.setInterpolator(new AccelerateInterpolator());
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) {
                    currentSheetAnimation = null;
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                dismissInternal();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }

                if (floatingActionButton != null && fabBehavior == FAB_SHOW_HIDE) {
                    floatingActionButton.show();
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                super.onAnimationCancel(animation);
                if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) {
                    currentSheetAnimation = null;
                }
            }
        });

        animatorSet.start();
        currentSheetAnimation = animatorSet;

        if (bottomSheetCallback != null) {
            bottomSheetCallback.onDismissed();
        }

        if (onDismissListener != null) {
            onDismissListener.onDismiss(this);
        }
    }

    private BottomSheet(Context context, boolean needFocus) {
        super(context, R.style.TransparentDialog);

        if (Build.VERSION.SDK_INT >= 21) {
            getWindow().addFlags(
                 WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
                 WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
            );
        }

        ViewConfiguration vc = ViewConfiguration.get(context);
        touchSlop = vc.getScaledTouchSlop();

        Rect padding = new Rect();
        shadowDrawable = ContextCompat.getDrawable(context, R.drawable.sheet_shadow);
        shadowDrawable.setColorFilter(new PorterDuffColorFilter(0xFFFFFFFF, PorterDuff.Mode.MULTIPLY)); // todo ADDED
        shadowDrawable.getPadding(padding);
        backgroundPaddingLeft = padding.left;
        backgroundPaddingTop = padding.top;

        container = new BottomSheet.ContainerView(getContext()) {
            @Override
            public boolean drawChild(Canvas canvas, View child, long drawingTime) {
                try {
                    return allowDrawContent && super.drawChild(canvas, child, drawingTime);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return true;
            }
        };

        container.setBackgroundDrawable(backDrawable);
        focusable = needFocus;

        if (Build.VERSION.SDK_INT >= 21) {
            container.setFitsSystemWindows(true);
            container.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
                @SuppressLint("NewApi")
                @Override
                public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) {
                    lastInsets = insets;
                    view.requestLayout();
                    return insets.consumeSystemWindowInsets();
                }
            });
            container.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
        }

        backDrawable.setAlpha(0);
    }

    private class ContainerView extends FrameLayout implements NestedScrollingParent {

        private int startedTrackingX;
        private int startedTrackingY;
        private int startedTrackingPointerId;

        private boolean maybeStartTracking = false;
        private boolean startedTracking = false;

        private AnimatorSet currentAnimation = null;
        private VelocityTracker velocityTracker = null;
        private NestedScrollingParentHelper nestedScrollingParentHelper;

        public ContainerView(Context context) {
            super(context);
            nestedScrollingParentHelper = new NestedScrollingParentHelper(this);
        }

        @Override
        public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes) {
            return !dismissed && axes == ViewCompat.SCROLL_AXIS_VERTICAL && !canDismissWithSwipe();
        }

        @Override
        public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes) {
            nestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
            if (dismissed) {
                return;
            }
            cancelCurrentAnimation();
        }

        @Override
        public void onStopNestedScroll(@NonNull View target) {
            nestedScrollingParentHelper.onStopNestedScroll(target);
            if (dismissed) {
                return;
            }
            checkDismiss(0, 0);
        }

        @Override
        public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
            if (dismissed) {
                return;
            }

            cancelCurrentAnimation();

            if (dyUnconsumed != 0) {
                float currentTranslation = containerView.getTranslationY();
                currentTranslation -= dyUnconsumed;

                if (currentTranslation < 0) {
                    currentTranslation = 0;
                }

                containerView.setTranslationY(currentTranslation);
            }
        }

        @Override
        public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) {
            if (dismissed) {
                return;
            }

            cancelCurrentAnimation();
            float currentTranslation = containerView.getTranslationY();

            if (currentTranslation > 0 && dy > 0) {
                currentTranslation -= dy;
                consumed[1] = dy;

                if (currentTranslation < 0) {
                    currentTranslation = 0;
                    consumed[1] += currentTranslation;
                }

                containerView.setTranslationY(currentTranslation);
            }
        }

        @Override
        public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) {
            return false;
        }

        @Override
        public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
            return false;
        }

        @Override
        public int getNestedScrollAxes() {
            return nestedScrollingParentHelper.getNestedScrollAxes();
        }

        private void checkDismiss(float velX, float velY) {
            float translationY = containerView.getTranslationY();
            boolean backAnimation = translationY < getPixelsInCM(0.8f, false) && (velY < 3500 || Math.abs(velY) < Math.abs(velX)) || velY < 0 && Math.abs(velY) >= 3500;

            if (!backAnimation) {
                boolean allowOld = allowCustomAnimation;
                allowCustomAnimation = false;
                useFastDismiss = true;
                dismiss();
                allowCustomAnimation = allowOld;
            } else {
                currentAnimation = new AnimatorSet();
                currentAnimation.playTogether(ObjectAnimator.ofFloat(containerView, "translationY", 0));
                currentAnimation.setDuration((int) (150 * (translationY / getPixelsInCM(0.8F, false))));
                currentAnimation.setInterpolator(new DecelerateInterpolator());
                currentAnimation.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);

                        if (currentAnimation != null && currentAnimation.equals(animation)) {
                            currentAnimation = null;
                        }
                    }
                });
                currentAnimation.start();
            }
        }

        private void cancelCurrentAnimation() {
            if (currentAnimation != null) {
                currentAnimation.cancel();
                currentAnimation = null;
            }
        }

        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            if (dismissed) {
                return false;
            }
            if (onContainerTouchEvent(ev)) {
                return true;
            }
            if (canDismissWithTouchOutside() && ev != null && (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_MOVE) && !startedTracking && !maybeStartTracking) {
                startedTrackingX = (int) ev.getX();
                startedTrackingY = (int) ev.getY();
                if (startedTrackingY < containerView.getTop() || startedTrackingX < containerView.getLeft() || startedTrackingX > containerView.getRight()) {
                    dismiss();
                    return true;
                }
                startedTrackingPointerId = ev.getPointerId(0);
                maybeStartTracking = true;
                cancelCurrentAnimation();
                if (velocityTracker != null) {
                    velocityTracker.clear();
                }
            } else if (ev != null && ev.getAction() == MotionEvent.ACTION_MOVE && ev.getPointerId(0) == startedTrackingPointerId) {
                if (velocityTracker == null) {
                    velocityTracker = VelocityTracker.obtain();
                }
                float dx = Math.abs((int) (ev.getX() - startedTrackingX));
                float dy = (int) ev.getY() - startedTrackingY;
                velocityTracker.addMovement(ev);
                if (maybeStartTracking && !startedTracking && (dy > 0 && dy / 3.0f > Math.abs(dx) && Math.abs(dy) >= touchSlop)) {
                    startedTrackingY = (int) ev.getY();
                    maybeStartTracking = false;
                    startedTracking = true;
                    requestDisallowInterceptTouchEvent(true);
                } else if (startedTracking) {
                    float translationY = containerView.getTranslationY();
                    translationY += dy;
                    if (translationY < 0) {
                        translationY = 0;
                    }
                    containerView.setTranslationY(translationY);
                    startedTrackingY = (int) ev.getY();
                }
            } else if (ev == null || ev != null && ev.getPointerId(0) == startedTrackingPointerId && (ev.getAction() == MotionEvent.ACTION_CANCEL || ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_POINTER_UP)) {
                if (velocityTracker == null) {
                    velocityTracker = VelocityTracker.obtain();
                }
                velocityTracker.computeCurrentVelocity(1000);
                float translationY = containerView.getTranslationY();
                if (startedTracking || translationY != 0) {
                    checkDismiss(velocityTracker.getXVelocity(), velocityTracker.getYVelocity());
                    startedTracking = false;
                } else {
                    maybeStartTracking = false;
                    startedTracking = false;
                }
                if (velocityTracker != null) {
                    velocityTracker.recycle();
                    velocityTracker = null;
                }
                startedTrackingPointerId = -1;
            }
            return startedTracking || !canDismissWithSwipe();
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int width = MeasureSpec.getSize(widthMeasureSpec);
            int height = MeasureSpec.getSize(heightMeasureSpec);
            if (lastInsets != null && Build.VERSION.SDK_INT >= 21) {
                height -= lastInsets.getSystemWindowInsetBottom();
            }

            setMeasuredDimension(width, height);
            if (lastInsets != null && Build.VERSION.SDK_INT >= 21) {
                width -= lastInsets.getSystemWindowInsetRight() + lastInsets.getSystemWindowInsetLeft();
            }
            boolean isPortrait = width < height;

            if (containerView != null) {
                if (!fullWidth) {
                    int widthSpec;
                    widthSpec = MeasureSpec.makeMeasureSpec(isPortrait ? width + backgroundPaddingLeft * 2 : (int) Math.max(width * 0.8f, Math.min(Utils.dp(getContext(), 480), width)) + backgroundPaddingLeft * 2, MeasureSpec.EXACTLY);

                    /*if (Utils.isTablet(getContext())) {
                        widthSpec = MeasureSpec.makeMeasureSpec((int) (Math.min(displaySize.x, displaySize.y) * 0.8f) + backgroundPaddingLeft * 2, MeasureSpec.EXACTLY);
                    } else {
                        widthSpec = MeasureSpec.makeMeasureSpec(isPortrait ? width + backgroundPaddingLeft * 2 : (int) Math.max(width * 0.8f, Math.min(Utils.dp(getContext(), 480), width)) + backgroundPaddingLeft * 2, MeasureSpec.EXACTLY);
                    }*/

                    containerView.measure(widthSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
                } else {
                    containerView.measure(MeasureSpec.makeMeasureSpec(width + backgroundPaddingLeft * 2, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
                }
            }
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if (child.getVisibility() == GONE || child == containerView) {
                    continue;
                }
                if (!onCustomMeasure(child, width, height)) {
                    measureChildWithMargins(child, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 0, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY), 0);
                }
            }
        }

        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            layoutCount--;
            if (containerView != null) {
                if (lastInsets != null && Build.VERSION.SDK_INT >= 21) {
                    left += lastInsets.getSystemWindowInsetLeft();
                    right -= lastInsets.getSystemWindowInsetRight();
                }
                int t = (bottom - top) - containerView.getMeasuredHeight();
                int l = ((right - left) - containerView.getMeasuredWidth()) / 2;
                if (lastInsets != null && Build.VERSION.SDK_INT >= 21) {
                    l += lastInsets.getSystemWindowInsetLeft();
                }
                containerView.layout(l, t, l + containerView.getMeasuredWidth(), t + containerView.getMeasuredHeight());
            }

            final int count = getChildCount();
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() == GONE || child == containerView) {
                    continue;
                }
                if (!onCustomLayout(child, left, top, right, bottom)) {
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                    final int width = child.getMeasuredWidth();
                    final int height = child.getMeasuredHeight();

                    int childLeft;
                    int childTop;

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

                    final int absoluteGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
                    final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

                    switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                        case Gravity.CENTER_HORIZONTAL:
                            childLeft = (right - left - width) / 2 + lp.leftMargin - lp.rightMargin;
                            break;
                        case Gravity.RIGHT:
                            childLeft = right - width - lp.rightMargin;
                            break;
                        case Gravity.LEFT:
                        default:
                            childLeft = lp.leftMargin;
                    }

                    switch (verticalGravity) {
                        case Gravity.TOP:
                            childTop = lp.topMargin;
                            break;
                        case Gravity.CENTER_VERTICAL:
                            childTop = (bottom - top - height) / 2 + lp.topMargin - lp.bottomMargin;
                            break;
                        case Gravity.BOTTOM:
                            childTop = (bottom - top) - height - lp.bottomMargin;
                            break;
                        default:
                            childTop = lp.topMargin;
                    }
                    if (lastInsets != null && Build.VERSION.SDK_INT >= 21) {
                        childLeft += lastInsets.getSystemWindowInsetLeft();
                    }
                    child.layout(childLeft, childTop, childLeft + width, childTop + height);
                }
            }
            if (layoutCount == 0 && startAnimationRunnable != null) {
                handler.removeCallbacks(startAnimationRunnable);
                startAnimationRunnable.run();
                startAnimationRunnable = null;
            }
        }

        @Override
        public boolean onInterceptTouchEvent(MotionEvent event) {
            if (canDismissWithSwipe()) {
                return onTouchEvent(event);
            }

            return super.onInterceptTouchEvent(event);
        }

        @Override
        public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
            if (maybeStartTracking && !startedTracking) {
                onTouchEvent(null);
            }

            super.requestDisallowInterceptTouchEvent(disallowIntercept);
        }

        @Override
        public boolean hasOverlappingRendering() {
            return false;
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            onContainerDraw(canvas);
        }
    }

    private boolean onContainerTouchEvent(MotionEvent event) {
        return false;
    }

    private boolean canDismissWithSwipe() {
        return false; // todo: true
    }

    private boolean canDismissWithTouchOutside() {
        return true;
    }

    private void cancelSheetAnimation() {
        if (currentSheetAnimation != null) {
            currentSheetAnimation.cancel();
            currentSheetAnimation = null;
        }
    }

    private void startShowAnimation() {
        if (dismissed) {
            return;
        }
        containerView.setVisibility(View.VISIBLE);
        if (!onCustomOpenAnimation()) {
            if (Build.VERSION.SDK_INT >= 20 && useHardwareLayer) {
                container.setLayerType(View.LAYER_TYPE_HARDWARE, null);
            }
            containerView.setTranslationY(containerView.getMeasuredHeight());
            AnimatorSet animatorSet = new AnimatorSet();

            if (floatingActionButton != null && fabBehavior == FAB_SLIDE_UP) {
                animatorSet.playTogether(
                    ObjectAnimator.ofFloat(floatingActionButton, "translationY", -(containerView.getMeasuredHeight())),
                    ObjectAnimator.ofFloat(containerView, "translationY", 0),
                    ObjectAnimator.ofInt(backDrawable, "alpha", dimmingValue));
            } else if (floatingActionButton == null || fabBehavior != FAB_SLIDE_UP) {
                animatorSet.playTogether(
                    ObjectAnimator.ofFloat(containerView, "translationY", 0),
                    ObjectAnimator.ofInt(backDrawable, "alpha", dimmingValue));
            }

            animatorSet.setDuration(200);
            animatorSet.setStartDelay(20);
            animatorSet.setInterpolator(new DecelerateInterpolator());
            animatorSet.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {
                    super.onAnimationStart(animation);

                    if (floatingActionButton != null && fabBehavior == FAB_SHOW_HIDE) {
                        floatingActionButton.hide();
                    }
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) {
                        currentSheetAnimation = null;
                        if (useHardwareLayer) {
                            container.setLayerType(View.LAYER_TYPE_NONE, null);
                        }
                    }
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                    if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) {
                        currentSheetAnimation = null;
                    }
                }
            });
            animatorSet.start();
            currentSheetAnimation = animatorSet;
        }
    }

    private void dismissInternal() {
        super.dismiss();
    }

    private float getPixelsInCM(float cm, boolean isX) {
        return (cm / 2.54F) * (isX ? metrics.xdpi : metrics.ydpi);
    }

    protected void onContainerTranslationYChanged(float translationY) {}

    public void setAllowDrawContent(boolean value) {
        if (allowDrawContent != value) {
            allowDrawContent = value;
            container.setBackgroundDrawable(allowDrawContent ? backDrawable : null);
            container.invalidate();
        }
    }

    protected boolean onCustomMeasure(View view, int width, int height) {
        return false;
    }

    protected boolean onCustomLayout(View view, int left, int top, int right, int bottom) {
        return false;
    }

    public void onContainerDraw(Canvas canvas) {}

    protected boolean onCustomOpenAnimation() {
        return false;
    }

//--------------------------------------------------------------------------------------------------

    public static class Builder {

        private Context context;
        private BottomSheet bottomSheet;

        public Builder(@NonNull Context context) {
            this.context = context;
            bottomSheet = new BottomSheet(context, false);
        }

        public Builder(@NonNull Context context, boolean focus) {
            this.context = context;
            bottomSheet = new BottomSheet(context, focus);
        }

        public Builder(@NonNull Context context, @BoolRes int focus) {
            this.context = context;
            bottomSheet = new BottomSheet(context, context.getResources().getBoolean(focus));
        }

        public Builder setTitle(@StringRes int titleId) {
            bottomSheet.titleText = context.getText(titleId);
            return this;
        }

        public Builder setTitle(@Nullable CharSequence title) {
            bottomSheet.titleText = title;
            return this;
        }

        public Builder setItems(@ArrayRes int itemsId, final OnClickListener listener) {
            bottomSheet.ITEMS.addAll(Arrays.asList(context.getResources().getTextArray(itemsId)));
            bottomSheet.onClickListener = listener;
            return this;
        }

        public Builder setItems(@StringRes int[] items, final OnClickListener listener) {
            for (int i : items) {
                bottomSheet.ITEMS.add(context.getResources().getString(i));
            }
            bottomSheet.onClickListener = listener;
            return this;
        }

        public Builder setItems(CharSequence[] items, final OnClickListener listener) {
            bottomSheet.ITEMS.addAll(Arrays.asList(items));
            bottomSheet.onClickListener = listener;
            return this;
        }

        public Builder setItems(@ArrayRes int itemsId, int[] icons, final OnClickListener listener) {
            bottomSheet.ITEMS.addAll(Arrays.asList(context.getResources().getTextArray(itemsId)));
            for (int i: icons) {
                bottomSheet.ICONS.add(ContextCompat.getDrawable(context, i));
            }
            bottomSheet.onClickListener = listener;
            return this;
        }

        public Builder setItems(@ArrayRes int itemsId, Drawable[] icons, final OnClickListener listener) {
            Collections.addAll(bottomSheet.ITEMS, context.getResources().getTextArray(itemsId));
            Collections.addAll(bottomSheet.ICONS, icons);
            bottomSheet.onClickListener = listener;
            return this;
        }

        public Builder setItems(@StringRes int[] items, int[] icons, final OnClickListener listener) {
            for (int i : items) {
                bottomSheet.ITEMS.add(context.getResources().getString(i));
            }
            for (int j: icons) {
                bottomSheet.ICONS.add(ContextCompat.getDrawable(context, j));
            }
            bottomSheet.onClickListener = listener;
            return this;
        }

        public Builder setItems(@StringRes int[] items, Drawable[] icons, final OnClickListener listener) {
            for (int i : items) {
                bottomSheet.ITEMS.add(context.getResources().getString(i));
            }
            Collections.addAll(bottomSheet.ICONS, icons);
            bottomSheet.onClickListener = listener;
            return this;
        }

        public Builder setItems(@NonNull CharSequence[] items, int[] icons, final OnClickListener listener) {
            bottomSheet.ITEMS.addAll(Arrays.asList(items));
            for (int i: icons) {
                bottomSheet.ICONS.add(ContextCompat.getDrawable(context, i));
            }
            bottomSheet.onClickListener = listener;
            return this;
        }

        public Builder setItems(@NonNull CharSequence[] items, Drawable[] icons, final OnClickListener listener) {
            bottomSheet.ITEMS.addAll(Arrays.asList(items));
            Collections.addAll(bottomSheet.ICONS, icons);
            bottomSheet.onClickListener = listener;
            return this;
        }

        public Builder setMenu(@MenuRes int menuResId, final OnClickListener listener) {
            BottomSheetMenu menu = new BottomSheetMenu(context);
            new MenuInflater(context).inflate(menuResId, menu);

            for (int i = 0; i < menu.size(); i++) {
                bottomSheet.ITEMS.add(menu.getItem(i).getTitle());
                bottomSheet.ICONS.add(menu.getItem(i).getIcon());
            }

            bottomSheet.onClickListener = listener;
            return this;
        }

        public Builder setMenu(Menu menu, final OnClickListener listener) {
            for (int i = 0; i < menu.size(); i++) {
                bottomSheet.ITEMS.add(menu.getItem(i).getTitle());
                bottomSheet.ICONS.add(menu.getItem(i).getIcon());
            }

            bottomSheet.onClickListener = listener;
            return this;
        }

        public Builder setView(@LayoutRes int layoutResId) {
            bottomSheet.customView = LayoutInflater.from(context).inflate(layoutResId, null);
            return this;
        }

        public Builder setView(View view) {
            bottomSheet.customView = view;
            return this;
        }

//----- Styles -------------------------------------------------------------------------------------

        public Builder setContentType(@Type int type) {
            bottomSheet.contentType = type;
            return this;
        }

        public Builder setDarkTheme(boolean theme) {
            bottomSheet.darkTheme = theme;
            return this;
        }

        public Builder setDarkTheme(@BoolRes int theme) {
            bottomSheet.darkTheme = context.getResources().getBoolean(theme);
            return this;
        }

        public Builder setFullWidth(boolean fullWidth) {
            bottomSheet.fullWidth = fullWidth;
            return this;
        }

        public Builder setFullWidth(@BoolRes int fullWidth) {
            bottomSheet.fullWidth = context.getResources().getBoolean(fullWidth);
            return this;
        }

        public Builder setCellHeight(int cellHeight) {
            bottomSheet.cellHeight = cellHeight;
            return this;
        }

        public Builder setDividers(boolean dividers) {
            bottomSheet.dividers = dividers;
            return this;
        }

        public Builder setDividers(@BoolRes int dividers) {
            bottomSheet.dividers = context.getResources().getBoolean(dividers);
            return this;
        }

        public Builder setWindowDimming(@IntRange(from = 0, to = 255) int windowDimming) {
            bottomSheet.dimmingValue = windowDimming;
            return this;
        }

        public Builder setTitleMultiline(boolean titleMultiline) {
            bottomSheet.titleTextMultiline = titleMultiline;
            return this;
        }

        public Builder setTitleMultiline(@BoolRes int titleMultiline) {
            bottomSheet.titleTextMultiline = context.getResources().getBoolean(titleMultiline);
            return this;
        }

        public Builder setFabBehavior(FloatingActionButton fab) {
            bottomSheet.floatingActionButton = fab;
            return this;
        }

        public Builder setFabBehavior(FloatingActionButton fab, @FabBehavior int fabBehavior) {
            bottomSheet.floatingActionButton = fab;
            bottomSheet.fabBehavior = fabBehavior;
            return this;
        }

//----- Colors -------------------------------------------------------------------------------------

        public Builder setBackgroundColor(@ColorInt int backgroundColor) {
            bottomSheet.backgroundColor = backgroundColor;
            return this;
        }

        public Builder setBackgroundColorRes(@ColorRes int backgroundColorRes) {
            bottomSheet.backgroundColor = ContextCompat.getColor(context, backgroundColorRes);
            return this;
        }

        public Builder setTitleTextColor(@ColorInt int titleTextColor) {
            bottomSheet.titleTextColor = titleTextColor;
            return this;
        }

        public Builder setTitleTextColorRes(@ColorRes int titleTextColorRes) {
            bottomSheet.titleTextColor = ContextCompat.getColor(context, titleTextColorRes);
            return this;
        }

        public Builder setItemTextColor(@ColorInt int itemTextColor) {
            bottomSheet.itemTextColor = itemTextColor;
            return this;
        }

        public Builder setItemTextColorRes(@ColorRes int itemTextColorRes) {
            bottomSheet.itemTextColor = ContextCompat.getColor(context, itemTextColorRes);
            return this;
        }

        public Builder setIconColor(@ColorInt int iconColor) {
            bottomSheet.iconColor = iconColor;
            return this;
        }

        public Builder setIconColorRes(@ColorInt int iconColorRes) {
            bottomSheet.iconColor = ContextCompat.getColor(context, iconColorRes);
            return this;
        }

        public Builder setItemSelector(int selector) {
            bottomSheet.itemSelector = selector;
            return this;
        }

//----- Interfaces ---------------------------------------------------------------------------------

        public Builder setOnShowListener(DialogInterface.OnShowListener listener) {
            bottomSheet.onShowListener = listener;
            return this;
        }

        public Builder setOnDismissListener(DialogInterface.OnDismissListener listener) {
            bottomSheet.onDismissListener = listener;
            return this;
        }

        public Builder setCallback(BottomSheetCallback callback) {
            bottomSheet.bottomSheetCallback = callback;
            return this;
        }

//----- Build --------------------------------------------------------------------------------------

        public BottomSheet show() {
            bottomSheet.show();
            return bottomSheet;
        }

        public BottomSheet dismiss() {
            bottomSheet.dismiss();
            return bottomSheet;
        }

        public BottomSheet create() {
            return bottomSheet;
        }

//----- Getters ------------------------------------------------------------------------------------

        public TextView getTitleTextView() {
            return bottomSheet.titleTextView;
        }

        public ListView getListView() {
            return bottomSheet.listView;
        }

        public GridView getGridView() {
            return bottomSheet.gridView;
        }

        public View getView() {
            return bottomSheet.customView;
        }

//----- Deprecated ---------------------------------------------------------------------------------

        @Deprecated
        public Builder setCustomView(View view) {
            bottomSheet.customView = view;
            return this;
        }

        @Deprecated
        public Builder setCustomView(@LayoutRes int layoutResId) {
            bottomSheet.customView = LayoutInflater.from(context).inflate(layoutResId, null);
            return this;
        }
    }

//--------------------------------------------------------------------------------------------------

    private class BottomSheetAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return ITEMS != null ? ITEMS.size() : 0;
        }

        @Override
        public Object getItem(int i) {
            return null;
        }

        @Override
        public long getItemId(int i) {
            return 0;
        }

        @Override
        public View getView(int position, View view, ViewGroup viewGroup) {
            int type = getItemViewType(position);
            BottomSheetItem item = bottomsheetItems.get(position);

            if (type == 0) {
                if (contentType == LIST) {
                    if (view == null) {
                        view = new BottomSheetCell(getContext());
                    }

                    BottomSheetCell cell = (BottomSheetCell) view;
                    cell.setIcon(item.icon, iconColor);
                    cell.setText(item.text, itemTextColor);
                    cell.setHeight(cellHeight);
                    if (position != bottomsheetItems.size() - 1) {
                        cell.setDivider(dividers);
                        cell.setDividerColor(darkTheme);
                    }
                } else {
                    if (view == null) {
                        view = new BottomSheetGrid(getContext());
                    }

                    BottomSheetGrid cell = (BottomSheetGrid) view;
                    cell.setIcon(item.icon, iconColor);
                    cell.setText(item.text, itemTextColor);
                }
            }

            return view;
        }

        @Override
        public int getItemViewType(int i) {
            return 0;
        }

        @Override
        public int getViewTypeCount() {
            return 1;
        }
    }
}