/*
 * 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 java.util.Vector;

import javax.microedition.lcdui.Graphics;

import minime.Drawable;
import minime.Font;
import minime.FontManager;
import minime.Image;
import minime.Portability;
import minime.core.Component;
import minime.core.Event;
import minime.core.EventDispatcher;
import minime.core.ResourceManager;
import minime.gen.ImageRscId;

/**
 * A ChoiceGroup is a group of selectable elements.The group may be created with
 * a mode that requires a single choice to be made or that allows multiple
 * choices. The implementation is responsible for providing the graphical
 * representation of these modes and must provide visually different graphics
 * for different modes. For example, it might use "radio buttons" for the single
 * choice mode and "check boxes" for the multiple choice mode.
 * 
 * Each element of a ChoiceGroup is a MenuItemComposite object as a unit. The Choice
 * also has a selection indicator (such as a radio button or a checkbox) placed
 * at the beginning.
 * 
 * There are tow types of Choices: exclusive-choice, multiple-choice.
 * 
 * The exclusive-choice presents a series of elements and interacts with the
 * user. That is, when the user selects an element, that element is shown to be
 * selected using a distinct visual representation. If there are elements
 * present in the Choice, one element must be selected at any given time. If at
 * any time a situation would result where there are elements in the
 * exclusive-choice but none is selected, the implementation will choose an
 * element and select it. This situation can arise when an element is added to
 * an empty Choice, when the selected element is deleted from the Choice, or
 * when a Choice is created and populated with elements by a constructor. In
 * these cases, the choice of which element is selected is left to the
 * implementation. Applications for which the selected element is significant
 * should set the selection explicitly. There is no way for the user to unselect
 * an element within an exclusive Choice.
 * 
 * A multiple-choice presents a series of elements and allows the user to select
 * any number of elements in any combination. As with exclusive-choice, the
 * multiple-choice interacts with the user in object-operation mode. The visual
 * appearance of a multiple-choice will likely have a visual representation
 * distinct from the exclusive-choice that shows the selected state of each
 * element as well as indicating to the user that multiple elements may be
 * selected.
 * 
 * If the mode is EXCLUSIVE, when selecte one item, some object should fire a
 * CHOICE_SELECTED_EVENT type event, ChoiceGroup will recieve this event and
 * handle it.If the mode is MULTIPLE, when selecte one item, some object should
 * fire a CHOICE_MARKED_EVENT type event, when unselecte an item, that object
 * should fire a CHOICE_UNMARKED_EVENT type event, ChoiceGroup will recieve the
 * event and handle it.So if some view use this component, the view should have
 * some buttons like "Select","Mark/Unmark" to fire those events.
 * 
 * @author yishu
 * 
 */
public class ChoiceGroup extends Component
{

    /**
     * RADIO_BUTTON is a choice having exactly one element selected at time.
     */
    public static final int RADIO_BUTTON = 1;
    /**
     * CHECK_BOX is a choice that can have arbitrary number of elements selected
     * at a time.
     */
    public static final int CHECK_BOX = 2;

    private int choiceType = 0;
    private Vector selectedFlags;

    private ImagePanel itemUnselImg;
    private ImagePanel itemSelImg;

    private Menu choiceGroupMenu;

    /**
     * Creates a new Radio Button Group from an array of text resource and with a selection background image.
     * 
     * @param elements
     * 			array of text resource Id
     * @param selImRscId
     * 			resource id of the image used for selected item background
     * 				
     * @return the created choicegroup
     */
    public static ChoiceGroup createRadioButtonGroup(int[] txtRscId) throws NullPointerException, IllegalArgumentException
    {
		if (txtRscId == null)
			throw new IllegalArgumentException();
    	Image boxUnselected = new Image(ResourceManager.getImage(ImageRscId.IM_RADIO_UNSELECTED)) ;
    	Image boxSelected = new Image(ResourceManager.getImage(ImageRscId.IM_RADIO_SELECTED)) ; 
    	ChoiceGroup cg = new ChoiceGroup(ChoiceGroup.RADIO_BUTTON, 
    							boxUnselected, boxSelected,
    							5, txtRscId);
    	return cg;
    }

