package net.simonvt.menudrawer;

import net.simonvt.menudrawer.compat.ActionBarHelper;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;

import com.way.yahoo.R;

@SuppressLint("NewApi")
public abstract class MenuDrawer extends ViewGroup {

    /**
     * Callback interface for changing state of the drawer.
     */
    public interface OnDrawerStateChangeListener {

        /**
         * Called when the drawer state changes.
         *
         * @param oldState The old drawer state.
         * @param newState The new drawer state.
         */
        void onDrawerStateChange(int oldState, int newState);

        /**
         * Called when the drawer slides.
         *
         * @param openRatio    Ratio for how open the menu is.
         * @param offsetPixels Current offset of the menu in pixels.
         */
        void onDrawerSlide(float openRatio, int offsetPixels);
    }

    /**
     * Callback that is invoked when the drawer is in the process of deciding whether it should intercept the touch
     * event. This lets the listener decide if the pointer is on a view that would disallow dragging of the drawer.
     * This is only called when the touch mode is {@link #TOUCH_MODE_FULLSCREEN}.
     */
    public interface OnInterceptMoveEventListener {

        /**
         * Called for each child the pointer i on when the drawer is deciding whether to intercept the touch event.
         *
         * @param v     View to test for draggability
         * @param delta Delta drag in pixels
         * @param x     X coordinate of the active touch point
         * @param y     Y coordinate of the active touch point
         * @return true if view is draggable by delta dx.
         */
        boolean isViewDraggable(View v, int delta, int x, int y);
    }

    public enum Type {
        /**
         * Positions the drawer behind the content.
         */
        BEHIND,

        /**
         * A static drawer that can not be dragged.
         */
        STATIC,

        /**
         * Positions the drawer on top of the content.
         */
        OVERLAY,
    }

    /**
     * Tag used when logging.
     */
    private static final String TAG = "MenuDrawer";

    /**
     * Indicates whether debug code should be enabled.
     */
    private static final boolean DEBUG = false;

    /**
     * The time between each frame when animating the drawer.
     */
    protected static final int ANIMATION_DELAY = 1000 / 60;

    /**
     * The default touch bezel size of the drawer in dp.
     */
    private static final int DEFAULT_DRAG_BEZEL_DP = 24;

    /**
     * The default drop shadow size in dp.
     */
    private static final int DEFAULT_DROP_SHADOW_DP = 6;

    /**
     * Drag mode for sliding only the content view.
     */
    public static final int MENU_DRAG_CONTENT = 0;

    /**
     * Drag mode for sliding the entire window.
     */
    public static final int MENU_DRAG_WINDOW = 1;

    /**
     * Disallow opening the drawer by dragging the screen.
     */
    public static final int TOUCH_MODE_NONE = 0;

    /**
     * Allow opening drawer only by dragging on the edge of the screen.
     */
    public static final int TOUCH_MODE_BEZEL = 1;

    /**
     * Allow opening drawer by dragging anywhere on the screen.
     */
    public static final int TOUCH_MODE_FULLSCREEN = 2;

    /**
     * Indicates that the drawer is currently closed.
     */
    public static final int STATE_CLOSED = 0;

    /**
     * Indicates that the drawer is currently closing.
     */
    public static final int STATE_CLOSING = 1;

    /**
     * Indicates that the drawer is currently being dragged by the user.
     */
    public static final int STATE_DRAGGING = 2;

    /**
     * Indicates that the drawer is currently opening.
     */
    public static final int STATE_OPENING = 4;

    /**
     * Indicates that the drawer is currently open.
     */
    public static final int STATE_OPEN = 8;

    /**
     * Indicates whether to use {@link View#setTranslationX(float)} when positioning views.
     */
    static final boolean USE_TRANSLATIONS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;

    /**
     * Time to animate the indicator to the new active view.
     */
    static final int INDICATOR_ANIM_DURATION = 800;

    /**
     * The maximum animation duration.
     */
    private static final int DEFAULT_ANIMATION_DURATION = 600;

    /**
     * Interpolator used when animating the drawer open/closed.
     */
    protected static final Interpolator SMOOTH_INTERPOLATOR = new SmoothInterpolator();

