/*
 * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 
 *  o Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer. 
 *     
 *  o Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution. 
 *     
 *  o Neither the name of Flamingo Kirill Grouchnikov nor the names of 
 *    its contributors may be used to endorse or promote products derived 
 *    from this software without specific prior written permission. 
 *     
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
package org.pushingpixels.flamingo.api.ribbon;

import java.awt.event.ActionListener;
import java.util.*;

import org.pushingpixels.flamingo.api.common.JCommandMenuButton;
import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenuEntryPrimary.PrimaryRolloverCallback;

/**
 * Metadata description of the application menu of the {@link JRibbon}
 * component. The ribbon application menu has three parts:
 * 
 * <pre>
 * +-------------------------------------+
 * |           |                         |
 * |           |                         |
 * |  primary  |        secondary        |
 * |   area    |           area          |        
 * |           |                         |
 * |           |                         |
 * |-------------------------------------|
 * |            footer area              |
 * +-------------------------------------+
 * </pre>
 * 
 * <p>
 * The entries in the primary area are always visible. The secondary area
 * entries are shown based on the currently active element in the primary area.
 * There are three different types of primary entries:
 * </p>
 * 
 * <ul>
 * <li>Associated {@link ActionListener} passed to the constructor of the
 * {@link RibbonApplicationMenuEntryPrimary}. When this entry is armed (with
 * mouse rollover or via keyboard navigation), the contents of the secondary
 * area are cleared. The <code>Quit</code> menu item is an example of such a
 * primary menu entry.</li>
 * <li>Associated {@link PrimaryRolloverCallback} set by the
 * {@link RibbonApplicationMenuEntryPrimary#setRolloverCallback(PrimaryRolloverCallback)}
 * . When this entry is armed (with mouse rollover or via keyboard navigation),
 * the contents of the secondary area are populated by the application callback
 * implementation of
 * {@link PrimaryRolloverCallback#menuEntryActivated(javax.swing.JPanel)}. The
 * <code>Open</code> menu item is an example of such a primary menu entry,
 * showing a list of recently opened files.</li>
 * <li>Associated list of {@link RibbonApplicationMenuEntrySecondary}s added
 * with the
 * {@link RibbonApplicationMenuEntryPrimary#addSecondaryMenuGroup(String, RibbonApplicationMenuEntrySecondary...)}
 * API. When this entry is armed (with mouse rollover or via keyboard
 * navigation), the secondary area shows menu buttons for the registered
 * secondary menu entries. The <code>Save As</code> menu item is an example of
 * such a primary menu item, showing a list of default save formats.</li>
 * </ul>
 * 
 * <p>
 * At runtime, the application menu entries are implemented as
 * {@link JCommandMenuButton}, but the application code does not operate on that
 * level. Instead, the application code creates metadata-driven description of
 * the ribbon application menu, and that description is used to create and
 * populate the "real" controls of the application menu popup.
 * </p>
 * 
 * <p>
 * Note that once a {@link RibbonApplicationMenu} is set on the {@link JRibbon}
 * with the {@link JRibbon#setApplicationMenu(RibbonApplicationMenu)}, its
 * contents cannot be changed. An {@link IllegalStateException} will be thrown
 * from {@link #addMenuEntry(RibbonApplicationMenuEntryPrimary)} and
 * {@link #addFooterEntry(RibbonApplicationMenuEntryFooter)}.
 * </p>
 * 
 * @author Kirill Grouchnikov
 */
public class RibbonApplicationMenu {
	/**
	 * Indicates whether this ribbon application menu has been set on the
	 * {@link JRibbon} with the
	 * {@link JRibbon#setApplicationMenu(RibbonApplicationMenu)}. Once that API
	 * is called, the contents of this menu cannot be changed. An
	 * {@link IllegalStateException} will be thrown from
	 * {@link #addMenuEntry(RibbonApplicationMenuEntryPrimary)} and
	 * {@link #addFooterEntry(RibbonApplicationMenuEntryFooter)}.
	 * 
	 * @see #setFrozen(boolean)
	 * @see #addMenuEntry(RibbonApplicationMenuEntryPrimary)
	 * @see #addFooterEntry(RibbonApplicationMenuEntryFooter)
	 */
	private boolean isFrozen;

	/**
	 * Primary menu entries.
	 */
	private List<List<RibbonApplicationMenuEntryPrimary>> primaryEntries;

	/**
	 * Footer menu entries.
	 */
	private List<RibbonApplicationMenuEntryFooter> footerEntries;

	/**
	 * The default callback to be called when:
	 * 
	 * <ul>
	 * <li>The ribbon application menu is first shown.</li>
	 * <li>The currently active (rollover) primary application menu entry has no
	 * secondary menu entries and no associated rollover callback.
	 * </ul>
	 */
	private PrimaryRolloverCallback defaultCallback;

