/*
 *  Freeplane - mind map editor
 *  Copyright (C) 2008 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitry Polivaev
 *
 *  @author <a href="mailto:labe@users.sourceforge.net">Lars Berning</a>
 *  This file is modified by Dimitry Polivaev in 2008.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.freeplane.core.ui.components;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;
import javax.swing.border.BevelBorder;

import org.freeplane.core.resources.ResourceController;
import org.freeplane.core.util.TextUtils;
import org.freeplane.features.icon.IIconInformation;

public class IconSelectionPopupDialog extends JDialog implements KeyListener, MouseListener {
	static class Position {
		final private int x, y;

		public Position(final int x, final int y) {
			this.x = x;
			this.y = y;
		}

		/**
		 * @return Returns the x.
		 */
		public int getX() {
			return x;
		}

		/**
		 * @return Returns the y.
		 */
		public int getY() {
			return y;
		}

		@Override
		public String toString() {
			return ("(" + getX() + "," + getY() + ")");
		}
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	final private JLabel descriptionLabel;
	final private JLabel[] iconLabels;
	final private JPanel iconPanel = new JPanel();
	final private List<? extends IIconInformation> icons;
	private int mModifiers;
	final private int numOfIcons;
	private int result;
	private Position selected = new Position(0, 0);
	final private int xDimension;
	private int yDimension;

	public IconSelectionPopupDialog(final Frame frame, final List<? extends IIconInformation> icons) {
		super(frame, TextUtils.getText("select_icon"));
		getContentPane().setLayout(new BorderLayout());
		this.icons = icons;
		setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(final WindowEvent we) {
				close();
			}
		});
		numOfIcons = icons.size();
		xDimension = (int) Math.ceil(Math.sqrt(numOfIcons));
		if (numOfIcons <= xDimension * (xDimension - 1)) {
			yDimension = xDimension - 1;
		}
		else {
			yDimension = xDimension;
		}
		final GridLayout gridlayout = new GridLayout(0, xDimension);
		gridlayout.setHgap(3);
		gridlayout.setVgap(3);
		iconPanel.setLayout(gridlayout);
		iconLabels = new JLabel[numOfIcons];
		for (int i = 0; i < numOfIcons; ++i) {
			final IIconInformation icon = icons.get(i);
			iconPanel.add(iconLabels[i] = new JLabel(icon.getIcon()));
			iconLabels[i].setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
			iconLabels[i].addMouseListener(this);
		}
		final int perIconSize = 27;
		iconPanel.setPreferredSize(new Dimension(xDimension * perIconSize, yDimension * perIconSize));
		iconPanel.setMinimumSize(new Dimension(xDimension * perIconSize, yDimension * perIconSize));
		iconPanel.setMaximumSize(new Dimension(xDimension * perIconSize, yDimension * perIconSize));
		iconPanel.setSize(new Dimension(xDimension * perIconSize, yDimension * perIconSize));
		getContentPane().add(iconPanel, BorderLayout.CENTER);
		descriptionLabel = new JLabel(" ");
		getContentPane().add(descriptionLabel, BorderLayout.SOUTH);
		setSelectedPosition(new Position(0, 0));
		select(getSelectedPosition());
		addKeyListener(this);
		pack();
	}

	private void addIcon(final int pModifiers) {
		result = calculateIndex(getSelectedPosition());
		mModifiers = pModifiers;
		this.dispose();
	}

	private int calculateIndex(final Position position) {
		return position.getY() * xDimension + position.getX();
	}

	private boolean canSelect(final Position position) {
		return ((position.getX() >= 0) && (position.getX() < xDimension) && (position.getY() >= 0)
		        && (position.getY() < yDimension) && (calculateIndex(position) < numOfIcons));
	}

	private void close() {
		result = -1;
		mModifiers = 0;
		this.dispose();
	}

	private void cursorDown() {
		final Position newPosition = new Position(getSelectedPosition().getX(), getSelectedPosition().getY() + 1);
		if (canSelect(newPosition)) {
			select(newPosition);
		}
	}

	private void cursorLeft() {
		final Position newPosition = new Position(getSelectedPosition().getX() - 1, getSelectedPosition().getY());
		if (canSelect(newPosition)) {
			select(newPosition);
		}
	}

	private void cursorRight() {
		final Position newPosition = new Position(getSelectedPosition().getX() + 1, getSelectedPosition().getY());
		if (canSelect(newPosition)) {
			select(newPosition);
		}
	}

	private void cursorUp() {
		final Position newPosition = new Position(getSelectedPosition().getX(), getSelectedPosition().getY() - 1);
		if (canSelect(newPosition)) {
			select(newPosition);
		}
	}

	private int findIndexByKeyEvent(final KeyEvent keyEvent) {
		for (int i = 0; i < icons.size(); i++) {
			final IIconInformation info = icons.get(i);
			final KeyStroke iconKeyStroke = info.getKeyStroke();
			if (iconKeyStroke != null
			        && (keyEvent.getKeyCode() == iconKeyStroke.getKeyCode()
			                && keyEvent.getKeyCode() != 0
			                && (iconKeyStroke.getModifiers() & InputEvent.SHIFT_MASK) == (keyEvent.getModifiers() & InputEvent.SHIFT_MASK) || keyEvent
			            .getKeyChar() == iconKeyStroke.getKeyChar()) && keyEvent.getKeyChar() != 0
			        && keyEvent.getKeyChar() != KeyEvent.CHAR_UNDEFINED) {
				return i;
			}
		}
		return -1;
	}

	/**
	 * Transfer shift masks from InputEvent to ActionEvent. But, why don't they
	 * use the same constants???? Java miracle.
	 */
	public int getModifiers() {
		int m = mModifiers;
		if ((mModifiers & (ActionEvent.SHIFT_MASK | InputEvent.SHIFT_DOWN_MASK)) != 0) {
			m |= ActionEvent.SHIFT_MASK;
		}
		if ((mModifiers & (ActionEvent.CTRL_MASK | InputEvent.CTRL_DOWN_MASK)) != 0) {
			m |= ActionEvent.CTRL_MASK;
		}
		if ((mModifiers & (ActionEvent.META_MASK | InputEvent.META_DOWN_MASK)) != 0) {
			m |= ActionEvent.META_MASK;
		}
		if ((mModifiers & (ActionEvent.ALT_MASK | InputEvent.ALT_DOWN_MASK)) != 0) {
			m |= ActionEvent.ALT_MASK;
		}
		return m;
	}

	private Position getPosition(final JLabel caller) {
		int index = 0;
		for (index = 0; index < iconLabels.length; index++) {
			if (caller == iconLabels[index]) {
				break;
			}
		}
		return new Position(index % xDimension, index / xDimension);
	}

	public int getResult() {
		return result;
	}

	private Position getSelectedPosition() {
		return selected;
	}

	private void highlight(final Position position) {
		iconLabels[calculateIndex(position)].setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
	}

	/*
	 * (non-Javadoc)
	 * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
	 */
	public void keyPressed(final KeyEvent keyEvent) {
		switch (keyEvent.getKeyCode()) {
			case KeyEvent.VK_RIGHT:
			case KeyEvent.VK_KP_RIGHT:
				cursorRight();
				return;
			case KeyEvent.VK_LEFT:
			case KeyEvent.VK_KP_LEFT:
				cursorLeft();
				return;
			case KeyEvent.VK_DOWN:
			case KeyEvent.VK_KP_DOWN:
				cursorDown();
				return;
			case KeyEvent.VK_UP:
			case KeyEvent.VK_KP_UP:
				cursorUp();
				return;
			case KeyEvent.VK_ESCAPE:
				keyEvent.consume();
				close();
				return;
			case KeyEvent.VK_ENTER:
			case KeyEvent.VK_SPACE:
				keyEvent.consume();
				addIcon(keyEvent.getModifiers());
				return;
		}
		final int index = findIndexByKeyEvent(keyEvent);
		if (index != -1) {
			result = index;
			mModifiers = keyEvent.getModifiers();
			keyEvent.consume();
			this.dispose();
		}
	}

	/*
	 * (non-Javadoc)
	 * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
	 */
	public void keyReleased(final KeyEvent arg0) {
	}

	/*
	 * (non-Javadoc)
	 * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
	 */
	public void keyTyped(final KeyEvent arg0) {
	}

	/*
	 * (non-Javadoc)
	 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
	 */
	public void mouseClicked(final MouseEvent mouseEvent) {
		addIcon(mouseEvent.getModifiers());
	}

	/*
	 * (non-Javadoc)
	 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
	 */
	public void mouseEntered(final MouseEvent arg0) {
		select(getPosition((JLabel) arg0.getSource()));
	}

	/*
	 * (non-Javadoc)
	 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
	 */
	public void mouseExited(final MouseEvent arg0) {
	}

	/*
	 * (non-Javadoc)
	 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
	 */
	public void mousePressed(final MouseEvent arg0) {
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
	 */
	public void mouseReleased(final MouseEvent arg0) {
	}

	private void select(final Position position) {
		unhighlight(getSelectedPosition());
		setSelectedPosition(position);
		highlight(position);
		final int index = calculateIndex(position);
		final IIconInformation iconInformation = icons.get(index);
		final String keyStroke = ResourceController.getResourceController().getProperty(iconInformation.getShortcutKey());
		if (keyStroke != null) {
			descriptionLabel.setText(iconInformation.getTranslatedDescription() + ", " + keyStroke);
		}
		else {
			descriptionLabel.setText(iconInformation.getTranslatedDescription());
		}
	}

	private void setSelectedPosition(final Position position) {
		selected = position;
	}

	private void unhighlight(final Position position) {
		iconLabels[calculateIndex(position)].setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
	}
}