    /**
     * Creates a new Radio Button Group from an array of text resource and with a selection background image.
     * 
     * @param elements
     * 			array of text resource Id
     * @param selImRscId
     * 			resource id of the image used for selected item background
     * 				
     * @return the created choicegroup
     */
    public static ChoiceGroup createRadioButtonGroup(MenuItemComposite[] items)
    {
		if (items == null)
			throw new IllegalArgumentException();
    	Image boxUnselected = new Image(ResourceManager.getImage(ImageRscId.IM_RADIO_UNSELECTED)) ;
    	Image boxSelected = new Image(ResourceManager.getImage(ImageRscId.IM_RADIO_SELECTED)) ; 
    	ChoiceGroup cg = new ChoiceGroup(ChoiceGroup.RADIO_BUTTON, 
    							boxUnselected, boxSelected,
    							5, items);
    	return cg;
    }

    /**
     * Creates a new Radio Button Group from an array of text resource and with a selection background image.
     * 
     * @param elements
     * 			array of text resource Id
     * @param selImRscId
     * 			resource id of the image used for selected item background
     * 				
     * @return the created choicegroup
     */
    public static ChoiceGroup createCheckBoxGroup(int[] txtRscId) throws NullPointerException, IllegalArgumentException
    {
		if (txtRscId == null)
			throw new IllegalArgumentException();
    	
    	Image boxUnselected = new Image(ResourceManager.getImage(ImageRscId.IM_CHECKBOX_UNSELECTED)) ;
    	Image boxSelected = new Image(ResourceManager.getImage(ImageRscId.IM_CHECKBOX_SELECTED)) ; 
    	ChoiceGroup cg = new ChoiceGroup(ChoiceGroup.CHECK_BOX, 
    							boxUnselected, boxSelected,
    							5, txtRscId);
    	return cg;
    }
    
    public static ChoiceGroup createCheckBoxGroup(MenuItemComposite[] items) throws NullPointerException, IllegalArgumentException
    {
		if (items == null)
			throw new IllegalArgumentException();

		Image boxUnselected = new Image(ResourceManager.getImage(ImageRscId.IM_CHECKBOX_UNSELECTED)) ;
    	Image boxSelected = new Image(ResourceManager.getImage(ImageRscId.IM_CHECKBOX_SELECTED)) ; 
    	ChoiceGroup cg = new ChoiceGroup(ChoiceGroup.CHECK_BOX, 
    							boxUnselected, boxSelected,
    							5, items);
    	return cg;
    }
    
    private ChoiceGroup(int choiceType, Image imBoxUnselected, Image imBoxSelected, int offset, int[] txtRscId)
	{
	    if (choiceType != RADIO_BUTTON && choiceType != CHECK_BOX)
	        throw new IllegalArgumentException("ChoiceType should be RADIO_BUTTON or CHECK_BOX");
	
	    // create image for selected & unselected box
	    itemUnselImg = new ImagePanel(imBoxUnselected) ;
	    itemSelImg = new ImagePanel(imBoxSelected) ;
	    
	    // create the menu items
	    // create the menu items
	    int [][]offsets = new int[txtRscId.length][2] ;
	    Drawable[][]drawables = new Drawable[txtRscId.length][2] ;
	    MenuItemComposite[] menuItems = new MenuItemComposite[txtRscId.length] ;
	    for(int i=0;i<txtRscId.length;i++)
	    {
	    	drawables[i][0] = itemUnselImg ;
	    	drawables[i][1] = new Label(txtRscId[i],Portability.defaultMenuItemTextColor) ;
	        offsets[i][0] = 0 ;
	        offsets[i][1] = itemUnselImg.getWidth()+offset ;
	        menuItems[i] = new MenuItemComposite(drawables[i], offsets[i],Portability.defaultMenuItemFrameThickness, Portability.defaultMenuSelectionFrameColor, null, null) ;
	        // attach the rscId to the MenuItemComposite so view can check it 
	        // when catching RADIO_SELECTED_EVENT,BOX_MARKED_EVENT or BOX_UNMARKED_EVENT
	        menuItems[i].setRscId(txtRscId[i]) ;
	    }
	
	    // create menu ...
	    choiceGroupMenu = new Menu(menuItems) ;
	    selectedFlags = new Vector();
	    clearAllSelection() ;
	    this.choiceType = choiceType;
	    if (this.choiceType==RADIO_BUTTON)
	    	setSelectedIndex(0) ;
        setNeedLayout() ;
	}

