/**
 * Copyright (c) 2001-2016 Mathew A. Nelson and Robocode contributors
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://robocode.sourceforge.net/license/epl-v10.html
 */
package net.sf.robocode.ui.editor;


import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.File;


/**
 * Customized JMenuItem where each item is bound to a specific JInternalFrame,
 * so we can have a dynamic menu of open windows.
 *
 * @author Matthew Reeder (original)
 */
@SuppressWarnings("serial")
public class WindowMenuItem extends JCheckBoxMenuItem implements ActionListener {

	// Maximum number of windows that will be shown on the menu (to get the rest, you'll
	// have to open the dialog).  The number 9 is also the number of most recently used
	// files that normally show up in other applications.  The reason is so that you can
	// give them dynamic hotkeys from 1 to 9.  Otherwise, there's no reason (besides
	// avoiding taking up way too much space) to limit the size of the menu.
	public static final int WINDOW_MENU_MAX_SIZE = 9;
	// Number of static menu items before the dynamic menu (including seperators)
	public static final int PRECEDING_WINDOW_MENU_ITEMS = 3;
	// Number of static menu items after the dynamic menu (including seperators
	// and the More Windows... menu item)
	public static final int SUBSEQUENT_WINDOW_MENU_ITEMS = 1;
	// Normal max length of a window name
	public static final int MAX_WINDOW_NAME_LENGTH = 30;
	// I make one "special" menu item that isn't tied to a window. Since it has
	// similar needs for enabling/visibility and labeling, I made it the same class.
	public static final int REGULAR_WINDOW = 0, SPECIAL_MORE = 2;
	private EditWindow window;
	private JMenu parentMenu;
	private final int type;

	public WindowMenuItem(EditWindow window, JMenu parentMenu) {
		super();
		this.window = window;
		this.parentMenu = parentMenu;
		type = REGULAR_WINDOW;
		parentMenu.add(this, parentMenu.getMenuComponentCount() - SUBSEQUENT_WINDOW_MENU_ITEMS);
		addActionListener(this);
	}

	/**
	 * WindowMenuItem Constructor for "More Windows..." menu.
	 */
	public WindowMenuItem() {
		type = SPECIAL_MORE;
	}

	/**
	 * Event handler for the menu item
	 * <p/>
	 * Brings the window to the front. This should be called for the "More
	 * Windows..." Item, because it doesn't make itself its own ActionListener.
	 * <p/>
	 * Note that e can be null, and this menu item might not be showing (if this
	 * is called from the "More Windows" dialog).
	 */
	public void actionPerformed(ActionEvent e) {
		if (window.isIcon()) {
			try {
				window.setIcon(false);
			} catch (Throwable ignored) {}
		}
		if (window.getDesktopPane() != null) {
			window.getDesktopPane().setSelectedFrame(window);
		}
		window.toFront();
		window.grabFocus();
		try {
			window.setSelected(true);
		} catch (Throwable ignored) {}
	}

	/**
	 * Returns the label that should be used. If the menu item is supposed to be
	 * hidden, this may not be a real valid label.
	 */
	@Override
	public String getText() {
		if (type == SPECIAL_MORE) {
			Container parent = getParent();

			if (parent == null) {
				return "";
			}

			int numWindows = parent.getComponentCount() - PRECEDING_WINDOW_MENU_ITEMS - SUBSEQUENT_WINDOW_MENU_ITEMS;

			if (numWindows <= 0) {
				return "No Windows Open";
			}

			return "More Windows...";
		}
		if (window == null || parentMenu == null) {
			return "";
		}
		String text = (getIndex() + 1) + " " + getFileName();

		if (window.modified) {
			text += " *";
		}
		return text;
	}

	protected String getFileName() {
		if (window.getFileName() == null) {
			return "Untitled " + (getPrecedingNewFiles() + 1);
		}

		String name = window.getFileName();

		if (name.length() < MAX_WINDOW_NAME_LENGTH) {
			return name;
		}
		if (name.indexOf(File.separatorChar) < 0) {
			return name;
		} // If there are no separators, I can't really intelligently truncate.
		int startLength = name.indexOf(File.separatorChar, 1) + 1;
		int endLength = name.length() - name.lastIndexOf(File.separatorChar);

		if (endLength + startLength + 3 > name.length()) {
			return name;
		} // return name anyways, since we're not getting it any shorter.

		boolean change;

		do {
			change = false;
			int newEndLength = name.length() - name.lastIndexOf(File.separatorChar, name.length() - endLength - 1);

			if (newEndLength + startLength + 3 <= MAX_WINDOW_NAME_LENGTH) {
				endLength = newEndLength;
				change = true;
			}
			int newStartLength = name.indexOf(File.separatorChar, startLength + 1) + 1;

			if (endLength + startLength + 3 <= MAX_WINDOW_NAME_LENGTH) {
				startLength = newStartLength;
				change = true;
			}
		} while (change);

		return name.substring(0, startLength) + "..." + name.substring(name.length() - endLength);
	}

