// Copyright 2006 Konrad Twardowski
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.makagiga.commons.swing;

import java.awt.event.MouseEvent;
import java.io.Serializable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.JTable;

import org.makagiga.commons.MAction;
import org.makagiga.commons.MDisposable;
import org.makagiga.commons.TK;
import org.makagiga.commons.WTFError;
import org.makagiga.commons.swing.event.MMouseAdapter;

/**
 * @since 1.2, 4.0 (org.makagiga.commons.swing package)
 */
public class ActionGroup
implements
	Iterable<ActionGroup.Item>,
	MDisposable,
	Serializable
{
	
	// public
	
	public enum Type {
		ACTION,
		SEPARATOR,
		STRETCH,
	
		/**
		 * @since 3.6
		 */
		CHECK_BOX
	}
	
	// private

	private int genericID;
	private final Map<String, Item> map = new LinkedHashMap<>();
	private String groupID;
	private String groupName;
	
	// public
	
	public ActionGroup() { }
	
	/**
	 * @since 2.0
	 *
	 * @throws IllegalArgumentException If @p id is invalid
	 */
	public ActionGroup(final String id, final String name) {
		groupID = TK.validateID(id);
		groupName = name;
	}

	/**
	 * @since 5.0
	 */
	public void add(final JMenu menu, final String... actionIDs) {
		for (String i : actionIDs) {
			if (i.equals("-")) {
				menu.addSeparator();
			}
			else {
				Action a = getAction(i);
				menu.add(a);
			}
		}
	}

	public Item add(final String id, final Action action) {
		TK.validateID(id);
		Item item = new Item(id, action, Type.ACTION);
		map.put(id, item);
		
		return item;
	}

	/**
	 * @since 3.6
	 */
	public Item addCheckBox(final String id, final Action action) {
		TK.validateID(id);
		Item item = new Item(id, action, Type.CHECK_BOX);
		map.put(id, item);

		return item;
	}

	public Item addSeparator() {
		Item item = new Item(null, null, Type.SEPARATOR);
		map.put(Type.SEPARATOR.name() + "." + genericID++, item);
		
		return item;
	}

	public Item addStretch() {
		Item item = new Item(null, null, Type.STRETCH);
		map.put(Type.STRETCH.name() + "." + genericID++, item);
		
		return item;
	}

	public void clear() {
		map.clear();
	}

	/**
	 * @since 2.0
	 */
	public void connect(final String id, final JComponent component, final int condition) {
		MAction.connect(component, condition, getAction(id));
	}

	public void connect(final String id, final JComponent component, final String actionName) {
		MAction.connect(component, actionName, getAction(id));
	}

	/**
	 * @since 3.8.1
	 */
	public void disconnect(final String id, final JComponent component, final int condition) {
		Action a = getAction(id);
		
		if (!(a instanceof MAction))
			throw new IllegalArgumentException("\"MAction\" class expected: " + id);

		MAction.class.cast(a).disconnect(component, condition);
	}

	/**
	 * Returns a new menu with actions.
	 * 
	 * @see #updateMenu(MMenu)
	 * @see #createPopupMenu()
	 */
	public MMenu createMenu() {
		MMenu menu = new MMenu();
		updateMenu(menu);
		
		return menu;
	}

	/**
	 * @since 3.0
	 */
	public MMenu createMenu(final String text) {
		MMenu menu = new MMenu(text);
		updateMenu(menu);
		
		return menu;
	}

	/**
	 * @since 4.2
	 */
	public MMenu createMenu(final String text, final String iconName) {
		MMenu menu = new MMenu(text, iconName);
		updateMenu(menu);
		
		return menu;
	}

	/**
	 * Returns a new popup menu with actions.
	 * 
	 * @see #createMenu()
	 * 
	 * @since 2.2
	 */
	public JPopupMenu createPopupMenu() {
		MMenu menu = createMenu();
		
		return menu.getPopupMenu();
	}
	
	public MToolBar createToolBar() {
		return createToolBar(MToolBar.HORIZONTAL);
	}
	
	/**
	 * @since 2.0
	 */
	public MToolBar createToolBar(final int orientation) {
		MToolBar toolBar = new MToolBar(orientation);
		updateToolBar(toolBar);
		
		return toolBar;
	}
	
	/**
	 * @since 2.0
	 */
	public void fire(final String id) {
		fire(id, MAction.NO_SOURCE);
	}

	/**
	 * @since 3.0
	 */
	public void fire(final String id, final Object source) {
		MAction.fire(getAction(id), source);
	}

	@SuppressWarnings("unchecked")
	public <T extends Action> T getAction(final String id) {
		return (T)map.get(id).action;
	}

	/**
	 * @since 3.2
	 */
	public ActionGroup.Item getItem(final String id) {
		return map.get(id);
	}

	/**
	 * @since 2.0
	 */
	public String getID() { return groupID; }

	/**
	 * @since 2.0
	 */
	public String getName() { return groupName; }
	
	/**
	 * @since 3.2
	 */
	public JPopupMenu installPopupMenu(final JComponent c) {
		final JPopupMenu popupMenu = createPopupMenu();

		// HACK: select item under mouse cursor
		if (c instanceof JTable) {
			c.addMouseListener(new MMouseAdapter() {
				@Override
				public void popupTrigger(final MouseEvent e) {
					MTable.adjustSelection(e);
					MMenu.showPopup(popupMenu, e.getComponent(), e.getX(), e.getY());
				}
			} );
		}
		else {
			c.setComponentPopupMenu(popupMenu);
		}

		return popupMenu;
	}
	
	/**
	 * @since 4.2
	 */
	public boolean isEmpty() {
		return map.isEmpty();
	}
	
	/**
	 * @since 2.0
	 */
	public boolean isEnabled(final String id) {
		return getAction(id).isEnabled();
	}
	
	public void setEnabled(final String id, final boolean enabled) {
		getAction(id).setEnabled(enabled);
	}

	/**
	 * @since 3.0
	 */
	public boolean isSelected(final String id) {
		return MAction.getValue(getAction(id), Action.SELECTED_KEY, false);
	}

	public void setSelected(final String id, final boolean selected) {
		getAction(id).putValue(Action.SELECTED_KEY, selected);
	}

	public void setEnabled(final boolean enabled) {
		for (Item i : this) {
			if (i.action != null)
				i.action.setEnabled(enabled);
		}
	}

	/**
	 * @since 4.2
	 */
	public void setEnabled(final boolean enabled, final String... id) {
		for (String i : id)
			setEnabled(i, enabled);
	}

	/**
	 * Updates action properties (e.g. @c Action.SELECTED_KEY).
	 *
	 * - Usually invoked by user.
	 * - The default implementation does nothing.
	 *
	 * @since 3.0
	 */
	public void update() { }

	public void updateMenu(final MMenu menu) {
		for (Item i : this) {
			if (!i.visibleInMenu)
				continue; // for

			switch (i.type) {
				case ACTION:
					menu.add(i.action);
					break;
				case CHECK_BOX:
					menu.addCheckBox(i.action);
					break;
				case SEPARATOR:
				case STRETCH:
					menu.addSeparator();
					break;
				default:
					throw new WTFError(i.type);
			}
		}
	}

	public void updateToolBar(final MToolBar toolBar) {
		for (Item i : this) {
			if (!i.visibleInToolBar)
				continue; // for

			switch (i.type) {
				case ACTION:
					toolBar.add(i.action, i.showTextInToolBar ? MToolBar.SHOW_TEXT : 0);
					break;
				case CHECK_BOX:
					MToggleButton button = new MToggleButton(i.action);
					toolBar.addButton(button, i.showTextInToolBar ? MToolBar.SHOW_TEXT : 0);
					break;
				case SEPARATOR:
					toolBar.addSeparator();
					break;
				case STRETCH:
					toolBar.addStretch();
					break;
				default:
					throw new WTFError(i.type);
			}
		}
	}
	
	// Iterable
	
	/**
	 * @since 3.0
	 */
	@Override
	public Iterator<Item> iterator() {
		return map.values().iterator();
	}

	// MDisposable
	
	/**
	 * @since 4.0
	 */
	@Override
	public void dispose() {
		clear();
	}

	// public classes
	
	public static final class Item implements Serializable {
		
		// private
		
		private final Action action;
		private boolean showTextInToolBar;
		private boolean visibleInMenu = true;
		private boolean visibleInToolBar = true;
		private final String id;
		private final Type type;
		
		// public
		
		public Action getAction() { return action; }
		
		public String getID() { return id; }
		
		public boolean getShowTextInToolBar() { return showTextInToolBar; }
		
		public void setShowTextInToolBar(final boolean value) { showTextInToolBar = value; }
		
		public Type getType() { return type; }
		
		public boolean isVisibleInMenu() { return visibleInMenu; }
		
		public void setVisibleInMenu(final boolean value) { visibleInMenu = value; }

		public boolean isVisibleInToolBar() { return visibleInToolBar; }
		
		public void setVisibleInToolBar(final boolean value) { visibleInToolBar = value; }

		// private
		
		private Item(final String id, final Action action, final Type type) {
			this.id = id;
			this.action = action;
			this.type = type;
		}
		
	}
	
}
