/*
 * Copyright (c) 2006-2008 MiniMe. Code released under The MIT/X Window System
 * License. Full license text can be found in license.txt
 */
package minime.ui;

import javax.microedition.lcdui.Graphics;

import minime.Composite;
import minime.Drawable;
import minime.Font;
import minime.FontManager;
import minime.Image;
import minime.Logger;
import minime.Portability;
import minime.core.CanDisable;
import minime.core.CanSelect;
import minime.core.Component;
import minime.core.Event;
import minime.core.EventDispatcher;
import minime.core.ResourceManager;

/**
 * A Menu containing list of menu items. When a Menu is present on the display,
 * the user can interact with it by selecting elements and possibly by
 * traversing and scrolling among them. Traversing and scrolling operations do
 * not cause application-visible events.
 * 
 * @author aaa
 * 
 */
public class Menu extends Component
{
	
    private Composite composite;
    private int selectedItemIndex = -1;
    private int startIndex = 0; // the start index of menu item displayed
    private int itemDisplayedCount; // the actual count of menu items displayed
    
    // in displayable area
    protected int itemCount = 0;

    public Menu(MenuItem[] items)
    {
        if (items == null)
            throw new NullPointerException();

        composite = new Composite();

        for (int i = 0; i < items.length; i++)
        {
            addItem(items[i]);
        }

        this.itemDisplayedCount = items.length;
    }
    /**
     * Creates menu from an array of MenuItem, with a specified selection frame thickness and
     * a specificed selection frame color.
     * 
     * @param items
     *            array of MenuItem
     * @param itemDisplayedCount
     *            the actual count of menu items displayed in displayable area
     * @param selectionFrameThickness
     *            the frame thickness of selected item frame
     * @param selectionFrameColor
     *            the color for rendering selected item frame
     * @throws NullPointerException
     *             if items is null
     * @throws IllegalArgumentException
     *             if itemDisplayedCount is less then 0
     */
    public Menu(MenuItem[] items, int selectionFrameThickness, int selectionFrameColor) throws IllegalArgumentException, NullPointerException
    {
        if (items == null)
            throw new NullPointerException();

        composite = new Composite();

        for (int i = 0; i < items.length; i++)
        {
        	items[i].setFrameThicknessAndColor(selectionFrameThickness, selectionFrameColor) ;
            addItem(items[i]);
        }

        this.itemDisplayedCount = items.length;
    }

    /**
     * Set thickness and color of the selection frame 
     * @param frameThickness
     * @param frameColor
     */
    public void setSelectionFrame(int frameThickness, int frameColor)
    {
    	int size = size() ;
    	for(int i=0;i<size;i++)
    		((MenuItem)composite.drawableAt(i)).setFrameThicknessAndColor(frameThickness, frameColor) ;
    }
    
    public void setSelectedItemBackgroundImage(Image image)
    {
    	int size = size() ;
    	for(int i=0;i<size;i++)
    		((MenuItem)composite.drawableAt(i)).setSelectedImage(image) ;
    }
    
    public void setItemsBackgroundImage(Image image)
    {
    	int size = size() ;
    	for(int i=0;i<size;i++)
    		((MenuItem)composite.drawableAt(i)).setBgImage(image) ;
    }
    
    /**
     * Calculates menu items size and position.
     */
    public void layoutImp()
    {
        int yPos = 0, xPos = 0;
        int menuItemMaxHeight=0 , menuItemMaxWidth=0 ;
        // do first round to layout all component and get maxW and maxH
        for (int i = 0; i < itemCount; i++)
        {
            Drawable d = composite.drawableAt(i);
            d.layout() ;
            menuItemMaxHeight=Math.max(menuItemMaxHeight,d.getHeight()) ;
            menuItemMaxWidth=Math.max(menuItemMaxWidth,d.getWidth()) ;
        }
        
        for (int i = 0; i < itemCount; i++)
        {
            Drawable d = composite.drawableAt(i);
            if (i >= startIndex && (i < startIndex + itemDisplayedCount))
            {
            	d.setWidth(menuItemMaxWidth);
            	d.setHeight(menuItemMaxHeight);
                d.setPosition(xPos, yPos);
                d.layout() ;		// force immediat layout (necessary to center label into frame)
                yPos += menuItemMaxHeight ;
                d.setVisible(true);
            }
            else
                d.setVisible(false);
        }
        setWidth(menuItemMaxWidth) ;
        setHeight(yPos) ;
    }