    /**
     * Interpolator used for stretching/retracting the active indicator.
     */
    protected static final Interpolator INDICATOR_INTERPOLATOR = new AccelerateInterpolator();

    /**
     * Drawable used as menu overlay.
     */
    protected Drawable mMenuOverlay;

    /**
     * Defines whether the drop shadow is enabled.
     */
    protected boolean mDropShadowEnabled;

    /**
     * The color of the drop shadow.
     */
    protected int mDropShadowColor;

    /**
     * Drawable used as content drop shadow onto the menu.
     */
    protected Drawable mDropShadowDrawable;

    private boolean mCustomDropShadow;

    /**
     * The size of the content drop shadow.
     */
    protected int mDropShadowSize;

    /**
     * Bitmap used to indicate the active view.
     */
    protected Bitmap mActiveIndicator;

    /**
     * The currently active view.
     */
    protected View mActiveView;

    /**
     * Position of the active view. This is compared to View#getTag(R.id.mdActiveViewPosition) when drawing the
     * indicator.
     */
    protected int mActivePosition;

    /**
     * Whether the indicator should be animated between positions.
     */
    private boolean mAllowIndicatorAnimation;

    /**
     * Used when reading the position of the active view.
     */
    protected final Rect mActiveRect = new Rect();

    /**
     * Temporary {@link Rect} used for deciding whether the view should be invalidated so the indicator can be redrawn.
     */
    private final Rect mTempRect = new Rect();

    /**
     * The custom menu view set by the user.
     */
    private View mMenuView;

    /**
     * The parent of the menu view.
     */
    protected BuildLayerFrameLayout mMenuContainer;

    /**
     * The parent of the content view.
     */
    protected BuildLayerFrameLayout mContentContainer;

    /**
     * The size of the menu (width or height depending on the gravity).
     */
    protected int mMenuSize;

    /**
     * Indicates whether the menu is currently visible.
     */
    protected boolean mMenuVisible;

    /**
     * The drag mode of the drawer. Can be either {@link #MENU_DRAG_CONTENT} or {@link #MENU_DRAG_WINDOW}.
     */
    private int mDragMode = MENU_DRAG_CONTENT;

    /**
     * The current drawer state.
     *
     * @see #STATE_CLOSED
     * @see #STATE_CLOSING
     * @see #STATE_DRAGGING
     * @see #STATE_OPENING
     * @see #STATE_OPEN
     */
    protected int mDrawerState = STATE_CLOSED;

    /**
     * The touch bezel size of the drawer in px.
     */
    protected int mTouchBezelSize;

    /**
     * The touch area size of the drawer in px.
     */
    protected int mTouchSize;

    /**
     * Listener used to dispatch state change events.
     */
    private OnDrawerStateChangeListener mOnDrawerStateChangeListener;

    /**
     * Touch mode for the Drawer.
     * Possible values are {@link #TOUCH_MODE_NONE}, {@link #TOUCH_MODE_BEZEL} or {@link #TOUCH_MODE_FULLSCREEN}
     * Default: {@link #TOUCH_MODE_BEZEL}
     */
    protected int mTouchMode = TOUCH_MODE_BEZEL;

    /**
     * Indicates whether to use {@link View#LAYER_TYPE_HARDWARE} when animating the drawer.
     */
    protected boolean mHardwareLayersEnabled = true;

    /**
     * The Activity the drawer is attached to.
     */
    private Activity mActivity;

    /**
     * Scroller used when animating the indicator to a new position.
     */
    private FloatScroller mIndicatorScroller;

    /**
     * Runnable used when animating the indicator to a new position.
     */
    private Runnable mIndicatorRunnable = new Runnable() {
        @Override
        public void run() {
            animateIndicatorInvalidate();
        }
    };

    /**
     * The start position of the indicator when animating it to a new position.
     */
    protected int mIndicatorStartPos;

    /**
     * [0..1] value indicating the current progress of the animation.
     */
    protected float mIndicatorOffset;

    /**
     * Whether the indicator is currently animating.
     */
    protected boolean mIndicatorAnimating;

    /**
     * Bundle used to hold the drawers state.
     */
    protected Bundle mState;

    /**
     * The maximum duration of open/close animations.
     */
    protected int mMaxAnimationDuration = DEFAULT_ANIMATION_DURATION;

