/*******************************************************************************
 * Copyright (c) 2015 - 2017
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *******************************************************************************/
package go.graphics.event.interpreter;

import java.util.LinkedList;

import go.graphics.UIPoint;
import go.graphics.event.GOEvent;
import go.graphics.event.GOEventHandlerProvider;
import go.graphics.event.GOKeyEvent;
import go.graphics.event.SingleHandlerGoEvent;
import go.graphics.event.command.GOCommandEvent;

/**
 * This class interprets events. It provides helper functions for e.g. Swing converter to send the events.
 * 
 * @author michael
 */
public class AbstractEventConverter {
	private static final int PAN_PER_KEY_PRESS = 20;

	private final GOEventHandlerProvider provider;
	private final LinkedList<EventReplacementRule> replace = new LinkedList<>();

	private GOKeyEvent ongoingKeyEvent = null;
	private ConvertedDrawEvent ongoingDrawEvent = null;
	private ConvertedPanEvent ongoingPanEvent = null;
	private ConvertedHoverEvent ongoingHoverEvent;
	private ConvertedZoomEvent ongoingZoomEvent;

	protected AbstractEventConverter(GOEventHandlerProvider provider) {
		this.provider = provider;
	}

	/**
	 * trys to cancel the first found ongoing event.
	 */
	protected void tryCancelCurrentEvent() {
		if (ongoingDrawEvent != null) {
			ongoingDrawEvent.aborted();
			ongoingDrawEvent = null;
		} else if (ongoingPanEvent != null) {
			ongoingPanEvent.aborted();
			ongoingPanEvent = null;
		}
	}

	/**
	 * Starts a draw event, if none is started yet.
	 * 
	 * @param start
	 */
	protected void startDraw(UIPoint start) {
		if (ongoingDrawEvent == null) {
			ongoingDrawEvent = new ConvertedDrawEvent(start);

			handleEvent(ongoingDrawEvent);

			ongoingDrawEvent.initialized();
		}
	}

	protected void updateDrawPosition(UIPoint current) {

		if (ongoingDrawEvent != null) {
			ongoingDrawEvent.setMousePosition(current);
		}
	}

	protected void endDraw(UIPoint position) {
		if (ongoingDrawEvent != null) {
			if (tryReplaceEvent(position, ReplacableEvent.DRAW,
					ongoingDrawEvent.getTime(),
					ongoingDrawEvent.getMouseMoved())) {
				abortDraw();
			} else {
				ongoingDrawEvent.released();
				ongoingDrawEvent = null;
			}
		}
	}

	protected void abortDraw() {
		if (ongoingDrawEvent != null) {
			ongoingDrawEvent.aborted();
			ongoingDrawEvent = null;
		}
	}

	protected void startPan(UIPoint start) {
		if (ongoingPanEvent == null) {
			ongoingPanEvent = new ConvertedPanEvent(start);
			handleEvent(ongoingPanEvent);
			ongoingPanEvent.initialized();
		}
	}

	protected void updatePanPosition(UIPoint current) {
		if (ongoingPanEvent != null) {
			ongoingPanEvent.setMousePosition(current);
		}
	}

	protected void endPan(UIPoint position) {
		if (ongoingPanEvent != null) {
			if (tryReplaceEvent(position, ReplacableEvent.PAN,
					ongoingPanEvent.getTime(), ongoingPanEvent.getMouseMoved())) {
				abortPan();
			} else {
				ongoingPanEvent.released();
				ongoingPanEvent = null;
			}
		}
	}

	protected void startZoom() {
		if (ongoingZoomEvent == null) {
			ongoingZoomEvent = new ConvertedZoomEvent();
			handleEvent(ongoingZoomEvent);
			ongoingZoomEvent.initialized();
		}
	}

	protected void updateZoomFactor(float factor) {
		if (ongoingZoomEvent != null) {
			ongoingZoomEvent.setZoomFactor(factor, null);
		}
	}

	protected void endZoomEvent(float factor, UIPoint pointingPosition) {
		if (ongoingZoomEvent != null) {
			ongoingZoomEvent.setZoomFactor(factor, pointingPosition);
			ongoingZoomEvent.released();
			ongoingZoomEvent = null;
		}
	}

	protected void abortPan() {
		if (ongoingPanEvent != null) {
			ongoingPanEvent.aborted();
			ongoingPanEvent = null;
		}
	}

	protected void startHover(UIPoint start) {
		if (ongoingHoverEvent == null) {
			ongoingHoverEvent = new ConvertedHoverEvent(start);
			handleEvent(ongoingHoverEvent);
			ongoingHoverEvent.initialized();
		}
	}

	protected void updateHoverPosition(UIPoint current) {
		if (ongoingHoverEvent != null) {
			ongoingHoverEvent.setMousePosition(current);
		}
	}