	/**
	 * @return how many nameless windows occur before this one in the parent.
	 */
	protected int getPrecedingNewFiles() {
		int count = 0;

		for (int i = 0; i < WINDOW_MENU_MAX_SIZE
				&& i < parentMenu.getMenuComponentCount() - PRECEDING_WINDOW_MENU_ITEMS - SUBSEQUENT_WINDOW_MENU_ITEMS
				&& parentMenu.getMenuComponent(i + PRECEDING_WINDOW_MENU_ITEMS) != this; i++) {
			if (parentMenu.getMenuComponent(i + PRECEDING_WINDOW_MENU_ITEMS) instanceof WindowMenuItem
					&& ((WindowMenuItem) parentMenu.getMenuComponent(i + PRECEDING_WINDOW_MENU_ITEMS)).window.getFileName()
							== null) {
				count++;
			}
		}
		return count;
	}

	/**
	 * Figures out what index (from 0 to WINDOW_MENU_MAX_SIZE-1) this item is in
	 * the window menu.
	 * <p/>
	 * @return -1 if this item isn't showing.
	 */
	protected int getIndex() {
		for (int i = 0; i < WINDOW_MENU_MAX_SIZE
				&& i < parentMenu.getMenuComponentCount() - PRECEDING_WINDOW_MENU_ITEMS - SUBSEQUENT_WINDOW_MENU_ITEMS; i++) {
			if (this == parentMenu.getMenuComponent(i + PRECEDING_WINDOW_MENU_ITEMS)) {
				return i;
			}
		}
		return -1;
	}

	/**
	 * Returns the index of the character in the label that should be underlined
	 */
	@Override
	public int getDisplayedMnemonicIndex() {
		return (type == SPECIAL_MORE) ? 11 : 0;
	}

	/**
	 * Returns the keyboard mnemonic for this item, which is the virtual key
	 * code for its 1-based index.
	 */
	@Override
	public int getMnemonic() {
		return (type == SPECIAL_MORE) ? KeyEvent.VK_S : KeyEvent.VK_1 + getIndex();
	}

	/**
	 * Returns true if this item should be showing.
	 * <p/>
	 * Returns false if there are more than WINDOW_MENU_MAX_SIZE items before it
	 * in the menu.
	 */
	@Override
	public boolean isVisible() {
		if (type == SPECIAL_MORE) {
			Container parent = getParent();

			if (parent == null) {
				return true;
			}
			int numWindows = parent.getComponentCount() - PRECEDING_WINDOW_MENU_ITEMS - SUBSEQUENT_WINDOW_MENU_ITEMS;

			updateSelection();
			return (numWindows <= 0) || (numWindows > WINDOW_MENU_MAX_SIZE);
		}
		updateSelection();
		return getIndex() >= 0;
	}

	/**
	 * Returns true if this item should be enabled (selectable).
	 * <p/>
	 * Returns false if it is a More Windows... item and there are no windows.
	 */
	@Override
	public boolean isEnabled() {
		if (type == SPECIAL_MORE) {
			Container parent = getParent();

			if (parent == null) {
				return true;
			}
			int numWindows = parent.getComponentCount() - PRECEDING_WINDOW_MENU_ITEMS - SUBSEQUENT_WINDOW_MENU_ITEMS;

			return (numWindows > 0);
		}
		return true;
	}

	/**
	 * Determines if this menu item should currently show as "selected".
	 * <p/>
	 * The item should be seleced if the window it's tied to has focus.
	 */
	@Override
	public boolean isSelected() {
		return (type != SPECIAL_MORE) && (window != null && window.getDesktopPane() != null)
				&& window.getDesktopPane().getSelectedFrame() == window;
	}

	/**
	 * Makes sure the underlying menu item knows if we're selected.
	 */
	public void updateSelection() {
		setSelected(isSelected()); // Sort of a silly thing to do...
		setEnabled(isEnabled());
	}

	/**
	 * @return the EditWindow that this menu item is tied to.
	 */
	public EditWindow getEditWindow() {
		return window;
	}

	/**
	 * Creates a string representation of this object.
	 * <p/>
	 * Handy for repurposing the menu items as list items :-)
	 */
	@Override
	public String toString() {
		return (type == SPECIAL_MORE) ? "" : getFileName();
	}
}
