package com.ovenbits.quickactionview;

import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.IdRes;
import android.support.annotation.MenuRes;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v7.view.menu.MenuBuilder;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowManager;
import android.widget.FrameLayout;

import com.ovenbits.quickactionview.animator.FadeInFadeOutActionsTitleAnimator;
import com.ovenbits.quickactionview.animator.SlideFromCenterAnimator;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * A QuickActionView, which shows actions when a view is long pressed.
 *
 * @see <a href="https://github.com/ovenbits/QuickActionView">https://github.com/ovenbits/QuickActionView</a>
 */
public class QuickActionView {

    private boolean mShown = false;
    private Context mContext;
    private OnActionSelectedListener mOnActionSelectedListener;
    private OnDismissListener mOnDismissListener;
    private OnShowListener mOnShowListener;
    private OnActionHoverChangedListener mOnActionHoverChangedListener;
    private float mActionDistance;
    private int mActionPadding;
    private ArrayList<Action> mActions = new ArrayList<>();
    private Bundle mExtras;
    private QuickActionViewLayout mQuickActionViewLayout;
    private Config mConfig;
    private ActionsInAnimator mActionsInAnimator;
    private ActionsOutAnimator mActionsOutAnimator;
    private ActionsTitleInAnimator mActionsTitleInAnimator;
    private ActionsTitleOutAnimator mActionsTitleOutAnimator;

    @ColorInt
    private int mScrimColor = Color.parseColor("#99000000");
    private Drawable mIndicatorDrawable;
    private HashMap<View, RegisteredListener> mRegisteredListeners = new HashMap<>();

    private View mClickedView;

    private QuickActionView(Context context) {
        mContext = context;
        mConfig = new Config(context);
        mIndicatorDrawable = ContextCompat.getDrawable(context, R.drawable.qav_indicator);
        mActionDistance = context.getResources().getDimensionPixelSize(R.dimen.qav_action_distance);
        mActionPadding = context.getResources().getDimensionPixelSize(R.dimen.qav_action_padding);
        SlideFromCenterAnimator defaultAnimator = new SlideFromCenterAnimator(true);
        FadeInFadeOutActionsTitleAnimator defaultTitleAnimator = new FadeInFadeOutActionsTitleAnimator();
        mActionsInAnimator = defaultAnimator;
        mActionsOutAnimator = defaultAnimator;
        mActionsTitleInAnimator = defaultTitleAnimator;
        mActionsTitleOutAnimator = defaultTitleAnimator;
    }

    /**
     * Create a QuickActionView which you can configure as desired, then
     * call {@link #register(View)} to show it.
     *
     * @param context activity context
     * @return the QuickActionView for you to
     */
    public static QuickActionView make(Context context) {
        return new QuickActionView(context);
    }

    private void show(View anchor, Point offset) {
        if (mShown) {
            throw new RuntimeException("Show cannot be called when the QuickActionView is already visible");
        }
        mShown = true;

        ViewParent parent = anchor.getParent();
        if (parent instanceof View) {
            parent.requestDisallowInterceptTouchEvent(true);
        }

        mClickedView = anchor;

        int[] loc = new int[2];
        anchor.getLocationInWindow(loc);
        Point point = new Point(offset);
        point.offset(loc[0], loc[1]);
        display(point);
    }


    /**
     * Register the QuickActionView to appear when the passed view is long pressed
     *
     * @param view the view to have long press responses
     * @return the QuickActionView
     */
    public QuickActionView register(View view) {
        RegisteredListener listener = new RegisteredListener();
        mRegisteredListeners.put(view, listener);
        view.setOnTouchListener(listener);
        view.setOnLongClickListener(listener);
        return this;
    }

    /**
     * Unregister the view so that it can no longer be long pressed to show the QuickActionView
     *
     * @param view the view to unregister
     */
    public void unregister(View view) {
        mRegisteredListeners.remove(view);
        view.setOnTouchListener(null);
        view.setOnLongClickListener(null);
    }

    /**
     * Adds an action to the QuickActionView
     *
     * @param action the action to add
     * @return the QuickActionView
     */
    public QuickActionView addAction(Action action) {
        checkShown();
        mActions.add(action);
        return this;
    }

