/*******************************************************************************
 * Copyright (c) 2004, 2010 Tasktop Technologies and others.
 * 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://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Tasktop Technologies - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.monitor.core;

import java.util.Calendar;
import java.util.Date;

import org.eclipse.core.runtime.Assert;

/**
 * Immutable. Encapsulates interaction made by the user or on behalf of the user. Also see:
 * http://wiki.eclipse.org/index.php/Mylyn_Integrator_Reference#Monitor_API
 * 
 * @author Mik Kersten
 * @since 2.0
 */
public class InteractionEvent {

	/**
	 * Determines the type of interaction that took place, either initiated by the user or done on behalf of the user.
	 */
	public enum Kind {
		/**
		 * User selection of elements, issued by the Eclipse post-selection mechanism.
		 */
		SELECTION,

		/**
		 * Edit events that are created by text selections in an editor.
		 */
		EDIT,

		/**
		 * Commands and actions invoked via buttons, menus, and keyboard shortcuts.
		 */
		COMMAND,

		/**
		 * Workbench preference changes, sometimes made by the user, sometimes automatically on behalf of the user.
		 */
		PREFERENCE,

		/**
		 * Candidates for future interaction.
		 */
		PREDICTION,

		/**
		 * Indirect user interaction with elements (e.g. parent gets implicitly selected when element is selected).
		 */
		PROPAGATION,

		/**
		 * Direct manipulation of interest via actions such as "Mark as Landmark" and "Mark Less Interesting".
		 */
		MANIPULATION,

		/**
		 * Capture interaction with tasks, the workbench, and lifecycle events that define where the user's attention is
		 * directed.
		 */
		ATTENTION;

		/**
		 * TODO: add PREFERENCE?
		 */
		public boolean isUserEvent() {
			return this == SELECTION || this == EDIT || this == COMMAND || this == PREFERENCE;
		}

		/**
		 * @return Simple string representation of the event kind or "null" if no such kind.
		 */
		@Override
		public String toString() {
			switch (this) {
			case SELECTION:
				return "selection"; //$NON-NLS-1$
			case EDIT:
				return "edit"; //$NON-NLS-1$
			case COMMAND:
				return "command"; //$NON-NLS-1$
			case PREFERENCE:
				return "preference"; //$NON-NLS-1$
			case PREDICTION:
				return "prediction"; //$NON-NLS-1$
			case PROPAGATION:
				return "propagation"; //$NON-NLS-1$
			case MANIPULATION:
				return "manipulation"; //$NON-NLS-1$
			case ATTENTION:
				return "attention"; //$NON-NLS-1$
			default:
				return "null"; //$NON-NLS-1$
			}
		}

		/**
		 * @return The corresponding event based on the string provided, or null if no such STring.
		 */
		public static Kind fromString(String string) {
			if (string == null) {
				return null;
			}
			if (string.equals("selection")) { //$NON-NLS-1$
				return SELECTION;
			}
			if (string.equals("edit")) { //$NON-NLS-1$
				return EDIT;
			}
			if (string.equals("command")) { //$NON-NLS-1$
				return COMMAND;
			}
			if (string.equals("preference")) { //$NON-NLS-1$
				return PREFERENCE;
			}
			if (string.equals("prediction")) { //$NON-NLS-1$
				return PREDICTION;
			}
			if (string.equals("propagation")) { //$NON-NLS-1$
				return PROPAGATION;
			}
			if (string.equals("manipulation")) { //$NON-NLS-1$
				return MANIPULATION;
			}
			if (string.equals("attention")) { //$NON-NLS-1$
				return ATTENTION;
			}
			return null;
		}
	}

	private final Kind kind;

	private final Date date;

	private final Date endDate;

	private final String originId;

	private final String structureKind;

	private final String structureHandle;

	private final String navigation;

	private final String delta;

	private final float interestContribution;

	/**
	 * Use to specify an uknown identifier, e.g. for an originId.
	 */
	public static final String ID_UNKNOWN = "?"; //$NON-NLS-1$

	/**
	 * For parameter description see this class's getters.
	 */
	public InteractionEvent(Kind kind, String structureKind, String handle, String originId) {
		this(kind, structureKind, handle, originId, 1f);
	}

	/**
	 * For parameter description see this class's getters.
	 */
	public InteractionEvent(Kind kind, String structureKind, String handle, String originId, String navigatedRelation) {
		this(kind, structureKind, handle, originId, navigatedRelation, "null", 1f); //$NON-NLS-1$
	}

	/**
	 * For parameter description see this class's getters.
	 */
	public InteractionEvent(Kind kind, String structureKind, String handle, String originId, String navigatedRelation,
			float interestContribution) {
		this(kind, structureKind, handle, originId, navigatedRelation, "null", interestContribution); //$NON-NLS-1$
	}

	/**
	 * For parameter description see this class's getters.
	 */
	public static InteractionEvent makeCommand(String originId, String delta) {
		return new InteractionEvent(InteractionEvent.Kind.COMMAND, "null", "null", originId, "null", delta, 1); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
	}

	/**
	 * For parameter description see this class's getters.
	 */
	public static InteractionEvent makeCopy(InteractionEvent originalEvent, float newInterestContribution) {
		return new InteractionEvent(originalEvent.getKind(), originalEvent.getStructureKind(),
				originalEvent.getStructureHandle(), originalEvent.getOriginId(), originalEvent.getNavigation(),
				originalEvent.getDelta(), newInterestContribution, originalEvent.getDate(), originalEvent.getEndDate());
	}