    /**
     * set the selected menu item.The index parameter must be within the range
     * [0..size()-1], inclusive.
     * 
     * @param index
     *            the selected item index
     * @param selected
     *            the new state of the element true=selected, false=not selected
     * @throws IndexOutOfBoundsException
     *             if index is invalid
     */
    public void setSelectedItem(int index)
    {
        if (index < 0 || index >= size())
            throw new IndexOutOfBoundsException();

        CanSelect selectedItem = null;
        // if the last selected item existed, set its property not selected
        if (selectedItemIndex != -1)
        {
            selectedItem = (CanSelect)composite.drawableAt(selectedItemIndex);
            selectedItem.setSelected(false);
        }

        // set the property of current selected item SELECTED
        if (index != -1)
        {
            selectedItem = (CanSelect)composite.drawableAt(index);
            selectedItem.setSelected(true);
        }
        selectedItemIndex = index;
    }

    /**
     * Sets the item count can be displayed on screen.If itemDisplayedCount
     * equals to 0, no items will be displayed.
     * 
     * @param itemDisplayedCount
     *            the item count can be displayed on screen.
     * @throws IllegalArgumentException
     *             if itemDisplayedCount is less then 0
     */
    public void SetitemDisplayedCount(int itemDisplayedCount)
    {
        if (itemDisplayedCount < 0)
            throw new IllegalArgumentException();

        this.itemDisplayedCount = itemDisplayedCount;
        setNeedLayout();
    }

    /**
     * Returns the selected item index
     */
    public int getSelectedItemIndex()
    {
        return selectedItemIndex;
    }

    /**
     * Gets the item at given position.The index parameter must be within the
     * range [0..size()-1], inclusive.
     * 
     * @param index
     *            the index of item
     * @return the drawable at the given position.
     * @throws IndexOutOfBoundsException
     *             if index is invalid
     */
    public Drawable getItem(int index)
    {
        if (index < 0 || index >= size())
            throw new IndexOutOfBoundsException();

        return composite.drawableAt(index);
    }

    /**
     * add a new menu item
     * 
     * @param item
     *            the Icon for being added
     * @throws NullPointerException
     *             if item is null
     */
    public void addItem(Drawable item) throws NullPointerException
    {
        if (item == null)
            throw new NullPointerException();

        composite.addDrawable(item);
        itemCount++;
        // if have no selected item, set the first one
        if (selectedItemIndex == -1)
        {
            selectedItemIndex = 0;
            setSelectedItem(selectedItemIndex);
        }
        else
        	((CanSelect)item).setSelected(false);
        
        setNeedLayout();
    }

    /**
     * Inserts an element into the menu just prior to the element specified. The
     * size of the menu grows by one. The elementNum parameter must be within
     * the range [0..size()], inclusive. The index of the last element is
     * size()-1, and so there is actually no element whose index is size(). If
     * this value is used for elementNum, the new element is inserted
     * immediately after the last element. In this case, the effect is identical
     * to addItem().
     * 
     * @param elementNum
     *            the index of the element where insertion is to occur
     * @param item
     *            the drawable to be inserted
     * @throws IndexOutOfBoundsException
     *             if elementNum is invalid
     * @throws NullPointerException
     *             if item is null
     */
    public void insert(int elementNum, Drawable item) throws IndexOutOfBoundsException, NullPointerException
    {
        if (elementNum < 0 || elementNum > size())
            throw new IndexOutOfBoundsException();
        if (item == null)
            throw new NullPointerException();

        itemCount++;
        composite.insertDrawable(item, elementNum);
        if (selectedItemIndex >= elementNum)
            selectedItemIndex++;

        setNeedLayout();
    }