	/**
	 * Creates an empty ribbon application menu.
	 */
	public RibbonApplicationMenu() {
		this.primaryEntries = new ArrayList<List<RibbonApplicationMenuEntryPrimary>>();
		this.primaryEntries
				.add(new ArrayList<RibbonApplicationMenuEntryPrimary>());
		this.footerEntries = new ArrayList<RibbonApplicationMenuEntryFooter>();
	}

	/**
	 * Adds the specified primary menu entry.
	 * 
	 * @param entry
	 *            Primary menu entry to add.
	 * @throws IllegalStateException
	 *             if this ribbon application menu has already been set on the
	 *             {@link JRibbon} with the
	 *             {@link JRibbon#setApplicationMenu(RibbonApplicationMenu)}.
	 * @see #getPrimaryEntries()
	 * @see #addFooterEntry(RibbonApplicationMenuEntryFooter)
	 */
	public synchronized void addMenuEntry(
			RibbonApplicationMenuEntryPrimary entry) {
		if (this.isFrozen) {
			throw new IllegalStateException(
					"Cannot add menu entries after the menu has been set on the ribbon");
		}
		this.primaryEntries.get(this.primaryEntries.size() - 1).add(entry);
	}

	public synchronized void addMenuSeparator() {
		if (this.isFrozen) {
			throw new IllegalStateException(
					"Cannot add menu entries after the menu has been set on the ribbon");
		}
		this.primaryEntries
				.add(new ArrayList<RibbonApplicationMenuEntryPrimary>());
	}

	/**
	 * Returns an unmodifiable list of all primary menu entries of this
	 * application menu. The result is guaranteed to be non-<code>null</code>.
	 * 
	 * @return An unmodifiable list of all primary menu entries of this
	 *         application menu.
	 * @see #addMenuEntry(RibbonApplicationMenuEntryPrimary)
	 * @see #getFooterEntries()
	 */
	public List<List<RibbonApplicationMenuEntryPrimary>> getPrimaryEntries() {
		return Collections.unmodifiableList(this.primaryEntries);
	}

	/**
	 * Adds the specified footer menu entry.
	 * 
	 * @param entry
	 *            Footer menu entry to add.
	 * @throws IllegalStateException
	 *             if this ribbon application menu has already been set on the
	 *             {@link JRibbon} with the
	 *             {@link JRibbon#setApplicationMenu(RibbonApplicationMenu)}.
	 * @see #getFooterEntries()
	 * @see #addMenuEntry(RibbonApplicationMenuEntryPrimary)
	 */
	public synchronized void addFooterEntry(
			RibbonApplicationMenuEntryFooter entry) {
		if (this.isFrozen) {
			throw new IllegalStateException(
					"Cannot add footer entries after the menu has been set on the ribbon");
		}
		this.footerEntries.add(entry);
	}

	/**
	 * Returns an unmodifiable list of all footer menu entries of this
	 * application menu. The result is guaranteed to be non-<code>null</code>.
	 * 
	 * @return An unmodifiable list of all footer menu entries of this
	 *         application menu.
	 * @see #addFooterEntry(RibbonApplicationMenuEntryFooter)
	 * @see #getPrimaryEntries()
	 */
	public List<RibbonApplicationMenuEntryFooter> getFooterEntries() {
		return Collections.unmodifiableList(this.footerEntries);
	}

	/**
	 * Sets the default callback to be called when:
	 * 
	 * <ul>
	 * <li>The ribbon application menu is first shown.</li>
	 * <li>The currently active (rollover) primary application menu entry has no
	 * secondary menu entries and no associated rollover callback.
	 * </ul>
	 * 
	 * @param defaultCallback
	 *            Default callback.
	 */
	public void setDefaultCallback(PrimaryRolloverCallback defaultCallback) {
		this.defaultCallback = defaultCallback;
	}

	/**
	 * Returns the default callback of this ribbon application menu.
	 * 
	 * @return The default callback of this ribbon application menu.
	 * @see #setDefaultCallback(PrimaryRolloverCallback)
	 */
	public PrimaryRolloverCallback getDefaultCallback() {
		return defaultCallback;
	}

	/**
	 * Marks this application menu as frozen. Subsequent calls to
	 * {@link #addMenuEntry(RibbonApplicationMenuEntryPrimary)} and
	 * {@link #addFooterEntry(RibbonApplicationMenuEntryFooter)} will throw an
	 * {@link IllegalStateException}.
	 * 
	 * @see #addMenuEntry(RibbonApplicationMenuEntryPrimary)
	 * @see #addFooterEntry(RibbonApplicationMenuEntryFooter)
	 * @see JRibbon#setApplicationMenu(RibbonApplicationMenu)
	 */
	synchronized void setFrozen() {
		this.isFrozen = true;
		if (this.primaryEntries.get(this.primaryEntries.size() - 1).isEmpty()) {
			this.primaryEntries.remove(this.primaryEntries.size() - 1);
		}
	}
}
