package com.camel.redpenguin.view.slidingdrawer;

import com.camel.redpenguin.R;

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.ColorDrawable;
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;



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;

	/**
	 * 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;

	@SuppressWarnings("unused")
	private int mCurrentUpContentDesc;

	private int mDrawerOpenContentDesc;

	private int mDrawerClosedContentDesc;

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

	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.LEFT);
	}

	/**
	 * 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) {
				drawer.setupUpIndicator(activity);
			}

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

		drawer.mDragMode = dragMode;
		drawer.mPosition = 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);
	}

	@SuppressWarnings("deprecation")
	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);
		}

		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);
		mPosition = 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(0xFF000000);

		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) {
			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 (mPosition) {
		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 (mPosition) {
			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 (mPosition) {
		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 (mPosition) {
		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;
	}

	/**
	 * 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();
		mIndicatorAnimating = true;
		mIndicatorScroller.startScroll(0.0f, 1.0f, INDICATOR_ANIM_DURATION);

		animateIndicatorInvalidate();
	}

	/**
	 * Returns the start position of the indicator.
	 * 
	 * @return The start position of the indicator.
	 */
	private int getIndicatorStartPos() {
		switch (mPosition) {
		case TOP:
			return mIndicatorClipRect.left;
		case RIGHT:
			return mIndicatorClipRect.top;
		case BOTTOM:
			return mIndicatorClipRect.left;
		default:
			return mIndicatorClipRect.top;
		}
	}

	/**
	 * Compute the touch area based on the touch mode.
	 */
	protected void updateTouchAreaSize() {
		if (mTouchMode == TOUCH_MODE_BEZEL) {
			mTouchSize = mTouchBezelSize;
		} else if (mTouchMode == TOUCH_MODE_FULLSCREEN) {
			mTouchSize = getMeasuredWidth();
		} else {
			mTouchSize = 0;
		}
	}

	/**
	 * Callback when each frame in the indicator animation should be drawn.
	 */
	private void animateIndicatorInvalidate() {
		if (mIndicatorScroller.computeScrollOffset()) {
			mIndicatorOffset = mIndicatorScroller.getCurr();
			invalidate();

			if (!mIndicatorScroller.isFinished()) {
				postOnAnimation(mIndicatorRunnable);
				return;
			}
		}

		completeAnimatingIndicator();
	}

	/**
	 * Called when the indicator animation has completed.
	 */
	private void completeAnimatingIndicator() {
		mIndicatorOffset = 1.0f;
		mIndicatorAnimating = false;
		invalidate();
	}

	/**
	 * Enables or disables offsetting the menu when dragging the drawer.
	 * 
	 * @param offsetMenu
	 *            True to offset the menu, false otherwise.
	 */
	public abstract void setOffsetMenuEnabled(boolean offsetMenu);

	/**
	 * Indicates whether the menu is being offset when dragging the drawer.
	 * 
	 * @return True if the menu is being offset, false otherwise.
	 */
	public abstract boolean getOffsetMenuEnabled();

	/**
	 * Get the current state of the drawer.
	 * 
	 * @return The state of the drawer.
	 */
	public int getDrawerState() {
		return mDrawerState;
	}

	/**
	 * Register a callback to be invoked when the drawer state changes.
	 * 
	 * @param listener
	 *            The callback that will run.
	 */
	public void setOnDrawerStateChangeListener(OnDrawerStateChangeListener listener) {
		mOnDrawerStateChangeListener = listener;
	}

	/**
	 * Register a callback that will be invoked when the drawer is about to intercept touch events.
	 * 
	 * @param listener
	 *            The callback that will be invoked.
	 */
	public void setOnInterceptMoveEventListener(OnInterceptMoveEventListener listener) {
		mOnInterceptMoveEventListener = listener;
	}

	/**
	 * Defines whether the drop shadow is enabled.
	 * 
	 * @param enabled
	 *            Whether the drop shadow is enabled.
	 */
	public void setDropShadowEnabled(boolean enabled) {
		mDropShadowEnabled = enabled;
		invalidate();
	}

	protected GradientDrawable.Orientation getDropShadowOrientation() {
		// Gets the orientation for the static and sliding drawer. The overlay drawer provides its own
		// implementation.
		switch (mPosition) {
		case TOP:
			return GradientDrawable.Orientation.BOTTOM_TOP;

		case RIGHT:
			return GradientDrawable.Orientation.LEFT_RIGHT;

		case BOTTOM:
			return GradientDrawable.Orientation.TOP_BOTTOM;

		default:
			return GradientDrawable.Orientation.RIGHT_LEFT;
		}
	}

	/**
	 * Sets the color of the drop shadow.
	 * 
	 * @param color
	 *            The color of the drop shadow.
	 */
	public void setDropShadowColor(int color) {
		GradientDrawable.Orientation orientation = getDropShadowOrientation();

		final int endColor = color & 0x00FFFFFF;
		mDropShadowDrawable = new GradientDrawable(orientation, new int[] { color, endColor, });
		invalidate();
	}

	/**
	 * Sets the drawable of the drop shadow.
	 * 
	 * @param drawable
	 *            The drawable of the drop shadow.
	 */
	public void setDropShadow(Drawable drawable) {
		mDropShadowDrawable = drawable;
		invalidate();
	}

	/**
	 * Sets the drawable of the drop shadow.
	 * 
	 * @param resId
	 *            The resource identifier of the the drawable.
	 */
	public void setDropShadow(int resId) {
		setDropShadow(getResources().getDrawable(resId));
	}

	/**
	 * Returns the drawable of the drop shadow.
	 */
	public Drawable getDropShadow() {
		return mDropShadowDrawable;
	}

	/**
	 * Sets the size of the drop shadow.
	 * 
	 * @param size
	 *            The size of the drop shadow in px.
	 */
	public void setDropShadowSize(int size) {
		mDropShadowSize = size;
		invalidate();
	}

	/**
	 * Animates the drawer slightly open until the user opens the drawer.
	 */
	public abstract void peekDrawer();

	/**
	 * Animates the drawer slightly open. If delay is larger than 0, this happens until the user opens the
	 * drawer.
	 * 
	 * @param delay
	 *            The delay (in milliseconds) between each run of the animation. If 0, this animation is only
	 *            run once.
	 */
	public abstract void peekDrawer(long delay);

	/**
	 * Animates the drawer slightly open. If delay is larger than 0, this happens until the user opens the
	 * drawer.
	 * 
	 * @param startDelay
	 *            The delay (in milliseconds) until the animation is first run.
	 * @param delay
	 *            The delay (in milliseconds) between each run of the animation. If 0, this animation is only
	 *            run once.
	 */
	public abstract void peekDrawer(long startDelay, long delay);

	/**
	 * Enables or disables the user of {@link View#LAYER_TYPE_HARDWARE} when animations views.
	 * 
	 * @param enabled
	 *            Whether hardware layers are enabled.
	 */
	public abstract void setHardwareLayerEnabled(boolean enabled);

	/**
	 * Sets the maximum duration of open/close animations.
	 * 
	 * @param duration
	 *            The maximum duration in milliseconds.
	 */
	public void setMaxAnimationDuration(int duration) {
		mMaxAnimationDuration = duration;
	}

	/**
	 * Sets whether an overlay should be drawn when sliding the drawer.
	 * 
	 * @param drawOverlay
	 *            Whether an overlay should be drawn when sliding the drawer.
	 */
	public void setDrawOverlay(boolean drawOverlay) {
		mDrawOverlay = drawOverlay;
	}

	/**
	 * Gets whether an overlay is drawn when sliding the drawer.
	 * 
	 * @return Whether an overlay is drawn when sliding the drawer.
	 */
	public boolean getDrawOverlay() {
		return mDrawOverlay;
	}

	protected void updateUpContentDescription() {
		@SuppressWarnings("unused")
		final int upContentDesc = isMenuVisible() ? mDrawerOpenContentDesc : mDrawerClosedContentDesc;
		// if (mDrawerIndicatorEnabled && mActionBarHelper != null && upContentDesc != mCurrentUpContentDesc)
		// {
		// mCurrentUpContentDesc = upContentDesc;
		// mActionBarHelper.setActionBarDescription(upContentDesc);
		// }
	}

	/**
	 * Sets the drawable used as the drawer indicator.
	 * 
	 * @param drawable
	 *            The drawable used as the drawer indicator.
	 */
	public void setSlideDrawable(int drawableRes) {
		setSlideDrawable(getResources().getDrawable(drawableRes));
	}

	/**
	 * Sets the drawable used as the drawer indicator.
	 * 
	 * @param drawable
	 *            The drawable used as the drawer indicator.
	 */
	public void setSlideDrawable(Drawable drawable) {
		mSlideDrawable = new SlideDrawable(drawable);

		// if (mActionBarHelper != null) {
		// mActionBarHelper.setDisplayShowHomeAsUpEnabled(true);
		//
		// if (mDrawerIndicatorEnabled) {
		// mActionBarHelper.setActionBarUpIndicator(mSlideDrawable, isMenuVisible() ? mDrawerOpenContentDesc :
		// mDrawerClosedContentDesc);
		// }
		// }
	}

	/**
	 * Sets up the drawer indicator. It cna then be shown with {@link #setDrawerIndicatorEnabled(boolean)}.
	 * 
	 * @param activity
	 *            The activity the drawer is attached to.
	 */
	public void setupUpIndicator(Activity activity) {
		// if (mActionBarHelper == null) {
		// mActionBarHelper = new ActionBarHelper(activity);
		// mThemeUpIndicator = mActionBarHelper.getThemeUpIndicator();
		//
		// if (mDrawerIndicatorEnabled) {
		// mActionBarHelper.setActionBarUpIndicator(mSlideDrawable, isMenuVisible() ? mDrawerOpenContentDesc :
		// mDrawerClosedContentDesc);
		// }
		// }
	}

	/**
	 * Sets whether the drawer indicator should be enabled. {@link #setupUpIndicator(android.app.Activity)}
	 * must be called first.
	 * 
	 * @param enabled
	 *            Whether the drawer indicator should enabled.
	 */
	public void setDrawerIndicatorEnabled(boolean enabled) {
		// if (mActionBarHelper == null) {
		// throw new IllegalStateException("setupUpIndicator(Activity) has not been called");
		// }

		mDrawerIndicatorEnabled = enabled;
		// if (enabled) {
		// mActionBarHelper.setActionBarUpIndicator(mSlideDrawable, isMenuVisible() ? mDrawerOpenContentDesc :
		// mDrawerClosedContentDesc);
		// } else {
		// mActionBarHelper.setActionBarUpIndicator(mThemeUpIndicator, 0);
		// }
	}

	/**
	 * Indicates whether the drawer indicator is currently enabled.
	 * 
	 * @return Whether the drawer indicator is enabled.
	 */
	public boolean isDrawerIndicatorEnabled() {
		return mDrawerIndicatorEnabled;
	}

	/**
	 * Returns the ViewGroup used as a parent for the menu view.
	 * 
	 * @return The menu view's parent.
	 */
	public ViewGroup getMenuContainer() {
		return mMenuContainer;
	}

	/**
	 * Returns the ViewGroup used as a parent for the content view.
	 * 
	 * @return The content view's parent.
	 */
	public ViewGroup getContentContainer() {
		if (mDragMode == MENU_DRAG_CONTENT) {
			return mContentContainer;
		} else {
			return (ViewGroup) findViewById(android.R.id.content);
		}
	}

	/**
	 * Set the menu view from a layout resource.
	 * 
	 * @param layoutResId
	 *            Resource ID to be inflated.
	 */
	public void setMenuView(int layoutResId) {
		mMenuContainer.removeAllViews();
		mMenuView = LayoutInflater.from(getContext()).inflate(layoutResId, mMenuContainer, false);
		mMenuContainer.addView(mMenuView);
	}

	/**
	 * Set the menu view to an explicit view.
	 * 
	 * @param view
	 *            The menu view.
	 */
	public void setMenuView(View view) {
		setMenuView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
	}

	/**
	 * Set the menu view to an explicit view.
	 * 
	 * @param view
	 *            The menu view.
	 * @param params
	 *            Layout parameters for the view.
	 */
	public void setMenuView(View view, LayoutParams params) {
		mMenuView = view;
		mMenuContainer.removeAllViews();
		mMenuContainer.addView(view, params);
	}

	/**
	 * Returns the menu view.
	 * 
	 * @return The menu view.
	 */
	public View getMenuView() {
		return mMenuView;
	}

	/**
	 * Set the content from a layout resource.
	 * 
	 * @param layoutResId
	 *            Resource ID to be inflated.
	 */
	public void setContentView(int layoutResId) {
		switch (mDragMode) {
		case MenuDrawer.MENU_DRAG_CONTENT:
			mContentContainer.removeAllViews();
			LayoutInflater.from(getContext()).inflate(layoutResId, mContentContainer, true);
			break;

		case MenuDrawer.MENU_DRAG_WINDOW:
			mActivity.setContentView(layoutResId);
			break;
		}
	}

	/**
	 * Set the content to an explicit view.
	 * 
	 * @param view
	 *            The desired content to display.
	 */
	public void setContentView(View view) {
		setContentView(view, new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
	}

	/**
	 * Set the content to an explicit view.
	 * 
	 * @param view
	 *            The desired content to display.
	 * @param params
	 *            Layout parameters for the view.
	 */
	public void setContentView(View view, LayoutParams params) {
		switch (mDragMode) {
		case MenuDrawer.MENU_DRAG_CONTENT:
			mContentContainer.removeAllViews();
			mContentContainer.addView(view, params);
			break;

		case MenuDrawer.MENU_DRAG_WINDOW:
			mActivity.setContentView(view, params);
			break;
		}
	}

	protected void setDrawerState(int state) {
		if (state != mDrawerState) {
			final int oldState = mDrawerState;
			mDrawerState = state;
			if (mOnDrawerStateChangeListener != null)
				mOnDrawerStateChangeListener.onDrawerStateChange(oldState, state);
			if (DEBUG)
				logDrawerState(state);
		}
	}

	protected void logDrawerState(int state) {
		switch (state) {
		case STATE_CLOSED:
			Log.d(TAG, "[DrawerState] STATE_CLOSED");
			break;

		case STATE_CLOSING:
			Log.d(TAG, "[DrawerState] STATE_CLOSING");
			break;

		case STATE_DRAGGING:
			Log.d(TAG, "[DrawerState] STATE_DRAGGING");
			break;

		case STATE_OPENING:
			Log.d(TAG, "[DrawerState] STATE_OPENING");
			break;

		case STATE_OPEN:
			Log.d(TAG, "[DrawerState] STATE_OPEN");
			break;

		default:
			Log.d(TAG, "[DrawerState] Unknown: " + state);
		}
	}

	/**
	 * Returns the touch mode.
	 */
	public abstract int getTouchMode();

	/**
	 * Sets the drawer touch mode. Possible values are {@link #TOUCH_MODE_NONE}, {@link #TOUCH_MODE_BEZEL} or
	 * {@link #TOUCH_MODE_FULLSCREEN}.
	 * 
	 * @param mode
	 *            The touch mode.
	 */
	public abstract void setTouchMode(int mode);

	/**
	 * Sets the size of the touch bezel.
	 * 
	 * @param size
	 *            The touch bezel size in px.
	 */
	public abstract void setTouchBezelSize(int size);

	/**
	 * Returns the size of the touch bezel in px.
	 */
	public abstract int getTouchBezelSize();


	@SuppressLint("Override")
	public void postOnAnimation(Runnable action) {
//		 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
//		 super.postOnAnimation(action);
//		 } else {
		postDelayed(action, ANIMATION_DELAY);
		// }
	}

	@Override
	protected boolean fitSystemWindows(Rect insets) {
		if (mDragMode == MENU_DRAG_WINDOW) {
			mMenuContainer.setPadding(0, insets.top, 0, 0);
		}
		return super.fitSystemWindows(insets);
	}

	protected void dispatchOnDrawerSlide(float openRatio, int offsetPixels) {
		if (mOnDrawerStateChangeListener != null) {
			mOnDrawerStateChangeListener.onDrawerSlide(openRatio, offsetPixels);
		}
	}

	/**
	 * Saves the state of the drawer.
	 * 
	 * @return Returns a Parcelable containing the drawer state.
	 */
	public final Parcelable saveState() {
		if (mState == null)
			mState = new Bundle();
		saveState(mState);
		return mState;
	}

	void saveState(Bundle state) {
		// State saving isn't required for subclasses.
	}

	/**
	 * Restores the state of the drawer.
	 * 
	 * @param in
	 *            A parcelable containing the drawer state.
	 */
	public void restoreState(Parcelable in) {
		mState = (Bundle) in;
	}

	@Override
	protected Parcelable onSaveInstanceState() {
		Parcelable superState = super.onSaveInstanceState();
		SavedState state = new SavedState(superState);

		if (mState == null)
			mState = new Bundle();
		saveState(mState);

		state.mState = mState;
		return state;
	}

	@Override
	protected void onRestoreInstanceState(Parcelable state) {
		SavedState savedState = (SavedState) state;
		super.onRestoreInstanceState(savedState.getSuperState());

		restoreState(savedState.mState);
	}

	static class SavedState extends BaseSavedState {

		Bundle mState;

		public SavedState(Parcelable superState) {
			super(superState);
		}

		public SavedState(Parcel in) {
			super(in);
			mState = in.readBundle();
		}

		@Override
		public void writeToParcel(Parcel dest, int flags) {
			super.writeToParcel(dest, flags);
			dest.writeBundle(mState);
		}

		public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {

			@Override
			public SavedState createFromParcel(Parcel in) {
				return new SavedState(in);
			}

			@Override
			public SavedState[] newArray(int size) {
				return new SavedState[size];
			}
		};
	}
}