    /**
     * Gets the number of items in the Menu.
     * 
     * @return the number of items
     */
    public int size()
    {
        return composite.drawableSize();
    }

    /**
     * Removes a menu item, if the item is not in menu, nothing will be done.
     * 
     * @param item
     *            the Icon for being removed
     * @throws NullPointerException
     *             if item is null
     */
    public void removeItem(Drawable item) throws NullPointerException
    {
        if (item == null)
            throw new NullPointerException();

        if (item == composite.drawableAt(selectedItemIndex))
        {// remove the item, if the item before the current item exist, set
            // its property SELECTED
            itemCount--;
            composite.removeDrawable(item);

            if (itemCount == 0)
            {
                selectedItemIndex = -1;
            }
            setSelectedItem(selectedItemIndex);
        }
        setNeedLayout();
    }

    /**
     * Removes all the items in the Menu.
     * 
     */
    public void removeAll()
    {
        composite.removeAll();
        selectedItemIndex = -1;
        itemCount = 0;
    }

    /*
     * (non-Javadoc)
     * 
     * @see minime.Drawable#renderImpl(javax.microedition.lcdui.Graphics)
     */
    public void renderImpl(Graphics gc)
    {
        composite.render(gc);
    }

    /*
     * (non-Javadoc)
     * 
     * @see minime.core.EventListener#onEvent(minime.core.Event)
     */
    public boolean onEvent(Event evt)
    {
        if (((evt.type != Event.KEY_EVENT) && (evt.type != Event.SB_EVENT)) || itemCount == 0)
        {
            return false;
        }

        boolean handled = false;
        int tmpSelectedItemIndex = selectedItemIndex;

        if (evt.paramA == Portability.KEY_UP)
        {
            while (tmpSelectedItemIndex > 0)
            {
                tmpSelectedItemIndex--;
                // automatically scrolling screen up if selected item index at
                // the first place of menu items displayed on screen and start
                // index greater than 0
                if (tmpSelectedItemIndex < startIndex + 1 && tmpSelectedItemIndex > 0)
                    startIndex--;

                setSelectedItem(tmpSelectedItemIndex);
               	fireMenuEvent(Event.MENU_EVENT);
                handled = true;
                break;
            }
        }
        else if (evt.paramA == Portability.KEY_DOWN)
        {
            while (tmpSelectedItemIndex < itemCount - 1)
            {
                tmpSelectedItemIndex++;
                // automatically scrolling screen down if selected item index at
                // the last place of menu items displayed on screen and selected
                // item index less than amount of items
                if (tmpSelectedItemIndex >= startIndex + itemDisplayedCount - 1 && tmpSelectedItemIndex < itemCount - 1)
                    startIndex++;

                setSelectedItem(tmpSelectedItemIndex);
               	fireMenuEvent(Event.MENU_EVENT);
                handled = true;
                break;
            }
        }
        else if (evt.paramA == Portability.KEY_FIRE)
        {
           	fireMenuEvent(Event.MENU_SELECTION_EVENT);
            return true ;
        }

        if (handled)
        {
            setNeedLayout();
            minime.core.Runtime.getInstance().repaint();
            return true;
        }
        return false;
    }

	// fire the menu selection event with item index as paramA, rscId as paramB and the drawable as payload
    private void fireMenuEvent(int eventType)
    {
    	Drawable d = (Drawable)composite.drawableAt(selectedItemIndex) ;
    	int rsc_id = d.getRscId() ;
        EventDispatcher.getInstance().fire(Event.createEvent(eventType, selectedItemIndex, rsc_id, d));
    }
}