    private ChoiceGroup(int choiceType, Image imBoxUnselected, Image imBoxSelected,	int offset, MenuItemComposite[] items)
	{
	    if (choiceType != RADIO_BUTTON && choiceType != CHECK_BOX)
	        throw new IllegalArgumentException("ChoiceType should be RADIO_BUTTON or CHECK_BOX");
	
	    // create image for selected & unselected box
	    itemUnselImg = new ImagePanel(imBoxUnselected) ;
	    itemSelImg = new ImagePanel(imBoxSelected) ;
	    	    
	    // add selected - unselected image to the MenuItemComposit
	    for(int i=0;i<items.length;i++)
	    {
	    	int initialComponentOffset = items[i].getElementOffsetAt(0) ;
	    	Drawable initialComponent = items[i].getElementAt(0) ;
	    	initialComponentOffset += itemUnselImg.getWidth() + offset ;  
	    	// replace initial component offset
	    	items[i].setElementAt(initialComponent, initialComponentOffset, 0) ;
	    	// insert box image as the first component
	    	items[i].insertElementAt(itemUnselImg, 0, 0) ;
	    }

	    // create menu ...
	    choiceGroupMenu = new Menu(items) ;
	    selectedFlags = new Vector();
	    clearAllSelection() ;
	    this.choiceType = choiceType;
	    if (this.choiceType==RADIO_BUTTON)
	    	setSelectedIndex(0) ;
        setNeedLayout() ;
	}

    public void setMenuItems(MenuItem[] menuItems)
    {
    	// clear up menu previous content
    	choiceGroupMenu.removeAll() ;
    	for(int i=0;i<menuItems.length;i++)
    	{
    		MenuItemComposite item = addBoxImageComponent(menuItems[i]) ;
    		choiceGroupMenu.addItem(item) ;
    	}    	
     }
    
    public MenuItemComposite getItem(int index)
    {
    	return (MenuItemComposite)choiceGroupMenu.getItem(index) ;
    }
    
    public void setSelectionFrame(int frameThickness, int frameColor)
    {
    	choiceGroupMenu.setSelectionFrame(frameThickness, frameColor) ;
    }

    public void setSelectedItemBackgroundImage(Image image)
    {
    	choiceGroupMenu.setSelectedItemBackgroundImage(image) ;
    }
    
    public void setItemsBackgroundImage(Image image)
    {
    	choiceGroupMenu.setItemsBackgroundImage(image) ;
    }
    
    private MenuItemComposite addBoxImageComponent(MenuItem item)
    {
    	if (item instanceof MenuItemComposite)
    	{
    		return (MenuItemComposite)item ;
		}
		
		return new MenuItemComposite(item) ;
	}

	/**
     * For ChoiceGroup objects of type MULTIPLE, this simply sets an individual
     * element's selected state.
     * 
     * For ChoiceGroup objects of type EXCLUSIVE, this can be used only to
     * select an element. That is, the selected parameter must be true . When an
     * element is selected, the previously selected element is deselected. If
     * selected is false , this call is ignored.
     * 
     * For both list types, the index parameter must be within the range
     * [0..size()-1], inclusive.
     * 
     * @param elementNum
     *            the number of the element. Indexing of the elements is
     *            zero-based
     * @param selected
     *            the new state of the element true=selected, false=not selected
     * @throws IndexOutOfBoundsException
     *             if index is invalid
     */
    public void setSelectedIndex(int elementNum) throws IndexOutOfBoundsException
    {
        if (elementNum < 0 || elementNum >= size())
            throw new IndexOutOfBoundsException();

        // if the choice type is exclusive and not selected, get out.
        if (choiceType == RADIO_BUTTON)
        {
        	// if already selected, do nothing
        	if (isSelected(elementNum))
        		return ;
        	clearAllSelection() ;
        	setSelectedItem(elementNum) ;
        	// get the rsc id of the element to select or un-select
        	int rsc_id = choiceGroupMenu.getItem(elementNum).getRscId() ;
        	EventDispatcher.getInstance().fire(Event.createEvent(Event.RADIO_SELECTED_EVENT, elementNum, rsc_id)) ;
            setNeedLayout() ;
        }
        else
        {
        	// get the rsc id of the element to select or un-select
        	int rsc_id = choiceGroupMenu.getItem(elementNum).getRscId() ;
        	if (isSelected(elementNum))
        	{
        		// clear the item and fire the specific event 
        		clrSelectedItem(elementNum) ;
            	EventDispatcher.getInstance().fire(Event.createEvent(Event.BOX_UNMARKED_EVENT, elementNum, rsc_id)) ;
        	}
        	else
        	{
        		// set the item and fire the specific event 
            	setSelectedItem(elementNum) ;
            	EventDispatcher.getInstance().fire(Event.createEvent(Event.BOX_MARKED_EVENT, elementNum, rsc_id)) ;
        	}
            setNeedLayout() ;
        }
    }

    private void clrSelectedItem(int elementNum)
    {
    	selectedFlags.setElementAt(new Boolean(false), elementNum) ;
	}
    private void setSelectedItem(int elementNum)
    {
    	selectedFlags.setElementAt(new Boolean(true), elementNum) ;
	}