    /**
     * Callback that lets the listener override intercepting of touch events.
     */
    protected OnInterceptMoveEventListener mOnInterceptMoveEventListener;

    protected SlideDrawable mSlideDrawable;

    protected Drawable mThemeUpIndicator;

    protected boolean mDrawerIndicatorEnabled;

    private ActionBarHelper mActionBarHelper;

    private int mCurrentUpContentDesc;

    private int mDrawerOpenContentDesc;

    private int mDrawerClosedContentDesc;

    /**
     * The position of the drawer.
     */
    private Position mPosition;

    private Position mResolvedPosition;

    private final Rect mIndicatorClipRect = new Rect();

    protected boolean mIsStatic;

    protected final Rect mDropShadowRect = new Rect();

    /**
     * Current offset.
     */
    protected float mOffsetPixels;

    /**
     * Whether an overlay should be drawn as the drawer is opened and closed.
     */
    protected boolean mDrawOverlay;

    /**
     * Attaches the MenuDrawer to the Activity.
     *
     * @param activity The activity that the MenuDrawer will be attached to.
     * @return The created MenuDrawer instance.
     */
    public static MenuDrawer attach(Activity activity) {
        return attach(activity, Type.BEHIND);
    }

    /**
     * Attaches the MenuDrawer to the Activity.
     *
     * @param activity The activity the menu drawer will be attached to.
     * @param type     The {@link Type} of the drawer.
     * @return The created MenuDrawer instance.
     */
    public static MenuDrawer attach(Activity activity, Type type) {
        return attach(activity, type, Position.START);
    }

    /**
     * Attaches the MenuDrawer to the Activity.
     *
     * @param activity The activity the menu drawer will be attached to.
     * @param position Where to position the menu.
     * @return The created MenuDrawer instance.
     */
    public static MenuDrawer attach(Activity activity, Position position) {
        return attach(activity, Type.BEHIND, position);
    }

    /**
     * Attaches the MenuDrawer to the Activity.
     *
     * @param activity The activity the menu drawer will be attached to.
     * @param type     The {@link Type} of the drawer.
     * @param position Where to position the menu.
     * @return The created MenuDrawer instance.
     */
    public static MenuDrawer attach(Activity activity, Type type, Position position) {
        return attach(activity, type, position, MENU_DRAG_CONTENT);
    }

    /**
     * Attaches the MenuDrawer to the Activity.
     *
     * @param activity The activity the menu drawer will be attached to.
     * @param type     The {@link Type} of the drawer.
     * @param position Where to position the menu.
     * @param dragMode The drag mode of the drawer. Can be either {@link MenuDrawer#MENU_DRAG_CONTENT}
     *                 or {@link MenuDrawer#MENU_DRAG_WINDOW}.
     * @return The created MenuDrawer instance.
     */
    public static MenuDrawer attach(Activity activity, Type type, Position position, int dragMode) {
        MenuDrawer menuDrawer = createMenuDrawer(activity, dragMode, position, type);
        menuDrawer.setId(R.id.md__drawer);

        switch (dragMode) {
            case MenuDrawer.MENU_DRAG_CONTENT:
                attachToContent(activity, menuDrawer);
                break;

            case MenuDrawer.MENU_DRAG_WINDOW:
                attachToDecor(activity, menuDrawer);
                break;

            default:
                throw new RuntimeException("Unknown menu mode: " + dragMode);
        }

        return menuDrawer;
    }

    /**
     * Constructs the appropriate MenuDrawer based on the position.
     */
    private static MenuDrawer createMenuDrawer(Activity activity, int dragMode, Position position, Type type) {
        MenuDrawer drawer;

        if (type == Type.STATIC) {
            drawer = new StaticDrawer(activity);

        } else if (type == Type.OVERLAY) {
            drawer = new OverlayDrawer(activity, dragMode);
            if (position == Position.LEFT || position == Position.START) {
                drawer.setupUpIndicator(activity);
            }

        } else {
            drawer = new SlidingDrawer(activity, dragMode);
            if (position == Position.LEFT || position == Position.START) {
                drawer.setupUpIndicator(activity);
            }
        }

        drawer.mDragMode = dragMode;
        drawer.setPosition(position);

        return drawer;
    }