	/**
	 * For parameter description see this class's getters.
	 */
	public static InteractionEvent makePreference(String originId, String delta) {
		return new InteractionEvent(InteractionEvent.Kind.PREFERENCE, "null", "null", originId, "null", delta, 1); // default //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		// contribution
	}

	/**
	 * For parameter description see this class's getters.
	 */
	public InteractionEvent(Kind kind, String structureKind, String handle, String originId, float interestContribution) {
		this(kind, structureKind, handle, originId, "null", "null", interestContribution); // default //$NON-NLS-1$ //$NON-NLS-2$
		// contribution
	}

	/**
	 * For parameter description see this class's getters.
	 */
	public InteractionEvent(Kind kind, String structureKind, String handle, String originId, String navigatedRelation,
			String delta, float interestContribution) {
		this(kind, structureKind, handle, originId, navigatedRelation, delta, interestContribution,
				Calendar.getInstance().getTime());
	}

	private InteractionEvent(Kind kind, String structureKind, String handle, String originId, String navigatedRelation,
			String delta, float interestContribution, Date startDate) {
		this(kind, structureKind, handle, originId, navigatedRelation, delta, interestContribution, startDate,
				startDate);
	}

	/**
	 * For parameter description see this class's getters.
	 */
	public InteractionEvent(Kind kind, String structureKind, String handle, String originId, String navigatedRelation,
			String delta, float interestContribution, Date startDate, Date endDate) {
		Assert.isNotNull(kind);
		Assert.isNotNull(originId);
		Assert.isNotNull(startDate);
		Assert.isNotNull(endDate);
		this.kind = kind;
		this.structureKind = (structureKind != null) ? structureKind.intern() : null;
		this.structureHandle = (handle != null) ? handle.intern() : null;
		this.originId = originId.intern();
		this.navigation = (navigatedRelation != null) ? navigatedRelation.intern() : null;
		this.delta = (delta != null) ? delta.intern() : null;
		this.interestContribution = interestContribution;
		this.date = startDate;
		this.endDate = endDate;
	}

	@Override
	public boolean equals(Object object) {
		if (object == null || !(object instanceof InteractionEvent)) {
			return false;
		}
		InteractionEvent event = (InteractionEvent) object;
		return (date == null ? event.date == null : date.equals(event.date))
				&& (endDate == null ? event.endDate == null : endDate.equals(event.endDate))
				&& (kind == null ? event.kind == null : kind.equals(event.kind))
				&& (structureKind == null ? event.structureKind == null : structureKind.equals(event.structureKind))
				&& (structureHandle == null
						? event.structureHandle == null
						: structureHandle.equals(event.structureHandle))
				&& (originId == null ? event.originId == null : originId.equals(event.originId))
				&& (navigation == null ? event.navigation == null : navigation.equals(event.navigation))
				&& (delta == null ? event.delta == null : delta.equals(event.delta))
				&& interestContribution == event.interestContribution;
	}

	@Override
	public int hashCode() {
		int hashCode = 0;
		if (date != null) {
			hashCode += date.hashCode();
		}
		if (endDate != null) {
			hashCode += endDate.hashCode();
		}
		if (kind != null) {
			hashCode += kind.hashCode();
		}
		if (structureKind != null) {
			hashCode += structureKind.hashCode();
		}
		if (structureHandle != null) {
			hashCode += structureHandle.hashCode();
		}
		if (originId != null) {
			hashCode += originId.hashCode();
		}
		if (navigation != null) {
			hashCode += navigation.hashCode();
		}
		if (delta != null) {
			hashCode += delta.hashCode();
		}
		// TODO: could this lose precision?
		hashCode += new Float(interestContribution).hashCode();
		return hashCode;
	}

	@Override
	public String toString() {
		return "(date: " + date + ", kind: " + kind + ", sourceHandle: " + structureHandle + ", origin: " + originId //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
				+ ", delta: " + delta + ")"; //$NON-NLS-1$ //$NON-NLS-2$
	}

	public boolean isValidStructureHandle() {
		return structureHandle != null && !structureHandle.equals("null") && !structureHandle.trim().equals(ID_UNKNOWN); //$NON-NLS-1$
	}

	// TODO 4.0 change to getHandleIdentifier()
	public String getStructureHandle() {
		return structureHandle;
	}

	/**
	 * @return The content type of the element being interacted with.
	 */
	public String getStructureKind() {
		return structureKind;
	}

	/**
	 * @return Time stamp for the occurrence of the event.
	 */
	public Date getDate() {
		return date;
	}

	/**
	 * Can be used for extensibility, e.g. by adding an XML-encoded String.
	 * 
	 * @return Additional information relevant to interaction monitoring.
	 */
	public String getDelta() {
		return delta;
	}

	/**
	 * @return Defines the kind of interaction that took place.
	 */
	public Kind getKind() {
		return kind;
	}

	/**
	 * @return The UI affordance that the event was issued from.
	 */
	public String getOriginId() {
		return originId;
	}

	/**
	 * @return If an aggregate event, amount of interest of all contained events.
	 */
	// TODO: consider refactoring in order to de-couple events from interest.
	public float getInterestContribution() {
		return interestContribution;
	}

	/**
	 * @return If an aggregate event, time stamp of the last occurrence.
	 */
	public Date getEndDate() {
		return endDate;
	}

	/**
	 * @return An identifier for the kind of relation that corresponds to the navigation to this element.
	 */
	public String getNavigation() {
		return navigation;
	}
}