    /**
     * Adds a collection of actions to the QuickActionView
     *
     * @param actions the actions to add
     * @return the QuickActionView
     */
    public QuickActionView addActions(Collection<Action> actions) {
        checkShown();
        mActions.addAll(actions);
        return this;
    }

    /**
     * Add actions to the QuickActionView from the given menu resource id.
     *
     * @param menuId menu resource id
     * @return the QuickActionView
     */
    public QuickActionView addActions(@MenuRes int menuId) {
        Menu menu = new MenuBuilder(mContext);
        new MenuInflater(mContext).inflate(menuId, menu);
        for (int i = 0; i < menu.size(); i++) {
            MenuItem item = menu.getItem(i);
            Action action = new Action(item.getItemId(), item.getIcon(), item.getTitle());
            addAction(action);
        }
        return this;
    }

    /**
     * Removes all actions from the QuickActionView
     *
     * @return the QuickActionView
     */
    public QuickActionView removeActions() {
        mActions.clear();
        return this;
    }

    /**
     * Remove an individual action from the QuickActionView
     *
     * @param actionId the action id
     * @return the QuickActionView
     */
    public QuickActionView removeAction(int actionId) {
        for (int i = 0; i < mActions.size(); i++) {
            if (mActions.get(i).getId() == actionId) {
                mActions.remove(i);
                return this;
            }
        }
        throw new IllegalArgumentException("No action exists for actionId" + actionId);
    }

    /**
     * @param onActionSelectedListener the listener
     * @return the QuickActionView
     * @see OnActionSelectedListener
     */
    public QuickActionView setOnActionSelectedListener(OnActionSelectedListener onActionSelectedListener) {
        mOnActionSelectedListener = onActionSelectedListener;
        return this;
    }

    /**
     * @param onDismissListener the listener
     * @return the QuickActionView
     * @see OnDismissListener
     */
    public QuickActionView setOnDismissListener(OnDismissListener onDismissListener) {
        mOnDismissListener = onDismissListener;
        return this;
    }

    /**
     * @param onShowListener the listener
     * @return the QuickActionView
     * @see OnShowListener
     */
    public QuickActionView setOnShowListener(OnShowListener onShowListener) {
        mOnShowListener = onShowListener;
        return this;
    }

    /**
     * @param listener the listener
     * @return the QuickActionView
     * @see OnActionHoverChangedListener
     */
    public QuickActionView setOnActionHoverChangedListener(OnActionHoverChangedListener listener) {
        mOnActionHoverChangedListener = listener;
        return this;
    }

    /**
     * Set the indicator drawable (the drawable that appears at the point the user has long pressed
     *
     * @param indicatorDrawable the indicator drawable
     * @return the QuickActionView
     */
    public QuickActionView setIndicatorDrawable(Drawable indicatorDrawable) {
        mIndicatorDrawable = indicatorDrawable;
        return this;
    }

    /**
     * Set the scrim color (the background behind the QuickActionView)
     *
     * @param scrimColor the desired scrim color
     * @return the QuickActionView
     */
    public QuickActionView setScrimColor(@ColorInt int scrimColor) {
        mScrimColor = scrimColor;
        return this;
    }

    /**
     * Set the drawable that appears behind the Action text labels
     *
     * @param textBackgroundDrawable the desired drawable
     * @return the QuickActionView
     */
    public QuickActionView setTextBackgroundDrawable(@DrawableRes int textBackgroundDrawable) {
        mConfig.setTextBackgroundDrawable(textBackgroundDrawable);
        return this;
    }


    /**
     * Set the background color state list for all action items
     *
     * @param backgroundColorStateList the desired colorstatelist
     * @return the QuickActionView
     */
    public QuickActionView setBackgroundColorStateList(ColorStateList backgroundColorStateList) {
        mConfig.setBackgroundColorStateList(backgroundColorStateList);
        return this;
    }

    /**
     * Set the text color for the Action labels
     *
     * @param textColor the desired text color
     * @return the QuickActionView
     */
    public QuickActionView setTextColor(@ColorInt int textColor) {
        mConfig.setTextColor(textColor);
        return this;
    }

    /**
     * Set the action's background color. If you want to have a pressed state,
     * see {@link #setBackgroundColorStateList(ColorStateList)}
     *
     * @param backgroundColor the desired background color
     * @return the QuickActionView
     */
    public QuickActionView setBackgroundColor(@ColorInt int backgroundColor) {
        mConfig.setBackgroundColor(backgroundColor);
        return this;
    }

