package org.cocos2d.menu_nodes;

import java.util.ArrayList;
import java.util.Arrays;

import org.cocos2d.CCDirector;
import org.cocos2d.base_nodes.CCNode;
import org.cocos2d.cocoa.CCGeometry.CCPoint;
import org.cocos2d.cocoa.CCGeometry.CCRect;
import org.cocos2d.cocoa.CCGeometry.CCSize;
import org.cocos2d.events.CCTouchDispatcher;
import org.cocos2d.include.ccMacros;
import org.cocos2d.layers_scenes_transitions_nodes.CCLayerRGBA;
import org.cocos2d.support.CCPointExtension;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.util.CGPointUtil;
import org.cocos2d.types.util.PoolHolder;
import org.cocos2d.utils.pool.OneClassPool;

import android.view.MotionEvent;

/**
 * @addtogroup GUI
 * @{
 * @addtogroup menu
 * @{
 */

/** @brief A CCMenu
 * 
 * Features and Limitation:
 *  - You can add MenuItem objects in runtime using addChild:
 *  - But the only accepted children are MenuItem objects
 */
public class CCMenu extends CCLayerRGBA {
	public enum tCCMenuState {
		kCCMenuStateWaiting,
		kCCMenuStateTrackingTouch
	}

	//* priority used by the menu for the event handler
	public static final int kCCMenuHandlerPriority = -128;

	/** whether or not the menu will receive events */
	private boolean m_bEnabled;

	public CCMenu() {
		m_pSelectedItem = null;
	}

	/** creates an empty CCMenu */
	public static CCMenu create() {
		return CCMenu.createWithArray(null);
	}

	/** creates a CCMenu with CCMenuItem objects */
	public static CCMenu create(CCMenuItem... items) {
		return CCMenu.createWithItems(items);
	}

	/** creates a CCMenu with a CCArray of CCMenuItem objects */
	public static CCMenu createWithArray(ArrayList<CCMenuItem> pArrayOfItems) {
		CCMenu pRet = new CCMenu();
		if(! pRet.initWithArray(pArrayOfItems)) {
			pRet = null;
		}
		return pRet;
	}

	/** creates a CCMenu with CCMenuItem objects */
	public static CCMenu createWithItems(CCMenuItem... items) {
		ArrayList<CCMenuItem> pArray = new ArrayList<CCMenuItem>();
		pArray.addAll(Arrays.asList(items));
		return CCMenu.createWithArray(pArray);
	}

	/** initializes an empty CCMenu */
	public boolean init() {
		return initWithArray(null);
	}

	/** initializes a CCMenu with a NSArray of CCMenuItem objects */
	public boolean initWithArray(ArrayList<CCMenuItem> pArrayOfItems) {
		if(super.init()) {
/* TODO
			setTouchPriority(kCCMenuHandlerPriority);
			setTouchMode(kCCTouchesOneByOne);
			setTouchEnabled(true);
*/
			// TODO legacy
			m_bTouchEnabled = true;

			m_bEnabled = true;
			// menu in the center of the screen
			CCSize s = CCDirector.sharedDirector().getWinSize();

			this.ignoreAnchorPointForPosition(true);
			setAnchorPoint(0.5f, 0.5f);
			this.setContentSize(s);

			setPosition(s.width/2, s.height/2);

			if(pArrayOfItems != null) {
				int z=0;
				for(CCMenuItem item : pArrayOfItems) {
					this.addChild(item, z);
					z++;
				}
			}

			//    [self alignItemsVertically];
			m_pSelectedItem = null;
			m_eState = tCCMenuState.kCCMenuStateWaiting;

			// enable cascade color and opacity on menus
			setCascadeColorEnabled(true);
			setCascadeOpacityEnabled(true);

			return true;
		}
		return false;
	}

	/** align items vertically */
	public void alignItemsVertically() {
		this.alignItemsVerticallyWithPadding(kDefaultPadding);
	}

	/** align items vertically with padding
	 * @since v0.7.2
	 */
	public void alignItemsVerticallyWithPadding(float padding) {
		float height = -padding;
		if((m_pChildren != null) && (m_pChildren.size()  > 0)) {
			for(CCNode pChild : m_pChildren) {
				height += pChild.getContentSize().height * pChild.getScaleY() + padding;
			}
		}

		float y = height / 2.0f;
		if((m_pChildren != null) && (m_pChildren.size()  > 0)) {
			for(CCNode pChild : m_pChildren) {
				pChild.setPosition(
						0,
						y - pChild.getContentSize().height * pChild.getScaleY() / 2.0f	);
				y -= pChild.getContentSize().height * pChild.getScaleY() + padding;
			}
		}
	}

