/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.gecko.menu;

import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
import org.mozilla.gecko.widget.GeckoActionProvider;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class GeckoMenu extends ListView
                       implements Menu,
                                  AdapterView.OnItemClickListener,
                                  GeckoMenuItem.OnShowAsActionChangedListener {
    private static final String LOGTAG = "GeckoMenu";

    /**
     * Controls whether off-UI-thread method calls in this class cause an
     * exception or just logging.
     */
    private static final AssertBehavior THREAD_ASSERT_BEHAVIOR = AppConstants.RELEASE_OR_BETA ? AssertBehavior.NONE : AssertBehavior.THROW;

    /*
     * A callback for a menu item click/long click event.
     */
    public static interface Callback {
        // Called when a menu item is clicked, with the actual menu item as the argument.
        public boolean onMenuItemClick(MenuItem item);

        // Called when a menu item is long-clicked, with the actual menu item as the argument.
        public boolean onMenuItemLongClick(MenuItem item);
    }

    /*
     * An interface for a presenter to show the menu.
     * Either an Activity or a View can be a presenter, that can watch for events
     * and show/hide menu.
     */
    public static interface MenuPresenter {
        // Open the menu.
        public void openMenu();

        // Show the actual view containing the menu items. This can either be a parent or sub-menu.
        public void showMenu(View menu);

        // Close the menu.
        public void closeMenu();
    }

    /*
     * An interface for a presenter of action-items.
     * Either an Activity or a View can be a presenter, that can watch for events
     * and add/remove action-items. If not ActionItemBarPresenter, the menu uses a
     * DefaultActionItemBar, that shows the action-items as a header over list-view.
     */
    public static interface ActionItemBarPresenter {
        // Add an action-item.
        public boolean addActionItem(View actionItem);

        // Remove an action-item.
        public void removeActionItem(View actionItem);
    }

    protected static final int NO_ID = 0;

    // List of all menu items.
    private final List<GeckoMenuItem> mItems;

    // Quick lookup array used to make a fast path in findItem.
    private final SparseArray<MenuItem> mItemsById;

    // Map of "always" action-items in action-bar and their views.
    private final Map<GeckoMenuItem, View> mPrimaryActionItems;

    // Map of "ifRoom" action-items in action-bar and their views.
    private final Map<GeckoMenuItem, View> mSecondaryActionItems;

    // Map of "collapseActionView" action-items in action-bar and their views.
    private final Map<GeckoMenuItem, View> mQuickShareActionItems;

    // Reference to a callback for menu events.
    private Callback mCallback;

    // Reference to menu presenter.
    private MenuPresenter mMenuPresenter;

    // Reference to "always" action-items bar in action-bar.
    private ActionItemBarPresenter mPrimaryActionItemBar;

    // Reference to "ifRoom" action-items bar in action-bar.
    private final ActionItemBarPresenter mSecondaryActionItemBar;

    // Reference to "collapseActionView" action-items bar in action-bar.
    private final ActionItemBarPresenter mQuickShareActionItemBar;

    // Adapter to hold the list of menu items.
    private final MenuItemsAdapter mAdapter;

    // Show/hide icons in the list.
    boolean mShowIcons;

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

    public GeckoMenu(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.geckoMenuListViewStyle);
    }

    public GeckoMenu(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
                                         LayoutParams.MATCH_PARENT));

        // Attach an adapter.
        mAdapter = new MenuItemsAdapter();
        setAdapter(mAdapter);
        setOnItemClickListener(this);

        mItems = new ArrayList<GeckoMenuItem>();
        mItemsById = new SparseArray<MenuItem>();
        mPrimaryActionItems = new HashMap<GeckoMenuItem, View>();
        mSecondaryActionItems = new HashMap<GeckoMenuItem, View>();
        mQuickShareActionItems = new HashMap<GeckoMenuItem, View>();

        mPrimaryActionItemBar = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_action_bar, null);
        mSecondaryActionItemBar = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_secondary_action_bar, null);
        mQuickShareActionItemBar = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_secondary_action_bar, null);
    }

    private static void assertOnUiThread() {
        ThreadUtils.assertOnUiThread(THREAD_ASSERT_BEHAVIOR);
    }

    @Override
    public MenuItem add(CharSequence title) {
        GeckoMenuItem menuItem = new GeckoMenuItem(this, NO_ID, 0, title);
        addItem(menuItem);
        return menuItem;
    }

    @Override
    public MenuItem add(int groupId, int itemId, int order, int titleRes) {
        GeckoMenuItem menuItem = new GeckoMenuItem(this, itemId, order, titleRes);
        addItem(menuItem);
        return menuItem;
    }

    @Override
    public MenuItem add(int titleRes) {
        GeckoMenuItem menuItem = new GeckoMenuItem(this, NO_ID, 0, titleRes);
        addItem(menuItem);
        return menuItem;
    }

    @Override
    public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
        GeckoMenuItem menuItem = new GeckoMenuItem(this, itemId, order, title);
        addItem(menuItem);
        return menuItem;
    }

    private void addItem(GeckoMenuItem menuItem) {
        assertOnUiThread();
        menuItem.setOnShowAsActionChangedListener(this);
        mAdapter.addMenuItem(menuItem);
        mItems.add(menuItem);
    }

    private boolean addActionItem(final GeckoMenuItem menuItem) {
        assertOnUiThread();
        menuItem.setOnShowAsActionChangedListener(this);

        final View actionView = menuItem.getActionView();
        final int actionEnum = menuItem.getActionEnum();
        boolean added = false;

        if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_ALWAYS) {
            if (mPrimaryActionItems.size() == 0 &&
                mPrimaryActionItemBar instanceof DefaultActionItemBar) {
                // Reset the adapter before adding the header view to a list.
                setAdapter(null);
                addHeaderView((DefaultActionItemBar) mPrimaryActionItemBar);
                setAdapter(mAdapter);
            }

            if (added = mPrimaryActionItemBar.addActionItem(actionView)) {
                mPrimaryActionItems.put(menuItem, actionView);
                mItems.add(menuItem);
            }
        } else if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_IF_ROOM) {
            if (mSecondaryActionItems.size() == 0) {
                // Reset the adapter before adding the header view to a list.
                setAdapter(null);
                addHeaderView((DefaultActionItemBar) mSecondaryActionItemBar);
                setAdapter(mAdapter);
            }

            if (added = mSecondaryActionItemBar.addActionItem(actionView)) {
                mSecondaryActionItems.put(menuItem, actionView);
                mItems.add(menuItem);
            }
        } else if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) {
            if (actionView instanceof MenuItemSwitcherLayout) {
                final MenuItemSwitcherLayout quickShareView = (MenuItemSwitcherLayout) actionView;

                // We don't want to add the quick share bar if we don't have any quick share items.
                if (quickShareView.getActionButtonCount() > 0 &&
                        (added = mQuickShareActionItemBar.addActionItem(quickShareView))) {
                    if (mQuickShareActionItems.size() == 0) {
                        // Reset the adapter before adding the header view to a list.
                        setAdapter(null);
                        addHeaderView((DefaultActionItemBar) mQuickShareActionItemBar);
                        setAdapter(mAdapter);
                    }

                    mQuickShareActionItems.put(menuItem, quickShareView);
                    mItems.add(menuItem);
                }
            }
        }

        // Set the listeners.
        if (actionView instanceof MenuItemActionBar) {
            ((MenuItemActionBar) actionView).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    handleMenuItemClick(menuItem);
                }
            });
            ((MenuItemActionBar) actionView).setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View view) {
                    if (handleMenuItemLongClick(menuItem)) {
                        GeckoAppShell.getHapticFeedbackDelegate().performHapticFeedback(
                                HapticFeedbackConstants.LONG_PRESS);
                        return true;
                    }
                    return false;
                }
            });
        } else if (actionView instanceof MenuItemSwitcherLayout) {
            ((MenuItemSwitcherLayout) actionView).setMenuItemClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    handleMenuItemClick(menuItem);
                }
            });
            ((MenuItemSwitcherLayout) actionView).setMenuItemLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View view) {
                    if (handleMenuItemLongClick(menuItem)) {
                        GeckoAppShell.getHapticFeedbackDelegate().performHapticFeedback(
                                HapticFeedbackConstants.LONG_PRESS);
                        return true;
                    }
                    return false;
                }
            });
        }

        return added;
    }

    @Override
    public int addIntentOptions(int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
        return 0;
    }

    @Override
    public SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) {
        MenuItem menuItem = add(groupId, itemId, order, title);
        return addSubMenu(menuItem);
    }

    @Override
    public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) {
        MenuItem menuItem = add(groupId, itemId, order, titleRes);
        return addSubMenu(menuItem);
    }

    @Override
    public SubMenu addSubMenu(CharSequence title) {
        MenuItem menuItem = add(title);
        return addSubMenu(menuItem);
    }

    @Override
    public SubMenu addSubMenu(int titleRes) {
        MenuItem menuItem = add(titleRes);
        return addSubMenu(menuItem);
    }

    private SubMenu addSubMenu(MenuItem menuItem) {
        GeckoSubMenu subMenu = new GeckoSubMenu(getContext());
        subMenu.setMenuItem(menuItem);
        subMenu.setCallback(mCallback);
        subMenu.setMenuPresenter(mMenuPresenter);
        ((GeckoMenuItem) menuItem).setSubMenu(subMenu);
        return subMenu;
    }

    private void removePrimaryActionBarView() {
        // Reset the adapter before removing the header view from a list.
        setAdapter(null);
        removeHeaderView((DefaultActionItemBar) mPrimaryActionItemBar);
        setAdapter(mAdapter);
    }

    private void removeSecondaryActionBarView() {
        // Reset the adapter before removing the header view from a list.
        setAdapter(null);
        removeHeaderView((DefaultActionItemBar) mSecondaryActionItemBar);
        setAdapter(mAdapter);
    }

    private void removeQuickShareActionBarView() {
        // Reset the adapter before removing the header view from a list.
        setAdapter(null);
        removeHeaderView((DefaultActionItemBar) mQuickShareActionItemBar);
        setAdapter(mAdapter);
    }

    @Override
    public void clear() {
        assertOnUiThread();
        for (GeckoMenuItem menuItem : mItems) {
            if (menuItem.hasSubMenu()) {
                SubMenu sub = menuItem.getSubMenu();
                if (sub == null) {
                    continue;
                }
                try {
                    sub.clear();
                } catch (Exception ex) {
                    Log.e(LOGTAG, "Couldn't clear submenu.", ex);
                }
            }
        }

        mAdapter.clear();
        mItems.clear();

        /*
         * Reinflating the menu will re-add any action items to the toolbar, so
         * remove the old ones. This also ensures that any text associated with
         * these is switched to the correct locale.
         */
        if (mPrimaryActionItemBar != null) {
            for (View item : mPrimaryActionItems.values()) {
                mPrimaryActionItemBar.removeActionItem(item);
            }
        }
        mPrimaryActionItems.clear();

        if (mSecondaryActionItemBar != null) {
            for (View item : mSecondaryActionItems.values()) {
                mSecondaryActionItemBar.removeActionItem(item);
            }
        }
        mSecondaryActionItems.clear();

        if (mQuickShareActionItemBar != null) {
            for (View item : mQuickShareActionItems.values()) {
                mQuickShareActionItemBar.removeActionItem(item);
            }
        }
        mQuickShareActionItems.clear();

        // Remove the view, too -- the first addActionItem will re-add it,
        // and this is simpler than changing that logic.
        if (mPrimaryActionItemBar instanceof DefaultActionItemBar) {
            removePrimaryActionBarView();
        }

        removeSecondaryActionBarView();
        removeQuickShareActionBarView();
    }

    @Override
    public void close() {
        if (mMenuPresenter != null)
            mMenuPresenter.closeMenu();
    }

    private void showMenu(View viewForMenu) {
        if (mMenuPresenter != null)
            mMenuPresenter.showMenu(viewForMenu);
    }

    @Override
    public MenuItem findItem(int id) {
        assertOnUiThread();
        MenuItem quickItem = mItemsById.get(id);
        if (quickItem != null) {
            return quickItem;
        }

        for (GeckoMenuItem menuItem : mItems) {
            if (menuItem.getItemId() == id) {
                mItemsById.put(id, menuItem);
                return menuItem;
            } else if (menuItem.hasSubMenu()) {
                if (!menuItem.hasActionProvider()) {
                    SubMenu subMenu = menuItem.getSubMenu();
                    MenuItem item = subMenu.findItem(id);
                    if (item != null) {
                        mItemsById.put(id, item);
                        return item;
                    }
                }
            }
        }
        return null;
    }

    @Override
    public MenuItem getItem(int index) {
        if (index < mItems.size())
            return mItems.get(index);

        return null;
    }

    @Override
    public boolean hasVisibleItems() {
        assertOnUiThread();
        for (GeckoMenuItem menuItem : mItems) {
            if (menuItem.isVisible() &&
                !mPrimaryActionItems.containsKey(menuItem) &&
                !mSecondaryActionItems.containsKey(menuItem) &&
                !mQuickShareActionItems.containsKey(menuItem))
                return true;
        }

        return false;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // Close the menu if it is open and the hardware menu key is pressed.
        if (keyCode == KeyEvent.KEYCODE_MENU && isShown()) {
            close();
            return true;
        }

        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean isShortcutKey(int keyCode, KeyEvent event) {
        return true;
    }

    @Override
    public boolean performIdentifierAction(int id, int flags) {
        return false;
    }

    @Override
    public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
        return false;
    }

    @Override
    public void removeGroup(int groupId) {
    }

    @Override
    public void removeItem(int id) {
        assertOnUiThread();
        GeckoMenuItem item = (GeckoMenuItem) findItem(id);
        if (item == null)
            return;

        // Remove it from the cache.
        mItemsById.remove(id);

        // Remove it from any sub-menu.
        for (GeckoMenuItem menuItem : mItems) {
            if (menuItem.hasSubMenu()) {
                SubMenu subMenu = menuItem.getSubMenu();
                if (subMenu != null && subMenu.findItem(id) != null) {
                    subMenu.removeItem(id);
                    return;
                }
            }
        }

        // Remove it from own menu.
        if (mPrimaryActionItems.containsKey(item)) {
            if (mPrimaryActionItemBar != null)
                mPrimaryActionItemBar.removeActionItem(mPrimaryActionItems.get(item));

            mPrimaryActionItems.remove(item);
            mItems.remove(item);

            if (mPrimaryActionItems.size() == 0 &&
                mPrimaryActionItemBar instanceof DefaultActionItemBar) {
                removePrimaryActionBarView();
            }

            return;
        }

        if (mSecondaryActionItems.containsKey(item)) {
            if (mSecondaryActionItemBar != null)
                mSecondaryActionItemBar.removeActionItem(mSecondaryActionItems.get(item));

            mSecondaryActionItems.remove(item);
            mItems.remove(item);

            if (mSecondaryActionItems.size() == 0) {
                removeSecondaryActionBarView();
            }

            return;
        }

        if (mQuickShareActionItems.containsKey(item)) {
            if (mQuickShareActionItemBar != null)
                mQuickShareActionItemBar.removeActionItem(mQuickShareActionItems.get(item));

            mQuickShareActionItems.remove(item);
            mItems.remove(item);

            if (mQuickShareActionItems.size() == 0) {
                removeQuickShareActionBarView();
            }

            return;
        }

        mAdapter.removeMenuItem(item);
        mItems.remove(item);
    }

    @Override
    public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
    }

    @Override
    public void setGroupEnabled(int group, boolean enabled) {
    }

    @Override
    public void setGroupVisible(int group, boolean visible) {
    }

    @Override
    public void setQwertyMode(boolean isQwerty) {
    }

    @Override
    public int size() {
        return mItems.size();
    }

    @Override
    public boolean hasActionItemBar() {
         return (mPrimaryActionItemBar != null) &&
                 (mSecondaryActionItemBar != null) &&
                 (mQuickShareActionItemBar != null);
    }

    @Override
    public void onShowAsActionChanged(GeckoMenuItem item) {
        removeItem(item.getItemId());

        if (item.isActionItem() && addActionItem(item)) {
            return;
        }

        addItem(item);
    }

    public void onItemChanged(GeckoMenuItem item) {
        assertOnUiThread();
        if (item.isActionItem()) {
            final View actionView;
            final int actionEnum = item.getActionEnum();
            if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_ALWAYS) {
                actionView = mPrimaryActionItems.get(item);
            } else if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_IF_ROOM) {
                actionView = mSecondaryActionItems.get(item);
            } else {
                actionView = mQuickShareActionItems.get(item);
            }

            if (actionView != null) {
                if (item.isVisible()) {
                    actionView.setVisibility(View.VISIBLE);
                    if (actionView instanceof MenuItemActionBar) {
                        ((MenuItemActionBar) actionView).initialize(item);
                    } else {
                        ((MenuItemSwitcherLayout) actionView).initialize(item);
                    }
                } else {
                    actionView.setVisibility(View.GONE);
                }
            }
        } else {
            mAdapter.notifyDataSetChanged();
        }
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        // We might be showing headers. Account them while using the position.
        position -= getHeaderViewsCount();

        GeckoMenuItem item = mAdapter.getItem(position);
        handleMenuItemClick(item);
    }

    void handleMenuItemClick(GeckoMenuItem item) {
        if (!item.isEnabled())
            return;

        if (item.invoke()) {
            close();
        } else if (item.hasSubMenu()) {
            // Refresh the submenu for the provider.
            GeckoActionProvider provider = item.getGeckoActionProvider();
            if (provider != null) {
                GeckoSubMenu subMenu = new GeckoSubMenu(getContext());
                subMenu.setShowIcons(true);
                provider.onPrepareSubMenu(subMenu);
                item.setSubMenu(subMenu);
            }

            // Show the submenu.
            GeckoSubMenu subMenu = (GeckoSubMenu) item.getSubMenu();
            showMenu(subMenu);
        } else {
            close();
            mCallback.onMenuItemClick(item);
        }
    }

    boolean handleMenuItemLongClick(GeckoMenuItem item) {
        if (!item.isEnabled()) {
            return false;
        }

        if (mCallback != null) {
            if (mCallback.onMenuItemLongClick(item)) {
                close();
                return true;
            }
        }
        return false;
    }

    public Callback getCallback() {
        return mCallback;
    }

    public MenuPresenter getMenuPresenter() {
        return mMenuPresenter;
    }

    public void setCallback(Callback callback) {
        mCallback = callback;

        // Update the submenus just in case this changes on the fly.
        for (GeckoMenuItem menuItem : mItems) {
            if (menuItem.hasSubMenu()) {
                GeckoSubMenu subMenu = (GeckoSubMenu) menuItem.getSubMenu();
                subMenu.setCallback(mCallback);
            }
        }
    }

    public void setMenuPresenter(MenuPresenter presenter) {
        mMenuPresenter = presenter;

        // Update the submenus just in case this changes on the fly.
        for (GeckoMenuItem menuItem : mItems) {
            if (menuItem.hasSubMenu()) {
                GeckoSubMenu subMenu = (GeckoSubMenu) menuItem.getSubMenu();
                subMenu.setMenuPresenter(mMenuPresenter);
            }
        }
    }

    public void setActionItemBarPresenter(ActionItemBarPresenter presenter) {
        mPrimaryActionItemBar = presenter;
    }

    public void setShowIcons(boolean show) {
        if (mShowIcons != show) {
            mShowIcons = show;
            mAdapter.notifyDataSetChanged();
        }
    }

    // Action Items are added to the header view by default.
    // URL bar can register itself as a presenter, in case it has a different place to show them.
    public static class DefaultActionItemBar extends LinearLayout
                                             implements ActionItemBarPresenter {
        private final int mRowHeight;
        private float mWeightSum;

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

        public DefaultActionItemBar(Context context, AttributeSet attrs) {
            super(context, attrs);

            mRowHeight = getResources().getDimensionPixelSize(R.dimen.menu_item_row_height);
        }

        @Override
        public boolean addActionItem(View actionItem) {
            ViewGroup.LayoutParams actualParams = actionItem.getLayoutParams();
            LinearLayout.LayoutParams params;

            if (actualParams != null) {
                params = new LinearLayout.LayoutParams(actionItem.getLayoutParams());
                params.width = 0;
            } else {
                params = new LinearLayout.LayoutParams(0, mRowHeight);
            }

            if (actionItem instanceof MenuItemSwitcherLayout) {
                params.weight = ((MenuItemSwitcherLayout) actionItem).getChildCount();
            } else {
                params.weight = 1.0f;
            }

            mWeightSum += params.weight;

            actionItem.setLayoutParams(params);
            addView(actionItem);
            setWeightSum(mWeightSum);
            return true;
        }

        @Override
        public void removeActionItem(View actionItem) {
            if (indexOfChild(actionItem) != -1) {
                LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) actionItem.getLayoutParams();
                mWeightSum -= params.weight;
                removeView(actionItem);
            }
        }
    }

    // Adapter to bind menu items to the list.
    private class MenuItemsAdapter extends BaseAdapter {
        private static final int VIEW_TYPE_DEFAULT = 0;
        private static final int VIEW_TYPE_ACTION_MODE = 1;
        private static final int VIEW_TYPE_ICON = 1;

        private final List<GeckoMenuItem> mItems;

        public MenuItemsAdapter() {
            mItems = new ArrayList<GeckoMenuItem>();
        }

        @Override
        public int getCount() {
            if (mItems == null)
                return 0;

            int visibleCount = 0;
            for (GeckoMenuItem item : mItems) {
                if (item.isVisible())
                    visibleCount++;
            }

            return visibleCount;
        }

        @Override
        public GeckoMenuItem getItem(int position) {
            for (GeckoMenuItem item : mItems) {
                if (item.isVisible()) {
                    position--;

                    if (position < 0)
                        return item;
                }
            }

            return null;
        }

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            GeckoMenuItem item = getItem(position);
            GeckoMenuItem.Layout view = null;

            // Try to re-use the view.
            if (convertView == null) {
                final int type = getItemViewType(position);
                if (type == VIEW_TYPE_ICON) {
                    view = new MenuItemIcon(parent.getContext(), null);
                } else if (type == VIEW_TYPE_DEFAULT) {
                    view = new MenuItemDefault(parent.getContext(), null);
                }
            } else {
                view = (GeckoMenuItem.Layout) convertView;
            }

            if (view == null || view instanceof MenuItemSwitcherLayout) {
                // Always get from the menu item.
                // This will ensure that the default activity is refreshed.
                view = (MenuItemSwitcherLayout) item.getActionView();

                // ListView will not perform an item click if the row has a focusable view in it.
                // Hence, forward the click event on the menu item in the action-view to the ListView.
                final View actionView = (View) view;
                final int pos = position;
                final long id = getItemId(position);
                ((MenuItemSwitcherLayout) view).setMenuItemClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        GeckoMenu listView = GeckoMenu.this;
                        listView.performItemClick(actionView, pos + listView.getHeaderViewsCount(), id);
                    }
                });
            }

            // Initialize the view.
            view.setShowIcon(mShowIcons);
            view.initialize(item);
            return (View) view;
        }

        @Override
        public int getItemViewType(int position) {
            final GeckoMenuItem item = getItem(position);
            if (item.getItemType() == GeckoMenuItem.ITEM_TYPE_ICON) {
                return VIEW_TYPE_ICON;
            }
            return getItem(position).getGeckoActionProvider() == null ? VIEW_TYPE_DEFAULT : VIEW_TYPE_ACTION_MODE;
        }

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

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

        @Override
        public boolean areAllItemsEnabled() {
            // Setting this to true is a workaround to fix disappearing
            // dividers in the menu (bug 963249).
            return true;
        }

        @Override
        public boolean isEnabled(int position) {
            // Setting this to true is a workaround to fix disappearing
            // dividers in the menu in L (bug 1050780).
            return true;
        }

        public void addMenuItem(GeckoMenuItem menuItem) {
            if (mItems.contains(menuItem))
                return;

            // Insert it in proper order.
            int index = 0;
            for (GeckoMenuItem item : mItems) {
                if (item.getOrder() > menuItem.getOrder()) {
                    mItems.add(index, menuItem);
                    notifyDataSetChanged();
                    return;
                } else {
                  index++;
                }
            }

            // Add the menuItem at the end.
            mItems.add(menuItem);
            notifyDataSetChanged();
        }

        public void removeMenuItem(GeckoMenuItem menuItem) {
            // Remove it from the list.
            mItems.remove(menuItem);
            notifyDataSetChanged();
        }

        public void clear() {
            mItemsById.clear();
            mItems.clear();
            notifyDataSetChanged();
        }

        public GeckoMenuItem getMenuItem(int id) {
            for (GeckoMenuItem item : mItems) {
                if (item.getItemId() == id)
                    return item;
            }

            return null;
        }
    }
}