    /**
     * Set the typeface for the Action labels
     *
     * @param typeface the desired typeface
     * @return the QuickActionView
     */
    public QuickActionView setTypeface(Typeface typeface) {
        mConfig.setTypeface(typeface);
        return this;
    }

    /**
     * Set the text size for the Action labels
     *
     * @param textSize the desired textSize (in pixels)
     * @return the QuickActionView
     */
    public QuickActionView setTextSize(int textSize) {
        mConfig.setTextSize(textSize);
        return this;
    }

    /**
     * Set the text top padding for the Action labels
     *
     * @param textPaddingTop the top padding in pixels
     * @return the QuickActionView
     */
    public QuickActionView setTextPaddingTop(int textPaddingTop) {
        mConfig.setTextPaddingTop(textPaddingTop);
        return this;
    }

    /**
     * Set the text bottom padding for the Action labels
     *
     * @param textPaddingBottom the top padding in pixels
     * @return the QuickActionView
     */
    public QuickActionView setTextPaddingBottom(int textPaddingBottom) {
        mConfig.setTextPaddingBottom(textPaddingBottom);
        return this;
    }

    /**
     * Set the text left padding for the Action labels
     *
     * @param textPaddingLeft the top padding in pixels
     * @return the QuickActionView
     */
    public QuickActionView setTextPaddingLeft(int textPaddingLeft) {
        mConfig.setTextPaddingLeft(textPaddingLeft);
        return this;
    }

    /**
     * Set the text right padding for the Action labels
     *
     * @param textPaddingRight the top padding in pixels
     * @return the QuickActionView
     */
    public QuickActionView setTextPaddingRight(int textPaddingRight) {
        mConfig.setTextPaddingRight(textPaddingRight);
        return this;
    }

    /**
     * Override the animations for when the QuickActionView shows
     *
     * @param actionsInAnimator the animation overrides
     * @return this QuickActionView
     */
    public QuickActionView setActionsInAnimator(ActionsInAnimator actionsInAnimator) {
        mActionsInAnimator = actionsInAnimator;
        return this;
    }

    /**
     * Override the animations for when the QuickActionView dismisses
     *
     * @param actionsOutAnimator the animation overrides
     * @return this QuickActionView
     */
    public QuickActionView setActionsOutAnimator(ActionsOutAnimator actionsOutAnimator) {
        mActionsOutAnimator = actionsOutAnimator;
        return this;
    }

    /**
     * Override the animations for when the QuickActionView action title shows
     *
     * @param actionsTitleInAnimator the custom animator
     * @return this QuickActionView
     */
    public QuickActionView setActionsTitleInAnimator(ActionsTitleInAnimator actionsTitleInAnimator) {
        mActionsTitleInAnimator = actionsTitleInAnimator;
        return this;
    }

    /**
     * Override the animations for when the QuickActionView dismisses
     *
     * @param actionsTitleOutAnimator the custom animator
     * @return this QuickActionView
     */
    public QuickActionView setActionsTitleOutAnimator(ActionsTitleOutAnimator actionsTitleOutAnimator) {
        mActionsTitleOutAnimator = actionsTitleOutAnimator;
        return this;
    }

    /**
     * Set a custom configuration for the action with the given id
     *
     * @param config   the configuration to attach
     * @param actionId the action id
     * @return this QuickActionView
     */
    public QuickActionView setActionConfig(Action.Config config, @IdRes int actionId) {
        for (Action action : mActions) {
            if (action.getId() == actionId) {
                action.setConfig(config);
                return this;
            }
        }

        throw new IllegalArgumentException("No Action exists with id " + actionId);
    }

    /**
     * Get the center point of the {@link QuickActionView} aka the point at which the actions will eminate from
     *
     * @return the center point, or null if the view has not yet been created
     */
    @Nullable
    public Point getCenterPoint() {
        if (mQuickActionViewLayout != null) {
            return mQuickActionViewLayout.mCenterPoint;
        }
        return null;
    }

