/*
 * 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.internal.ui.common;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.pushingpixels.flamingo.api.common.*;
import org.pushingpixels.flamingo.api.common.model.PopupButtonModel;

/**
 * Listener to track user interaction with the command buttons.
 * 
 * @author Kirill Grouchnikov
 */
public class BasicCommandButtonListener implements MouseListener,
		MouseMotionListener, FocusListener, ChangeListener {
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
	 */
	@Override
	public void focusLost(FocusEvent e) {
		AbstractCommandButton b = (AbstractCommandButton) e.getSource();
		// System.err.println(e.getComponent() + "\n\tlost "
		// + (e.isTemporary() ? "temporary" : "permanent")
		// + " focus to \n\t" + e.getOppositeComponent());
		b.getActionModel().setArmed(false);
		b.getActionModel().setPressed(false);
		if (b instanceof JCommandButton) {
			PopupButtonModel popupModel = ((JCommandButton) b).getPopupModel();
			popupModel.setPressed(false);
			popupModel.setArmed(false);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
	 */
	@Override
	public void focusGained(FocusEvent e) {
		AbstractCommandButton b = (AbstractCommandButton) e.getSource();
		b.repaint();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
	 */
	@Override
	public void mousePressed(MouseEvent e) {
		if (SwingUtilities.isLeftMouseButton(e)) {
			AbstractCommandButton b = (AbstractCommandButton) e.getSource();

			JScrollablePanel scrollable = (JScrollablePanel) SwingUtilities
					.getAncestorOfClass(JScrollablePanel.class, b);
			if (scrollable != null) {
				// scroll the viewport of the scrollable panel so that
				// the button is fully viewed.
				Point loc = SwingUtilities.convertPoint(b.getParent(), b
						.getLocation(), scrollable.getView());
				if (scrollable.getScrollType() == JScrollablePanel.ScrollType.HORIZONTALLY) {
					scrollable.scrollToIfNecessary(loc.x, b.getWidth());
				} else {
					scrollable.scrollToIfNecessary(loc.y, b.getHeight());
				}
			}

			if (b.contains(e.getX(), e.getY())) {
				CommandButtonUI ui = b.getUI();
				Rectangle actionRect = ui.getLayoutInfo().actionClickArea;
				Rectangle popupRect = ui.getLayoutInfo().popupClickArea;

				if ((actionRect != null) && actionRect.contains(e.getPoint())) {
					ButtonModel actionModel = b.getActionModel();
					if (actionModel.isEnabled()) {
						actionModel.setArmed(true);
						actionModel.setPressed(true);
					}
				} else {
					if ((popupRect != null) && popupRect.contains(e.getPoint())) {
						PopupButtonModel popupModel = ((JCommandButton) b)
								.getPopupModel();
						if (popupModel.isEnabled()) {
							popupModel.setArmed(true);
							popupModel.setPressed(true);
						}
					}
				}

				// System.err.println(b.getText() + " - hasFocus():"
				// + b.hasFocus() + ", isRequestFocusEnabled():"
				// + b.isRequestFocusEnabled());
				if (!b.hasFocus() && b.isRequestFocusEnabled()) {
					b.requestFocusInWindow();
				}
			}
		}
	};

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseReleased(MouseEvent e) {
		if (SwingUtilities.isLeftMouseButton(e)) {
			AbstractCommandButton b = (AbstractCommandButton) e.getSource();
			b.getActionModel().setPressed(false);
			if (b instanceof JCommandButton) {
				((JCommandButton) b).getPopupModel().setPressed(false);
			}
			b.getActionModel().setArmed(false);
			if (b instanceof JCommandButton) {
				((JCommandButton) b).getPopupModel().setArmed(false);
			}
		}
	};

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseClicked(MouseEvent e) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent
	 * )
	 */
	@Override
	public void mouseDragged(MouseEvent e) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseMoved(MouseEvent e) {
		this.syncMouseMovement(e);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseEntered(MouseEvent e) {
		this.syncMouseMovement(e);
	}

	/**
	 * Synchronizes the action and popup models of the command button with the
	 * specified mouse event.
	 * 
	 * @param e
	 *            Mouse event for the model synchronization.
	 */
	private void syncMouseMovement(MouseEvent e) {
		AbstractCommandButton b = (AbstractCommandButton) e.getSource();
		ButtonModel actionModel = b.getActionModel();
		PopupButtonModel popupModel = (b instanceof JCommandButton) ? ((JCommandButton) b)
				.getPopupModel()
				: null;
		CommandButtonUI ui = b.getUI();
		Rectangle actionRect = ui.getLayoutInfo().actionClickArea;
		Rectangle popupRect = ui.getLayoutInfo().popupClickArea;

		if ((actionRect != null) && actionRect.contains(e.getPoint())) {
			if (actionModel.isEnabled()) {
				if (!SwingUtilities.isLeftMouseButton(e))
					actionModel.setRollover(true);
				if (actionModel.isPressed())
					actionModel.setArmed(true);
			}
			if (popupModel != null && !SwingUtilities.isLeftMouseButton(e))
				popupModel.setRollover(false);
		} else {
			if ((popupRect != null) && popupRect.contains(e.getPoint())) {
				if ((popupModel != null) && popupModel.isEnabled()) {
					if (!SwingUtilities.isLeftMouseButton(e))
						popupModel.setRollover(true);
					if (popupModel.isPressed())
						popupModel.setArmed(true);
				}
				if (!SwingUtilities.isLeftMouseButton(e))
					actionModel.setRollover(false);
			}
		}
	};

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
	 */
	public void mouseExited(MouseEvent e) {
		AbstractCommandButton b = (AbstractCommandButton) e.getSource();
		ButtonModel actionModel = b.getActionModel();
		PopupButtonModel popupModel = (b instanceof JCommandButton) ? ((JCommandButton) b)
				.getPopupModel()
				: null;
		actionModel.setRollover(false);
		actionModel.setArmed(false);
		if (popupModel != null) {
			popupModel.setRollover(false);
			popupModel.setArmed(false);
		}
	};

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent
	 * )
	 */
	public void stateChanged(ChangeEvent e) {
		AbstractCommandButton b = (AbstractCommandButton) e.getSource();
		b.repaint();
	}

	/**
	 * Installs keyboard action (space / enter keys) on the specified command
	 * button.
	 * 
	 * @param button
	 *            Command button.
	 */
	public void installKeyboardActions(AbstractCommandButton button) {
		ActionMap map = new ActionMap();
		map.put(PressAction.PRESS, new PressAction(button));
		map.put(ReleaseAction.RELEASE, new ReleaseAction(button));

		SwingUtilities.replaceUIActionMap(button, map);
		InputMap km = LookAndFeel.makeInputMap(new Object[] { "SPACE",
				"pressed", "released SPACE", "released", "ENTER", "pressed",
				"released ENTER", "released" });

		SwingUtilities.replaceUIInputMap(button, JComponent.WHEN_FOCUSED, km);
	}

	/**
	 * Button press action.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private static class PressAction extends AbstractAction {
		/**
		 * Press action name.
		 */
		private static final String PRESS = "pressed";

		/**
		 * Associated command button.
		 */
		AbstractCommandButton button;

		/**
		 * Creates a new press action.
		 * 
		 * @param button
		 *            Associated command button.
		 */
		PressAction(AbstractCommandButton button) {
			super(PRESS);
			this.button = button;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent
		 * )
		 */
		public void actionPerformed(ActionEvent e) {
			ButtonModel model = button.getActionModel();
			model.setArmed(true);
			model.setPressed(true);
			if (!button.hasFocus()) {
				button.requestFocus();
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.AbstractAction#isEnabled()
		 */
		@Override
		public boolean isEnabled() {
			return button.getActionModel().isEnabled();
		}
	}

	/**
	 * Button release action.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private static class ReleaseAction extends AbstractAction {
		/**
		 * Release action name.
		 */
		private static final String RELEASE = "released";

		/**
		 * Associated command button.
		 */
		AbstractCommandButton button;

		/**
		 * Creates a new release action.
		 * 
		 * @param button
		 *            Associated command button.
		 */
		ReleaseAction(AbstractCommandButton button) {
			super(RELEASE);
			this.button = button;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent
		 * )
		 */
		public void actionPerformed(ActionEvent e) {
			ButtonModel model = button.getActionModel();
			model.setPressed(false);
			model.setArmed(false);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.AbstractAction#isEnabled()
		 */
		@Override
		public boolean isEnabled() {
			return button.getActionModel().isEnabled();
		}
	}

	/**
	 * Uninstalls keyboard action (space / enter keys) from the specified
	 * command button.
	 * 
	 * @param button
	 *            Command button.
	 */
	public void uninstallKeyboardActions(AbstractCommandButton button) {
		SwingUtilities.replaceUIInputMap(button,
				JComponent.WHEN_IN_FOCUSED_WINDOW, null);
		SwingUtilities.replaceUIInputMap(button, JComponent.WHEN_FOCUSED, null);
		SwingUtilities.replaceUIActionMap(button, null);
	}
}