	protected void endHover(UIPoint position) {
		if (ongoingHoverEvent != null) {
			if (tryReplaceEvent(position, ReplacableEvent.HOVER,
					ongoingHoverEvent.getTime(),
					ongoingHoverEvent.getMouseMoved())) {
				abortHover();
			} else {
				ongoingHoverEvent.released();
				ongoingHoverEvent = null;
			}
		}
	}

	protected void abortHover() {
		if (ongoingHoverEvent != null) {
			ongoingHoverEvent.aborted();
			ongoingHoverEvent = null;
		}
	}

	protected boolean fireCommandEvent(UIPoint point, boolean isSelect) {
		ConvertedCommandEvent commandEvent = new ConvertedCommandEvent(point, isSelect);

		handleEvent(commandEvent);

		commandEvent.initialized();

		return commandEvent.getHandler() != null;
	}

	protected synchronized void startKeyEvent(String string) {
		if (ongoingKeyEvent == null) {
			ongoingKeyEvent = new GOKeyEvent(string);
			replaceKeyEvent(ongoingKeyEvent);
			handleEvent(ongoingKeyEvent);
			ongoingKeyEvent.started();
		}
	}

	protected synchronized void endKeyEvent(String string) {
		if (ongoingKeyEvent != null) {
			ongoingKeyEvent.released();
			ongoingKeyEvent = null;
		}
	}

	protected synchronized boolean replaceKeyEvent(GOKeyEvent event) {
		if ("DOWN".equalsIgnoreCase(event.getKeyCode())) {
			doPan(event, 0, PAN_PER_KEY_PRESS);
			return true;
		} else if ("UP".equalsIgnoreCase(event.getKeyCode())) {
			doPan(event, 0, -PAN_PER_KEY_PRESS);
			return true;
		} else if ("LEFT".equalsIgnoreCase(event.getKeyCode())) {
			doPan(event, PAN_PER_KEY_PRESS, 0);
			return true;
		} else if ("RIGHT".equalsIgnoreCase(event.getKeyCode())) {
			doPan(event, -PAN_PER_KEY_PRESS, 0);
			return true;
		} else {
			return false;
		}
	}

	private void doPan(GOKeyEvent event, int x, int y) {
		PseudoPanEvent pan = new PseudoPanEvent(event, x, y);
		this.handleEvent(pan);
	}

	/**
	 * Lets the provider handle a event.
	 */
	protected void handleEvent(GOEvent e) {
		provider.handleEvent(e);

	}

	private class ConvertedCommandEvent extends SingleHandlerGoEvent implements
			GOCommandEvent {

		private final UIPoint position;
		private final boolean selecting;

		public ConvertedCommandEvent(UIPoint position, boolean selecting) {
			this.position = position;
			this.selecting = selecting;
		}

		public void initialized() {
			setPhase(PHASE_STARTED);
			setPhase(PHASE_MODAL);
			setPhase(PHASE_FINISHED);
		}

		@Override
		public UIPoint getCommandPosition() {
			return position;
		}

		@Override
		public boolean isSelecting() {
			return selecting;
		}

	}

	protected void addReplaceRule(EventReplacementRule r) {
		replace.add(r);
	}

	private boolean tryReplaceEvent(UIPoint p, ReplacableEvent e, double time,
			double distance) {
		for (EventReplacementRule r : replace) {
			if (r.matches(e, time, distance)) {
				boolean success = fireCommandEvent(p,
						r.replaced == Replacement.COMMAND_SELECT);
				if (success) {
					return true;
				}
			}
		}
		return false;
	}

	protected boolean drawStarted() {
		return ongoingDrawEvent != null;
	}

	protected boolean panStarted() {
		return ongoingPanEvent != null;
	}

	/**
	 * a rule on when to replace short draw, hover or pan events with other stuff.
	 * 
	 * @author michael
	 */
	public class EventReplacementRule {

		private final ReplacableEvent search;
		private final Replacement replaced;
		private final double maxTime;
		private final double maxDistance;

		/**
		 * @param search
		 * @param replaced
		 * @param maxTime
		 *            Maximum time the event took. -1: ignore
		 * @param maxDistance
		 *            Maximum distance the cursor moved. -1: ignore
		 */
		public EventReplacementRule(ReplacableEvent search, Replacement replaced, double maxTime, double maxDistance) {
			this.search = search;
			this.replaced = replaced;
			this.maxTime = maxTime;
			this.maxDistance = maxDistance;
		}

		private boolean matches(ReplacableEvent e, double time, double distance) {
			return e == search
					&& (maxTime < 0 || time < maxTime)
					&& (maxDistance < 0 || distance < maxDistance);
		}
	}

	public enum ReplacableEvent {
		DRAW,
		PAN,
		HOVER
	}

	public enum Replacement {
		COMMAND_SELECT,
		COMMAND_ACTION
	}
}