	/** align items horizontally */
	public void alignItemsHorizontally() {
		this.alignItemsHorizontallyWithPadding(kDefaultPadding);
	}

	/** align items horizontally with padding
	 * @since v0.7.2
	 */
	public void alignItemsHorizontallyWithPadding(float padding) {
		float width = -padding;
		if((m_pChildren != null) && (m_pChildren.size() > 0)) {
			for (CCNode pChild: m_pChildren) {
				width += pChild.getContentSize().width * pChild.getScaleX() + padding;
			}
		}

		float x = -width / 2.0f;
		if((m_pChildren != null) && (m_pChildren.size() > 0)) {
			for (CCNode pChild : m_pChildren) {
				pChild.setPosition(x + pChild.getContentSize().width * pChild.getScaleX() / 2.0f, 0);
				x += pChild.getContentSize().width * pChild.getScaleX() + padding;
			}
		}
	}

	/** align items in rows of columns */
	public void alignItemsInColumns(int... columns) {
		ArrayList<Integer> rows = new ArrayList<Integer>();
		for(int n : columns) {
			rows.add(n);
		}
		alignItemsInColumnsWithArray(rows);
	}

	public void alignItemsInColumnsWithArray(ArrayList<Integer> rows) {

		int height = -5;
		int row = 0;
		int rowHeight = 0;
		int columnsOccupied = 0;
		int rowColumns;

		if((m_pChildren != null) && (m_pChildren.size() > 0)) {
			for(CCNode pChild : m_pChildren) {
				assert row < rows.size() : "Too many menu items for the amount of rows/columns.";

				rowColumns = rows.get(row);
				assert rowColumns != 0 : "Can't have zero columns on a row";

				rowHeight = Math.max(rowHeight, (int)(pChild.getContentSize().height));

				++columnsOccupied;
				if(columnsOccupied >= rowColumns) {
					height += rowHeight + 5;
	
					columnsOccupied = 0;
					rowHeight = 0;
					++row;
				}
			}
		}

		assert columnsOccupied != 0 : "check if too many rows/columns for available menu items.";

		CCSize winSize = CCDirector.sharedDirector().getWinSizeInPixels();

		row = 0;
		rowHeight = 0;
		rowColumns = 0;
		float w = 0.0f;
		float x = 0.0f;
		float y = height / 2;

		if((m_pChildren != null) && (m_pChildren.size() > 0)) {
			for(CCNode pChild : m_pChildren) {
				if(rowColumns == 0) {
					rowColumns = rows.get(row);
					w = winSize.width / (1 + rowColumns);
					x = w;
				}

				rowHeight = Math.max(rowHeight, (int)(pChild.getContentSize().height));

				pChild.setPosition(
						x - winSize.width / 2,
						y - pChild.getContentSize().height / 2	);

				x += w;
				++columnsOccupied;

				if(columnsOccupied >= rowColumns) {
					y -= rowHeight + 5;

					columnsOccupied = 0;
					rowColumns = 0;
					rowHeight = 0;
					++row;
				}
			}
		}
	}

	/** align items in columns of rows */
	public void alignItemsInRows(int... rows) {
		ArrayList<Integer> columns = new ArrayList<Integer>();
		for(int n : rows) {
			columns.add(n);
		}
		alignItemsInRowsWithArray(columns);
	}

