package net.simonvt.widget;

import net.simonvt.menudrawer.R;

import android.app.Activity;
import android.os.Bundle;
import android.os.Parcelable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;

public class MenuDrawerManager {

    private static final String STATE_LAYOUT = "net.simonvt.widget.MenuDrawerManager.layoutState";

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

    /**
     * The MenuDrawer layout.
     */
    private MenuDrawer mMenuDrawer;

    /**
     * The container for the menu view.
     */
    private ViewGroup mMenuContainer;

    /**
     * The container for the content view.
     */
    private ViewGroup mContentContainer;

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

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

    /**
     * Indicates whether the menu view has been attached to the Activitys layout.
     */
    private boolean mMenuAttached;

    /**
     * The state passed to {@link #onRestoreDrawerState(Parcelable)}.
     */
    private Bundle mRestoredState;

    /**
     * Constructor to use when creating the menu drawer.
     *
     * @param activity The activity the menu drawer will be attached to.
     * @param dragMode The drag mode of the drawer. Can be either {@link MenuDrawer#MENU_DRAG_CONTENT}
     *                 or {@link MenuDrawer#MENU_DRAG_WINDOW}.
     */
    public MenuDrawerManager(Activity activity, int dragMode) {
        this(activity, dragMode, MenuDrawer.MENU_POSITION_LEFT);
    }

    /**
     * Constructor to use when creating the menu drawer.
     *
     * @param activity The activity the menu drawer will be attached to.
     * @param dragMode The drag mode of the drawer. Can be either {@link MenuDrawer#MENU_DRAG_CONTENT}
     *                 or {@link MenuDrawer#MENU_DRAG_WINDOW}.
     * @param gravity  Where to position the menu. Can be either {@link MenuDrawer#MENU_POSITION_LEFT} or
     *                 {@link MenuDrawer#MENU_POSITION_RIGHT}.
     */
    public MenuDrawerManager(Activity activity, int dragMode, int gravity) {
        mActivity = activity;
        mDragMode = dragMode;

        mMenuDrawer = gravity == MenuDrawer.MENU_POSITION_RIGHT ? new RightDrawer(activity) : new LeftDrawer(activity);
        mMenuDrawer.setDragMode(dragMode);
        mMenuDrawer.setId(R.id.md__layout);
        mMenuContainer = (ViewGroup) mMenuDrawer.findViewById(R.id.md__menu);
        mContentContainer = (ViewGroup) mMenuDrawer.findViewById(R.id.md__content);

        attachMenuLayout();
    }

    /**
     * Attaches the menu drawer to the activitys layout.
     */
    private void attachMenuLayout() {
        if (!mMenuAttached) {
            mMenuAttached = true;

            switch (mDragMode) {
                case MenuDrawer.MENU_DRAG_CONTENT:
                    attachToContent();
                    break;

                case MenuDrawer.MENU_DRAG_WINDOW:
                    attachToDecor();
                    break;

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

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

    /**
     * Attaches the menu drawer to the window.
     */
    private void attachToDecor() {
        ViewGroup decorView = (ViewGroup) mActivity.getWindow().getDecorView();
        ViewGroup decorChild = (ViewGroup) decorView.getChildAt(0);

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

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

    /**
     * Returns the MenuDrawer layout.
     *
     * @return The MenuDrawer layout.
     */
    public MenuDrawer getMenuDrawer() {
        return mMenuDrawer;
    }

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

    /**
     * Set the active view. If the mdArrowDrawable attribute is set, this View will have an arrow 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) {
        mMenuDrawer.setActiveView(v, position);
    }

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

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

    /**
     * Opens the menu.
     */
    public void openMenu() {
        mMenuDrawer.openMenu();
    }

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

    /**
     * Closes the menu.
     */
    public void closeMenu() {
        mMenuDrawer.closeMenu();
    }

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

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

    /**
     * Returns the state of the drawer. Can be one of {@link MenuDrawer#STATE_CLOSED}, {@link MenuDrawer#STATE_CLOSING},
     * {@link MenuDrawer#STATE_DRAGGING}, {@link MenuDrawer#STATE_OPENING} or {@link MenuDrawer#STATE_OPEN}.
     *
     * @return The drawers state.
     */
    public int getDrawerState() {
        return mMenuDrawer.getDrawerState();
    }

    /**
     * Set the menu view from a layout resource.
     *
     * @param layoutResId Resource ID to be inflated.
     */
    public void setMenuView(int layoutResId) {
        mMenuContainer.removeAllViews();
        mMenuView = mActivity.getLayoutInflater().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 inflater = mActivity.getLayoutInflater();
                inflater.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;
        }
    }

    /**
     * Returns the views current state.
     *
     * @return Returns a Parcelable object containing the MenuDrawer's current state.
     */
    public Parcelable onSaveDrawerState() {
        Bundle state = new Bundle();
        state.putParcelable(STATE_LAYOUT, mMenuDrawer.saveState());
        return state;
    }

    /**
     * Called to restore the MenuDrawer's state that has previously been generated with {@link #onSaveDrawerState()}.
     *
     * @param in The state that had previously been returned by {@link #onSaveDrawerState()}.
     */
    public void onRestoreDrawerState(Parcelable in) {
        mRestoredState = (Bundle) in;
        mMenuDrawer.restoreState(mRestoredState.getParcelable(STATE_LAYOUT));
    }
}