	private void clearAllSelection()
	{
		selectedFlags.removeAllElements() ;
    	for (int i=0;i<size();i++)
    		selectedFlags.addElement(new Boolean(false)) ;
	}

    private void setSelectedItemsImage()
    {
    	for (int i=0;i<selectedFlags.size();i++)
    	{
    		if (isSelected(i))
    			((MenuItemComposite)choiceGroupMenu.getItem(i)).setElementAt(itemSelImg, 0, 0);
    		else
    			((MenuItemComposite)choiceGroupMenu.getItem(i)).setElementAt(itemUnselImg, 0, 0);
    	}
	}

    /**
     * Deletes all elements from this Choice, leaving it with zero elements.
     * This method does nothing if the Choice is already empty.
     * 
     */
    public void deleteAll()
    {
        choiceGroupMenu.removeAll();
        selectedFlags.removeAllElements();
    }

    /**
     * Queries the state of a ChoiceGroup and returns the state of all elements
     * in the boolean array selectedArray_return. Note: this is a result
     * parameter. It must be at least as long as the size of the ChoiceGroup as
     * returned by size(). If the array is longer, the extra elements are set to
     * false.
     * 
     * For ChoiceGroup objects of type MULTIPLE, any number of elements may be
     * selected and set to true in the result array. For ChoiceGroup objects of
     * type EXCLUSIVE exactly one element will be selected, unless there are
     * zero elements in the ChoiceGroup.
     * 
     * @param selectedArray_return
     *            array to contain the results
     * @return the number of selected elements in the ChoiceGroup
     * @throws IllegalArgumentException
     *             if selectedArray_return is shorter than the size of the
     *             ChoiceGroup
     * @throws NullPointerException
     *             if selectedArray_return is null
     */
    public boolean[] getFlagArray() throws IllegalArgumentException, NullPointerException
    {
    	boolean[] array = new boolean[selectedFlags.size()] ;
    	for (int i=0;i<array.length;i++)
    		array[i]= ((Boolean) selectedFlags.elementAt(i)).booleanValue();
    	return array ;
    }

    public void setFlagArray(boolean[]array) throws IllegalArgumentException, NullPointerException
    {
    	selectedFlags.setSize(array.length) ;
    	for (int i=0;i<selectedFlags.size();i++)
    		selectedFlags.setElementAt(new Boolean(array[i]), i) ;
    }
    
    /**
     * Gets a boolean value indicating whether this element is selected.
     * 
     * @param elementNum
     *            elementNum - the index of the element to be queried
     * @return selection state of the element
     * @throws IndexOutOfBoundsException
     *             if elementNum is invalid
     */
    public boolean isSelected(int elementNum) throws IndexOutOfBoundsException
    {
        if (elementNum < 0 || elementNum >= size())
            throw new IndexOutOfBoundsException();

        return ((Boolean) selectedFlags.elementAt(elementNum)).booleanValue();
    }

    /**
     * Returns the number of elements in the ChoiceGroup.
     * 
     * @return the number of elements in the ChoiceGroup
     */
    public int size()
    {
        return choiceGroupMenu.size();
    }

    public void layoutImp()
    {
    	// layout the composite menu, and set width & height
    	// \TODO setSelectedItemsImage should not be called here ... 
    	setSelectedItemsImage() ;
    	// setNeedLayout is never done on choiceGroupMenu, so layout will be done only once
        choiceGroupMenu.layout();

        setWidth(choiceGroupMenu.getWidth()) ;
        setHeight(choiceGroupMenu.getHeight()) ;
    }

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

    /**
     * If the mode is EXCLUSIVE, when selecte one item, some object should fire
     * a CHOICE_SELECTED_EVENT type event, ChoiceGroup will recieve this event
     * and handle it.If the mode is MULTIPLE, when selecte one item, some object
     * should fire a CHOICE_MARKED_EVENT type event, when unselecte an item,
     * that object should fire a CHOICE_UNMARKED_EVENT type event, ChoiceGroup
     * will recieve the event and handle it.So if some view use this component,
     * the view should have some buttons like "Select","Mark/Unmark" to fire
     * those events
     * 
     * @see minime.core.EventListener#onEvent(minime.core.Event)
     */
    public boolean onEvent(Event evt)
    {
        if (evt.type == Event.MENU_SELECTION_EVENT)
        {
        	setSelectedIndex(evt.paramA) ;
            minime.core.Runtime.getInstance().repaint();
        	return true ;
        }

        return choiceGroupMenu.onEvent(evt);
    }
}