	public void alignItemsInRowsWithArray(ArrayList<Integer> columns) {
		ArrayList<Integer> columnWidths = new ArrayList<Integer>();
		ArrayList<Integer> columnHeights = new ArrayList<Integer>();

		int width = -10;
		int columnHeight = -5;
		int column = 0;
		int columnWidth = 0;
		int rowsOccupied = 0;
		int columnRows;

		if((m_pChildren != null) && (m_pChildren.size() > 0)) {
			for(CCNode pChild : m_pChildren) {
				assert column < columns.size() : "check if too many menu items for the amount of rows/columns.";

				columnRows = columns.get(column);
				assert columnRows != 0 : "can't have zero rows on a column";

				columnWidth = Math.max(columnWidth, (int)(pChild.getContentSize().width));

				columnHeight += pChild.getContentSize().height + 5;
				++rowsOccupied;

				if (rowsOccupied >= columnRows) {
					columnWidths.add(columnWidth);
					columnHeights.add(columnHeight);
					width += columnWidth + 10;

					rowsOccupied = 0;
					columnWidth = 0;
					columnHeight = -5;
					++column;
				}
			}
		}

		assert rowsOccupied != 0 : "check if too many rows/columns for available menu items.";

		CCSize winSize = CCDirector.sharedDirector().getWinSizeInPixels();

		column = 0;
		columnWidth = 0;
		columnRows = 0;
		float x = -width / 2;
		float y = 0.0f;

		if((m_pChildren != null) && (m_pChildren.size() > 0)) {
			for(CCNode pChild : m_pChildren) {
				if(columnRows == 0) {
					columnRows = columns.get(column);
					y = columnHeights.get(column);
				}

				columnWidth = Math.max(columnWidth, (int)(pChild.getContentSize().width));

				pChild.setPosition(
						x + columnWidths.get(column) / 2,
						y - winSize.height / 2	);

				y -= pChild.getContentSize().height + 10;
				++rowsOccupied;

				if (rowsOccupied >= columnRows) {
					x += columnWidth + 5;
					rowsOccupied = 0;
					columnRows = 0;
					columnWidth = 0;
					++column;
				}
			}
		}
	}

	/** set event handler priority. By default it is: kCCMenuTouchPriority */
	public void setHandlerPriority(int newPriority) {
/* TODO
		CCTouchDispatcher pDispatcher = CCDirector.sharedDirector().getTouchDispatcher();
		pDispatcher.setPriority(newPriority, this);
*/
	}

	//super methods

	@Override
	public void addChild(CCNode child) {
		super.addChild(child);
	}

	@Override
	public void addChild(CCNode child, int zOrder) {
		super.addChild(child, zOrder);
	}

	@Override
	public void addChild(CCNode child, int zOrder, int tag) {
		assert (CCMenuItem)child != null : "Menu only supports MenuItem objects as children";
		super.addChild(child, zOrder, tag);
	}

	@Override
	public void registerWithTouchDispatcher() {
/* TODO
		CCDirector pDirector = CCDirector.sharedDirector();
		pDirector.getTouchDispatcher().addTargetedDelegate(this, this.getTouchPriority(), true);
*/
		CCTouchDispatcher.sharedDispatcher().addTargetedDelegate(this, ccMacros.INT_MIN+1, true);
	}

	@Override
	public void removeChild(CCNode child, boolean cleanup) {
		CCMenuItem pMenuItem = (CCMenuItem)child;
		assert pMenuItem != null : "Menu only supports MenuItem objects as children";

		if(m_pSelectedItem == pMenuItem) {
			m_pSelectedItem = null;
		}

		super.removeChild(child, cleanup);
	}

	/**
	 * @brief For phone event handle functions
	 */
	@Override
	public boolean ccTouchesBegan(MotionEvent event) {
		if((m_eState != tCCMenuState.kCCMenuStateWaiting) || (! m_bVisible) || (! m_bEnabled)) {
			return false;
		}

		for(CCNode c = this.m_pParent; c != null; c = c.getParent()) {
			if(c.isVisible() == false) {
				return false;
			}
		}

		m_pSelectedItem = this.itemForTouch(event);
		if(m_pSelectedItem != null) {
			m_eState = tCCMenuState.kCCMenuStateTrackingTouch;
			m_pSelectedItem.selected();
			return true;
		}
		return false;
	}

	public boolean ccTouchesEnded(MotionEvent event) {
		assert m_eState == tCCMenuState.kCCMenuStateTrackingTouch : "[Menu ccTouchEnded] -- invalid state";
/* TODO
		if(m_pSelectedItem != null) {
			m_pSelectedItem.unselected();
			m_pSelectedItem.activate();
		}
		m_eState = tCCMenuState.kCCMenuStateWaiting;
*/
		// TODO legacy -->
		if (m_eState == tCCMenuState.kCCMenuStateTrackingTouch) {
			if (m_pSelectedItem != null) {
				m_pSelectedItem.unselected();
				m_pSelectedItem.activate();
			}

			m_eState = tCCMenuState.kCCMenuStateWaiting;
			return CCTouchDispatcher.kEventHandled;
		}

		return CCTouchDispatcher.kEventIgnored;
	}