    private void display(Point point) {
        if (mActions == null) {
            throw new IllegalStateException("You need to give the QuickActionView actions before calling show!");
        }

        WindowManager manager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
        params.format = PixelFormat.TRANSLUCENT;
        mQuickActionViewLayout = new QuickActionViewLayout(mContext, mActions, point);
        manager.addView(mQuickActionViewLayout, params);
        if (mOnShowListener != null) {
            mOnShowListener.onShow(this);
        }
    }

    private void animateHide() {
        int duration = mQuickActionViewLayout.animateOut();
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                removeView();
            }
        }, duration);
    }

    private void removeView() {

        if (mQuickActionViewLayout != null) {
            WindowManager manager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
            if (checkAttachedToWindow(mQuickActionViewLayout)) {
                manager.removeView(mQuickActionViewLayout);
            }
            mQuickActionViewLayout = null;
            mShown = false;
        }

        if (mClickedView != null) {
            ViewParent parent = mClickedView.getParent();
            if (parent instanceof View) {
                parent.requestDisallowInterceptTouchEvent(false);
            }
        }
    }

    private boolean checkAttachedToWindow(View view) {
        if (Build.VERSION.SDK_INT >= 19) {
            return view.isAttachedToWindow();
        }
        //Unfortunately, we have no way of truly knowing on versions less than 19
        return true;
    }

    private void dismiss() {
        if (!mShown) {
            throw new RuntimeException("The QuickActionView must be visible to call dismiss()");
        }
        if (mOnDismissListener != null) {
            mOnDismissListener.onDismiss(QuickActionView.this);
        }
        animateHide();
    }

    private void checkShown() {
        if (mShown) {
            throw new RuntimeException("QuickActionView cannot be configured if show has already been called.");
        }
    }

    /**
     * Get the extras associated with the QuickActionView. Allows for
     * saving state to the QuickActionView
     *
     * @return the bundle for the QuickActionView
     */
    public Bundle getExtras() {
        return mExtras;
    }

    /**
     * Set extras to associate with the QuickActionView to allow saving state
     *
     * @param extras the bundle
     * @return the QuickActionView
     */
    public QuickActionView setExtras(Bundle extras) {
        mExtras = extras;
        return this;
    }

    /**
     * Retrieve the view that has been long pressed
     *
     * @return the registered view that was long pressed to show the QuickActionView
     */
    @Nullable
    public View getLongPressedView() {
        return mClickedView;
    }

    /**
     * Listener for when an action is selected (hovered, then released)
     */
    public interface OnActionSelectedListener {
        void onActionSelected(Action action, QuickActionView quickActionView);
    }

    /**
     * Listener for when an action has its hover state changed (hovering or stopped hovering)
     */
    public interface OnActionHoverChangedListener {
        void onActionHoverChanged(Action action, QuickActionView quickActionView, boolean hovering);
    }

    /**
     * Listen for when the QuickActionView is dismissed
     */
    public interface OnDismissListener {
        void onDismiss(QuickActionView quickActionView);
    }

    /**
     * Listener for when the QuickActionView is shown
     */
    public interface OnShowListener {
        void onShow(QuickActionView quickActionView);
    }

    protected static class Config {
        private Action.Config mDefaultConfig;
        private Typeface mTypeface;
        private int mTextSize;
        private int mTextPaddingTop;
        private int mTextPaddingBottom;
        private int mTextPaddingLeft;
        private int mTextPaddingRight;

        protected Config(Context context) {
            this(context, null, context.getResources().getInteger(R.integer.qav_action_title_view_text_size), context.getResources().getDimensionPixelSize(R.dimen.qav_action_title_view_text_padding));
        }

        private Config(Context context, Typeface typeface, int textSize, int textPadding) {
            mTypeface = typeface;
            mTextSize = textSize;
            mTextPaddingTop = textPadding;
            mTextPaddingBottom = textPadding;
            mTextPaddingLeft = textPadding;
            mTextPaddingRight = textPadding;
            mDefaultConfig = new Action.Config(context);
        }


        public Drawable getTextBackgroundDrawable(Context context) {
            return mDefaultConfig.getTextBackgroundDrawable(context);
        }

        public void setTextBackgroundDrawable(@DrawableRes int textBackgroundDrawable) {
            mDefaultConfig.setTextBackgroundDrawable(textBackgroundDrawable);
        }

        public int getTextColor() {
            return mDefaultConfig.getTextColor();
        }

        public void setTextColor(@ColorInt int textColor) {
            mDefaultConfig.setTextColor(textColor);
        }


        public ColorStateList getBackgroundColorStateList() {
            return mDefaultConfig.getBackgroundColorStateList();
        }

        public void setBackgroundColorStateList(ColorStateList backgroundColorStateList) {
            mDefaultConfig.setBackgroundColorStateList(backgroundColorStateList);
        }

        public void setBackgroundColor(@ColorInt int backgroundColor) {
            mDefaultConfig.setBackgroundColor(backgroundColor);
        }


        public Typeface getTypeface() {
            return mTypeface;
        }

        public void setTypeface(Typeface typeface) {
            mTypeface = typeface;
        }

        public int getTextSize() {
            return mTextSize;
        }

        public void setTextSize(int textSize) {
            mTextSize = textSize;
        }

        public int getTextPaddingTop() {
            return mTextPaddingTop;
        }

        public void setTextPaddingTop(int textPaddingTop) {
            mTextPaddingTop = textPaddingTop;
        }

        public int getTextPaddingBottom() {
            return mTextPaddingBottom;
        }

        public void setTextPaddingBottom(int textPaddingBottom) {
            mTextPaddingBottom = textPaddingBottom;
        }

        public int getTextPaddingLeft() {
            return mTextPaddingLeft;
        }

        public void setTextPaddingLeft(int textPaddingLeft) {
            mTextPaddingLeft = textPaddingLeft;
        }

        public int getTextPaddingRight() {
            return mTextPaddingRight;
        }

        public void setTextPaddingRight(int textPaddingRight) {
            mTextPaddingRight = textPaddingRight;
        }
    }

    /**
     * Parent layout that actually houses all of the quick action views
     */
    private class QuickActionViewLayout extends FrameLayout {

        private Point mCenterPoint;
        private View mIndicatorView;
        private View mScrimView;
        private LinkedHashMap<Action, ActionView> mActionViews = new LinkedHashMap<>();
        private LinkedHashMap<Action, ActionTitleView> mActionTitleViews = new LinkedHashMap<>();
        private PointF mLastTouch = new PointF();
        private boolean mAnimated = false;

        public QuickActionViewLayout(Context context, ArrayList<Action> actions, Point centerPoint) {
            super(context);
            mCenterPoint = centerPoint;
            mScrimView = new View(context);
            mScrimView.setBackgroundColor(mScrimColor);
            FrameLayout.LayoutParams scrimParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            addView(mScrimView, scrimParams);
            mIndicatorView = new View(context);
            if (Build.VERSION.SDK_INT >= 16) {
                mIndicatorView.setBackground(mIndicatorDrawable);
            } else {
                mIndicatorView.setBackgroundDrawable(mIndicatorDrawable);
            }
            FrameLayout.LayoutParams indicatorParams = new FrameLayout.LayoutParams(mIndicatorDrawable.getIntrinsicWidth(), mIndicatorDrawable.getIntrinsicHeight());
            addView(mIndicatorView, indicatorParams);
            for (Action action : actions) {
                ConfigHelper helper = new ConfigHelper(action.getConfig(), mConfig);
                ActionView actionView = new ActionView(context, action, helper);
                mActionViews.put(action, actionView);
                FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                addView(actionView, params);
                if (!TextUtils.isEmpty(action.getTitle())) {
                    ActionTitleView actionTitleView = new ActionTitleView(context, action, helper);
                    actionTitleView.setVisibility(View.GONE);
                    mActionTitleViews.put(action, actionTitleView);
                    FrameLayout.LayoutParams titleParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                    addView(actionTitleView, titleParams);
                }
            }
        }

        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            mScrimView.layout(0, 0, getMeasuredWidth(), getMeasuredHeight());

            mIndicatorView.layout(mCenterPoint.x - (int) (mIndicatorView.getMeasuredWidth() / 2.0),
                    mCenterPoint.y - (int) (mIndicatorView.getMeasuredHeight() / 2.0),
                    mCenterPoint.x + (int) (mIndicatorView.getMeasuredWidth() / 2.0),
                    mCenterPoint.y + (int) (mIndicatorView.getMeasuredHeight() / 2.0));
            int index = 0;
            for (Map.Entry<Action, ActionView> entry : mActionViews.entrySet()) {

                float startAngle = getOptimalStartAngle(entry.getValue().getActionCircleRadiusExpanded());
                ActionView actionView = entry.getValue();
                PointF point = getActionPoint(index, startAngle, actionView);
                point.offset(-actionView.getCircleCenterX(), -actionView.getCircleCenterY());
                actionView.layout((int) point.x, (int) point.y, (int) (point.x + actionView.getMeasuredWidth()), (int) (point.y + actionView.getMeasuredHeight()));
                ActionTitleView titleView = mActionTitleViews.get(entry.getKey());
                if (titleView != null) {
                    float titleLeft = point.x + (actionView.getMeasuredWidth() / 2) - (titleView.getMeasuredWidth() / 2);
                    float titleTop = point.y - 10 - titleView.getMeasuredHeight();
                    titleView.layout((int) titleLeft, (int) titleTop, (int) (titleLeft + titleView.getMeasuredWidth()), (int) (titleTop + titleView.getMeasuredHeight()));
                }
                index++;
            }

            if (!mAnimated) {
                animateActionsIn();
                animateIndicatorIn();
                animateScrimIn();
                mAnimated = true;
            }
        }

        private void animateActionsIn() {
            int index = 0;
            for (ActionView view : mActionViews.values()) {
                mActionsInAnimator.animateActionIn(view.getAction(), index, view, mCenterPoint);
                index++;
            }
        }

        private void animateIndicatorIn() {
            mActionsInAnimator.animateIndicatorIn(mIndicatorView);
        }

        private void animateScrimIn() {
            mActionsInAnimator.animateScrimIn(mScrimView);
        }

        int animateOut() {
            int maxDuration = 0;
            maxDuration = Math.max(maxDuration, animateActionsOut());
            maxDuration = Math.max(maxDuration, animateScrimOut());
            maxDuration = Math.max(maxDuration, animateIndicatorOut());
            maxDuration = Math.max(maxDuration, animateLabelsOut());
            return maxDuration;
        }

        private int animateActionsOut() {
            int index = 0;
            int maxDuration = 0;
            for (ActionView view : mActionViews.values()) {
                view.clearAnimation();
                maxDuration = Math.max(mActionsOutAnimator.animateActionOut(view.getAction(), index, view, mCenterPoint), maxDuration);
                index++;
            }
            return maxDuration;
        }

        private int animateLabelsOut() {
            for (ActionTitleView view : mActionTitleViews.values()) {
                view.animate().alpha(0).setDuration(100);
            }
            return 200;
        }

        private int animateIndicatorOut() {
            mIndicatorView.clearAnimation();
            return mActionsOutAnimator.animateIndicatorOut(mIndicatorView);
        }

        private int animateScrimOut() {
            mScrimView.clearAnimation();
            return mActionsOutAnimator.animateScrimOut(mScrimView);
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (mShown) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                    case MotionEvent.ACTION_MOVE:
                        mLastTouch.set(event.getRawX(), event.getRawY());
                        int index = 0;
                        for (ActionView actionView : mActionViews.values()) {
                            if (insideCircle(getActionPoint(index, getOptimalStartAngle(actionView.getActionCircleRadiusExpanded()), actionView), actionView.getActionCircleRadiusExpanded(), event.getRawX(), event.getRawY())) {
                                if (!actionView.isSelected()) {
                                    actionView.setSelected(true);
                                    actionView.animateInterpolation(1);
                                    ActionTitleView actionTitleView = mActionTitleViews.get(actionView.getAction());
                                    if (actionTitleView != null) {
                                        actionTitleView.setVisibility(View.VISIBLE);
                                        actionTitleView.bringToFront();
                                        mActionsTitleInAnimator.animateActionTitleIn(actionView.getAction(), index, actionTitleView);
                                    }
                                    if (mOnActionHoverChangedListener != null) {
                                        mOnActionHoverChangedListener.onActionHoverChanged(actionView.getAction(), QuickActionView.this, true);
                                    }
                                }
                            } else {
                                if (actionView.isSelected()) {
                                    actionView.setSelected(false);
                                    actionView.animateInterpolation(0);
                                    final ActionTitleView actionTitleView = mActionTitleViews.get(actionView.getAction());
                                    if (actionTitleView != null) {
                                        int timeTaken = mActionsTitleOutAnimator.animateActionTitleOut(actionView.getAction(), index, actionTitleView);
                                        actionTitleView.postDelayed(new Runnable() {
                                            @Override
                                            public void run() {
                                                actionTitleView.setVisibility(View.GONE);
                                            }
                                        }, timeTaken);
                                    }
                                    if (mOnActionHoverChangedListener != null) {
                                        mOnActionHoverChangedListener.onActionHoverChanged(actionView.getAction(), QuickActionView.this, false);
                                    }
                                }
                            }
                            index++;
                        }
                        invalidate();
                        break;
                    case MotionEvent.ACTION_UP:
                        for (Map.Entry<Action, ActionView> entry : mActionViews.entrySet()) {
                            if (entry.getValue().isSelected() && mOnActionSelectedListener != null) {
                                mOnActionSelectedListener.onActionSelected(entry.getKey(), QuickActionView.this);
                                break;
                            }
                        }
                    case MotionEvent.ACTION_CANCEL:
                    case MotionEvent.ACTION_OUTSIDE:

                        dismiss();
                }
            }
            return true;
        }


        private PointF getActionPoint(int index, float startAngle, ActionView view) {
            PointF point = new PointF(mCenterPoint);
            float angle = (float) (Math.toRadians(startAngle) + getActionOffsetAngle(index, view));
            point.offset((int) (Math.cos(angle) * getTotalRadius(view.getActionCircleRadiusExpanded())), (int) (Math.sin(angle) * getTotalRadius(view.getActionCircleRadiusExpanded())));
            return point;
        }

        private float getActionOffsetAngle(int index, ActionView view) {
            return (float) (index * (2 * (Math.atan2(view.getActionCircleRadiusExpanded() + mActionPadding, getTotalRadius(view.getActionCircleRadiusExpanded())))));
        }

        private float getMaxActionAngle() {
            int index = 0;
            float max = 0;
            for (ActionView actionView : mActionViews.values()) {
                max = getActionOffsetAngle(index, actionView);
                index++;
            }
            return max;
        }

        private float getTotalRadius(float actionViewRadiusExpanded) {
            return mActionDistance + Math.max(mIndicatorView.getWidth(), mIndicatorView.getHeight()) + actionViewRadiusExpanded;
        }

        private float getOptimalStartAngle(float actionViewRadiusExpanded) {
            if (getMeasuredWidth() > 0) {
                float radius = getTotalRadius(actionViewRadiusExpanded);

                int top = -mCenterPoint.y;
                boolean topIntersect = !Double.isNaN(Math.acos(top / radius));

                float horizontalOffset = (mCenterPoint.x - (getMeasuredWidth() / 2.0f)) / (getMeasuredWidth() / 2.0f);

                float angle;
                float offset = (float) Math.pow(Math.abs(horizontalOffset), 1.2) * Math.signum(horizontalOffset);
                if (topIntersect) {
                    angle = 90 + (90 * offset);
                } else {
                    angle = 270 - (90 * offset);
                }
                normalizeAngle(angle);

                return (float) (angle - Math.toDegrees(getMiddleAngleOffset()));
            }
            return (float) (270 - Math.toDegrees(getMiddleAngleOffset()));
        }


        public float normalizeAngle(double angleDegrees) {
            angleDegrees = angleDegrees % (360);
            angleDegrees = (angleDegrees + 360) % 360;
            return (float) angleDegrees;
        }

        private float getMiddleAngleOffset() {
            return getMaxActionAngle() / 2f;
        }

        private boolean insideCircle(PointF center, float radius, float x, float y) {
            return distance(center, x, y) < radius;
        }

        private float distance(PointF point, float x, float y) {
            return (float) Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2));
        }
    }

    /**
     * A class to combine a long click listener and a touch listener, to register views with
     */
    private class RegisteredListener implements View.OnLongClickListener, View.OnTouchListener {

        private float mTouchX;
        private float mTouchY;

        @Override
        public boolean onLongClick(View v) {
            show(v, new Point((int) mTouchX, (int) mTouchY));
            return false;
        }

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            mTouchX = event.getX();
            mTouchY = event.getY();
            if (mShown) {
                mQuickActionViewLayout.onTouchEvent(event);
            }
            return mShown;
        }
    }
}