    /**
     * Attaches the menu drawer to the content view.
     */
    private static void attachToContent(Activity activity, MenuDrawer menuDrawer) {
        /**
         * Do not call mActivity#setContentView.
         * E.g. if using with a ListActivity, Activity#setContentView is overridden and dispatched to
         * MenuDrawer#setContentView, which then again would call Activity#setContentView.
         */
        ViewGroup content = (ViewGroup) activity.findViewById(android.R.id.content);
        content.removeAllViews();
        content.addView(menuDrawer, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    /**
     * Attaches the menu drawer to the window.
     */
    private static void attachToDecor(Activity activity, MenuDrawer menuDrawer) {
        ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
        ViewGroup decorChild = (ViewGroup) decorView.getChildAt(0);

        decorView.removeAllViews();
        decorView.addView(menuDrawer, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);

        menuDrawer.mContentContainer.addView(decorChild, decorChild.getLayoutParams());
    }

    MenuDrawer(Activity activity, int dragMode) {
        this(activity);

        mActivity = activity;
        mDragMode = dragMode;
    }

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

    public MenuDrawer(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.menuDrawerStyle);
    }

    public MenuDrawer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initDrawer(context, attrs, defStyle);
    }

    protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
        setWillNotDraw(false);
        setFocusable(false);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MenuDrawer, R.attr.menuDrawerStyle,
                R.style.Widget_MenuDrawer);

        final Drawable contentBackground = a.getDrawable(R.styleable.MenuDrawer_mdContentBackground);
        final Drawable menuBackground = a.getDrawable(R.styleable.MenuDrawer_mdMenuBackground);

        mMenuSize = a.getDimensionPixelSize(R.styleable.MenuDrawer_mdMenuSize, dpToPx(240));

        final int indicatorResId = a.getResourceId(R.styleable.MenuDrawer_mdActiveIndicator, 0);
        if (indicatorResId != 0) {
            mActiveIndicator = BitmapFactory.decodeResource(getResources(), indicatorResId);
        }

        mDropShadowEnabled = a.getBoolean(R.styleable.MenuDrawer_mdDropShadowEnabled, true);

        mDropShadowDrawable = a.getDrawable(R.styleable.MenuDrawer_mdDropShadow);

        if (mDropShadowDrawable == null) {
            mDropShadowColor = a.getColor(R.styleable.MenuDrawer_mdDropShadowColor, 0xFF000000);
        } else {
            mCustomDropShadow = true;
        }

        mDropShadowSize = a.getDimensionPixelSize(R.styleable.MenuDrawer_mdDropShadowSize,
                dpToPx(DEFAULT_DROP_SHADOW_DP));

        mTouchBezelSize = a.getDimensionPixelSize(R.styleable.MenuDrawer_mdTouchBezelSize,
                dpToPx(DEFAULT_DRAG_BEZEL_DP));

        mAllowIndicatorAnimation = a.getBoolean(R.styleable.MenuDrawer_mdAllowIndicatorAnimation, false);

        mMaxAnimationDuration = a.getInt(R.styleable.MenuDrawer_mdMaxAnimationDuration, DEFAULT_ANIMATION_DURATION);

        final int slideDrawableResId = a.getResourceId(R.styleable.MenuDrawer_mdSlideDrawable, -1);
        if (slideDrawableResId != -1) {
            setSlideDrawable(slideDrawableResId);
        }

        mDrawerOpenContentDesc = a.getResourceId(R.styleable.MenuDrawer_mdDrawerOpenUpContentDescription, 0);
        mDrawerClosedContentDesc = a.getResourceId(R.styleable.MenuDrawer_mdDrawerClosedUpContentDescription, 0);

        mDrawOverlay = a.getBoolean(R.styleable.MenuDrawer_mdDrawOverlay, true);

        final int position = a.getInt(R.styleable.MenuDrawer_mdPosition, 0);
        setPosition(Position.fromValue(position));

        a.recycle();

        mMenuContainer = new NoClickThroughFrameLayout(context);
        mMenuContainer.setId(R.id.md__menu);
        mMenuContainer.setBackgroundDrawable(menuBackground);

        mContentContainer = new NoClickThroughFrameLayout(context);
        mContentContainer.setId(R.id.md__content);
        mContentContainer.setBackgroundDrawable(contentBackground);

        mMenuOverlay = new ColorDrawable(0x00000000);//fix by liweiping 20140930

        mIndicatorScroller = new FloatScroller(SMOOTH_INTERPOLATOR);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        View menu = findViewById(R.id.mdMenu);
        if (menu != null) {
            removeView(menu);
            setMenuView(menu);
        }

        View content = findViewById(R.id.mdContent);
        if (content != null) {
            removeView(content);
            setContentView(content);
        }

        if (getChildCount() > 2) {
            throw new IllegalStateException(
                    "Menu and content view added in xml must have id's @id/mdMenu and @id/mdContent");
        }
    }

    protected int dpToPx(int dp) {
        return (int) (getResources().getDisplayMetrics().density * dp + 0.5f);
    }

    protected boolean isViewDescendant(View v) {
        ViewParent parent = v.getParent();
        while (parent != null) {
            if (parent == this) {
                return true;
            }

            parent = parent.getParent();
        }

        return false;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        getViewTreeObserver().addOnScrollChangedListener(mScrollListener);
    }

    @Override
    protected void onDetachedFromWindow() {
        getViewTreeObserver().removeOnScrollChangedListener(mScrollListener);
        super.onDetachedFromWindow();
    }

    private boolean shouldDrawIndicator() {
        return mActiveView != null && mActiveIndicator != null && isViewDescendant(mActiveView);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        final int offsetPixels = (int) mOffsetPixels;

        if (mDrawOverlay && offsetPixels != 0) {
            drawOverlay(canvas);
        }
        if (mDropShadowEnabled && (offsetPixels != 0 || mIsStatic)) {
            drawDropShadow(canvas);
        }
        if (shouldDrawIndicator() && (offsetPixels != 0 || mIsStatic)) {
            drawIndicator(canvas);
        }
    }

    protected abstract void drawOverlay(Canvas canvas);

    private void drawDropShadow(Canvas canvas) {
        // Can't pass the position to the constructor, so wait with loading the drawable until the drop shadow is
        // actually drawn.
        if (mDropShadowDrawable == null) {
            setDropShadowColor(mDropShadowColor);
        }

        updateDropShadowRect();
        mDropShadowDrawable.setBounds(mDropShadowRect);
        mDropShadowDrawable.draw(canvas);
    }

    protected void updateDropShadowRect() {
        // This updates the rect for the static and sliding drawer. The overlay drawer has its own implementation.
        switch (getPosition()) {
            case LEFT:
                mDropShadowRect.top = 0;
                mDropShadowRect.bottom = getHeight();
                mDropShadowRect.right = ViewHelper.getLeft(mContentContainer);
                mDropShadowRect.left = mDropShadowRect.right - mDropShadowSize;
                break;

            case TOP:
                mDropShadowRect.left = 0;
                mDropShadowRect.right = getWidth();
                mDropShadowRect.bottom = ViewHelper.getTop(mContentContainer);
                mDropShadowRect.top = mDropShadowRect.bottom - mDropShadowSize;
                break;

            case RIGHT:
                mDropShadowRect.top = 0;
                mDropShadowRect.bottom = getHeight();
                mDropShadowRect.left = ViewHelper.getRight(mContentContainer);
                mDropShadowRect.right = mDropShadowRect.left + mDropShadowSize;
                break;

            case BOTTOM:
                mDropShadowRect.left = 0;
                mDropShadowRect.right = getWidth();
                mDropShadowRect.top = ViewHelper.getBottom(mContentContainer);
                mDropShadowRect.bottom = mDropShadowRect.top + mDropShadowSize;
                break;
        }
    }

    private void drawIndicator(Canvas canvas) {
        Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition);
        final int pos = position == null ? 0 : position;
        if (pos == mActivePosition) {
            updateIndicatorClipRect();
            canvas.save();
            canvas.clipRect(mIndicatorClipRect);

            int drawLeft = 0;
            int drawTop = 0;
            switch (getPosition()) {
                case LEFT:
                case TOP:
                    drawLeft = mIndicatorClipRect.left;
                    drawTop = mIndicatorClipRect.top;
                    break;

                case RIGHT:
                    drawLeft = mIndicatorClipRect.right - mActiveIndicator.getWidth();
                    drawTop = mIndicatorClipRect.top;
                    break;

                case BOTTOM:
                    drawLeft = mIndicatorClipRect.left;
                    drawTop = mIndicatorClipRect.bottom - mActiveIndicator.getHeight();
            }

            canvas.drawBitmap(mActiveIndicator, drawLeft, drawTop, null);
            canvas.restore();
        }
    }

    /**
     * Update the {@link Rect} where the indicator is drawn.
     */
    protected void updateIndicatorClipRect() {
        mActiveView.getDrawingRect(mActiveRect);
        offsetDescendantRectToMyCoords(mActiveView, mActiveRect);

        final float openRatio = mIsStatic ? 1.0f : Math.abs(mOffsetPixels) / mMenuSize;

        final float interpolatedRatio = 1.f - INDICATOR_INTERPOLATOR.getInterpolation((1.f - openRatio));

        final int indicatorWidth = mActiveIndicator.getWidth();
        final int indicatorHeight = mActiveIndicator.getHeight();

        final int interpolatedWidth = (int) (indicatorWidth * interpolatedRatio);
        final int interpolatedHeight = (int) (indicatorHeight * interpolatedRatio);

        final int startPos = mIndicatorStartPos;

        int left = 0;
        int top = 0;
        int right = 0;
        int bottom = 0;

        switch (getPosition()) {
            case LEFT:
            case RIGHT:
                final int finalTop = mActiveRect.top + ((mActiveRect.height() - indicatorHeight) / 2);
                if (mIndicatorAnimating) {
                    top = (int) (startPos + ((finalTop - startPos) * mIndicatorOffset));
                } else {
                    top = finalTop;
                }
                bottom = top + indicatorHeight;
                break;

            case TOP:
            case BOTTOM:
                final int finalLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2);
                if (mIndicatorAnimating) {
                    left = (int) (startPos + ((finalLeft - startPos) * mIndicatorOffset));
                } else {
                    left = finalLeft;
                }
                right = left + indicatorWidth;
                break;
        }

        switch (getPosition()) {
            case LEFT: {
                right = ViewHelper.getLeft(mContentContainer);
                left = right - interpolatedWidth;
                break;
            }

            case TOP: {
                bottom = ViewHelper.getTop(mContentContainer);
                top = bottom - interpolatedHeight;
                break;
            }

            case RIGHT: {
                left = ViewHelper.getRight(mContentContainer);
                right = left + interpolatedWidth;
                break;
            }

            case BOTTOM: {
                top = ViewHelper.getBottom(mContentContainer);
                bottom = top + interpolatedHeight;
                break;
            }
        }

        mIndicatorClipRect.left = left;
        mIndicatorClipRect.top = top;
        mIndicatorClipRect.right = right;
        mIndicatorClipRect.bottom = bottom;
    }

    private void setPosition(Position position) {
        mPosition = position;
        mResolvedPosition = getPosition();
    }

    protected Position getPosition() {
        final int layoutDirection = ViewHelper.getLayoutDirection(this);

        switch (mPosition) {
            case START:
                if (layoutDirection == LAYOUT_DIRECTION_RTL) {
                    return Position.RIGHT;
                } else {
                    return Position.LEFT;
                }

            case END:
                if (layoutDirection == LAYOUT_DIRECTION_RTL) {
                    return Position.LEFT;
                } else {
                    return Position.RIGHT;
                }
        }

        return mPosition;
    }

    @Override
    public void onRtlPropertiesChanged(int layoutDirection) {
        super.onRtlPropertiesChanged(layoutDirection);

        if (!mCustomDropShadow) setDropShadowColor(mDropShadowColor);

        if (getPosition() != mResolvedPosition) {
            mResolvedPosition = getPosition();
            setOffsetPixels(mOffsetPixels * -1);
        }

        if (mSlideDrawable != null) mSlideDrawable.setIsRtl(layoutDirection == LAYOUT_DIRECTION_RTL);

        requestLayout();
        invalidate();
    }

    /**
     * Sets the number of pixels the content should be offset.
     *
     * @param offsetPixels The number of pixels to offset the content by.
     */
    protected void setOffsetPixels(float offsetPixels) {
        final int oldOffset = (int) mOffsetPixels;
        final int newOffset = (int) offsetPixels;

        mOffsetPixels = offsetPixels;

        if (mSlideDrawable != null) {
            final float offset = Math.abs(mOffsetPixels) / mMenuSize;
            mSlideDrawable.setOffset(offset);
            updateUpContentDescription();
        }

        if (newOffset != oldOffset) {
            onOffsetPixelsChanged(newOffset);
            mMenuVisible = newOffset != 0;

            // Notify any attached listeners of the current open ratio
            final float openRatio = ((float) Math.abs(newOffset)) / mMenuSize;
            dispatchOnDrawerSlide(openRatio, newOffset);
        }
    }

    /**
     * Called when the number of pixels the content should be offset by has changed.
     *
     * @param offsetPixels The number of pixels to offset the content by.
     */
    protected abstract void onOffsetPixelsChanged(int offsetPixels);

    /**
     * Toggles the menu open and close with animation.
     */
    public void toggleMenu() {
        toggleMenu(true);
    }

    /**
     * Toggles the menu open and close.
     *
     * @param animate Whether open/close should be animated.
     */
    public abstract void toggleMenu(boolean animate);

    /**
     * Animates the menu open.
     */
    public void openMenu() {
        openMenu(true);
    }

    /**
     * Opens the menu.
     *
     * @param animate Whether open/close should be animated.
     */
    public abstract void openMenu(boolean animate);

    /**
     * Animates the menu closed.
     */
    public void closeMenu() {
        closeMenu(true);
    }

    /**
     * Closes the menu.
     *
     * @param animate Whether open/close should be animated.
     */
    public abstract void closeMenu(boolean animate);

    /**
     * Indicates whether the menu is currently visible.
     *
     * @return True if the menu is open, false otherwise.
     */
    public abstract boolean isMenuVisible();

    /**
     * Set the size of the menu drawer when open.
     *
     * @param size The size of the menu.
     */
    public abstract void setMenuSize(int size);

    /**
     * Returns the size of the menu.
     *
     * @return The size of the menu.
     */
    public int getMenuSize() {
        return mMenuSize;
    }

    /**
     * Set the active view.
     * If the mdActiveIndicator attribute is set, this View will have the indicator drawn next to it.
     *
     * @param v The active view.
     */
    public void setActiveView(View v) {
        setActiveView(v, 0);
    }

    /**
     * Set the active view.
     * If the mdActiveIndicator attribute is set, this View will have the indicator drawn next to it.
     *
     * @param v        The active view.
     * @param position Optional position, usually used with ListView. v.setTag(R.id.mdActiveViewPosition, position)
     *                 must be called first.
     */
    public void setActiveView(View v, int position) {
        final View oldView = mActiveView;
        mActiveView = v;
        mActivePosition = position;

        if (mAllowIndicatorAnimation && oldView != null) {
            startAnimatingIndicator();
        }

        invalidate();
    }

    /**
     * Sets whether the indicator should be animated between active views.
     *
     * @param animate Whether the indicator should be animated between active views.
     */
    public void setAllowIndicatorAnimation(boolean animate) {
        if (animate != mAllowIndicatorAnimation) {
            mAllowIndicatorAnimation = animate;
            completeAnimatingIndicator();
        }
    }

    /**
     * Indicates whether the indicator should be animated between active views.
     *
     * @return Whether the indicator should be animated between active views.
     */
    public boolean getAllowIndicatorAnimation() {
        return mAllowIndicatorAnimation;
    }

    /**
     * Scroll listener that checks whether the active view has moved before the drawer is invalidated.
     */
    private ViewTreeObserver.OnScrollChangedListener mScrollListener = new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            if (mActiveView != null && isViewDescendant(mActiveView)) {
                mActiveView.getDrawingRect(mTempRect);
                offsetDescendantRectToMyCoords(mActiveView, mTempRect);
                if (mTempRect.left != mActiveRect.left || mTempRect.top != mActiveRect.top
                        || mTempRect.right != mActiveRect.right || mTempRect.bottom != mActiveRect.bottom) {
                    invalidate();
                }
            }
        }
    };

    /**
     * Starts animating the indicator to a new position.
     */
    private void startAnimatingIndicator() {
        mIndicatorStartPos = getIndicatorStartPos();