	public boolean ccTouchesCancelled(MotionEvent event) {
		assert m_eState == tCCMenuState.kCCMenuStateTrackingTouch : "[Menu ccTouchCancelled] -- invalid state";
/* TODO
		if(m_pSelectedItem != null) {
			m_pSelectedItem.unselected();
		}
		m_eState = tCCMenuState.kCCMenuStateWaiting;
*/
		// TODO legacy -->
		if (m_eState == tCCMenuState.kCCMenuStateTrackingTouch) {
			if (m_pSelectedItem != null) {
				m_pSelectedItem.unselected();
			}

			m_eState = tCCMenuState.kCCMenuStateWaiting;
			return CCTouchDispatcher.kEventHandled;
		}

		return CCTouchDispatcher.kEventIgnored;
	}

	public boolean ccTouchesMoved(MotionEvent event) {
		assert m_eState == tCCMenuState.kCCMenuStateTrackingTouch : "[Menu ccTouchMoved] -- invalid state";
/* TODO
		CCMenuItem currentItem = this.itemForTouch(event);
		if(currentItem != m_pSelectedItem) {
			if (m_pSelectedItem != null) {
				m_pSelectedItem.unselected();
			}
			m_pSelectedItem = currentItem;
			if(m_pSelectedItem != null) {
				m_pSelectedItem.selected();
			}
		}
*/
		// TODO legacy -->
		if (m_eState == tCCMenuState.kCCMenuStateTrackingTouch) {
			CCMenuItem currentItem = itemForTouch(event);

			if (currentItem != m_pSelectedItem) {
				if (m_pSelectedItem != null) {
					m_pSelectedItem.unselected();
				}
				m_pSelectedItem = currentItem;
				if (m_pSelectedItem != null) {
					m_pSelectedItem.selected();
				}
			}
			return CCTouchDispatcher.kEventHandled;
		}

		return CCTouchDispatcher.kEventIgnored;
	}

	/**
	 * @since v0.99.5
	 * override onExit
	 */
	@Override
	public void onExit() {
		if(m_eState == tCCMenuState.kCCMenuStateTrackingTouch) {
			if(m_pSelectedItem != null) {
				m_pSelectedItem.unselected();
				m_pSelectedItem = null;
			}

			m_eState = tCCMenuState.kCCMenuStateWaiting;
		}

		super.onExit();
	}

	@Override
	public void setOpacityModifyRGB(boolean bValue) {
	}

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

	public boolean isEnabled() {
		return m_bEnabled;
	}

	public void setEnabled(boolean value) {
		m_bEnabled = value;
	};

	protected CCMenuItem itemForTouch(MotionEvent event) {
		// TODO legacy -->
		if(m_pChildren == null)
			return null;

		PoolHolder holder = PoolHolder.getInstance();
		OneClassPool<CCPoint> pointPool = holder.getCCPointPool();
		OneClassPool<CCRect>  rectPool  = holder.getCCRectPool();

		CCPoint touchLocation = pointPool.get();
		CCPoint local		  = pointPool.get();
		CCRect  r			  = rectPool.get();

		CCMenuItem retItem = null;

		CCDirector.sharedDirector().convertToGL(event.getX(), event.getY(), touchLocation);

		for (int i = 0; i < m_pChildren.size(); i++) {
			CCMenuItem item = (CCMenuItem) m_pChildren.get(i);
			if (item.isVisible() && item.isEnabled()){
//                item.convertToNodeSpace(touchLocation.x, touchLocation.y, local);
				local = item.convertToNodeSpace(touchLocation);
				item.rect(r);
				r.origin.setPoint(0, 0);
				if (r.containsPoint(local)) {
					retItem = item;
					break;
				}
			}
		}

		pointPool.free(touchLocation);
		pointPool.free(local);
		rectPool.free(r);

		return retItem;
	}

	protected tCCMenuState m_eState;
	protected CCMenuItem m_pSelectedItem;

	private static final int kDefaultPadding = 5;

	// TODO legacy -->

    /** conforms to CCRGBAProtocol protocol */
//    private int        opacity_;
    /** conforms to CCRGBAProtocol protocol */
//    private ccColor3B   color_;

    /*
     * override add:
     */
    /** Override synthesized setOpacity to recurse items */
/*    public void setOpacity(int newOpacity) {
        opacity_ = newOpacity;
        if (m_pChildren != null) {
        	for (CCNode item: m_pChildren) {
        		((CCRGBAProtocol)item).setOpacity(opacity_);
        	}
        }
    }
    
    public int getOpacity() {
    	return opacity_;
    }

    public void setColor(ccColor3B color) {
        color_ = color;
        for (CCNode item: m_pChildren) {
            ((CCRGBAProtocol)item).setColor(color_);
        }
    }
*/
}

// end of GUI group
/// @}
/// @}
