/*******************************************************************************
 * 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.internal.tasks.core;

import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.mylyn.tasks.core.IRepositoryElement;
import org.eclipse.mylyn.tasks.core.ITask;

/**
 * Encapsulates tasks that reside on a repository or local computer and participate in synchronization with the source
 * that contains their data.
 * 
 * @author Mik Kersten
 * @author Rob Elves
 * @since 2.0
 */
public abstract class AbstractTask extends AbstractTaskContainer implements ITask, ITaskRepositoryElement {

	public static final String DEFAULT_TASK_KIND = "task"; //$NON-NLS-1$

	private String repositoryUrl;

	private String taskKind = DEFAULT_TASK_KIND;

	private final String taskId;

	private String owner;

	private boolean active = false;

	private String summary;

	private String priority = PriorityLevel.getDefault().toString();

	private boolean isNotifiedIncoming = false;

	private boolean reminded = false;

	private final Set<AbstractTaskContainer> containers = new CopyOnWriteArraySet<AbstractTaskContainer>();

	// ************ Synch ****************

	/** The last time this task's bug report was in a synchronized (read?) state. */
	private String lastReadTimeStamp;

	private boolean synchronizing;

	private boolean submitting;

	private SynchronizationState synchronizationState = SynchronizationState.SYNCHRONIZED;

	// transient
	private IStatus status = null;

	private boolean stale = false;

	private Date completionDate = null;

	private Date creationDate = null;

	private Date modificationDate = null;

	private DateRange scheduledForDate = null;

	private Date dueDate = null;

	private String notes = ""; //$NON-NLS-1$

	private int estimatedTimeHours = 0;

	private boolean markReadPending;

	// TODO 4.0 make private
	protected String taskKey;

	private AttributeMap attributeMap;

	private boolean changed;

	public AbstractTask(String repositoryUrl, String taskId, String summary) {
		super(RepositoryTaskHandleUtil.getHandle(repositoryUrl, taskId));
		this.repositoryUrl = repositoryUrl;
		this.taskId = taskId;
		this.summary = summary;
	}

	/**
	 * Final to preserve the handle identifier format required by the framework.
	 */
	@Override
	public final String getHandleIdentifier() {
		return super.getHandleIdentifier();
	}

	/**
	 * True for tasks that can be modified without a round-trip to a server. For example, such a task can be marked
	 * completed via the Task List.
	 * 
	 * @deprecated use <code>task instanceof LocalTask</code> instead
	 */
	@Deprecated
	public abstract boolean isLocal();

	public abstract String getConnectorKind();

	@Deprecated
	public String getLastReadTimeStamp() {
		return lastReadTimeStamp;
	}

	@Deprecated
	public void setLastReadTimeStamp(String lastReadTimeStamp) {
		this.lastReadTimeStamp = (lastReadTimeStamp != null) ? lastReadTimeStamp.intern() : null;
	}

	/**
	 * @since 3.0
	 */
	public void setSynchronizationState(SynchronizationState syncState) {
		Assert.isNotNull(syncState);
		this.synchronizationState = syncState;
	}

	/**
	 * @since 3.0
	 */
	public SynchronizationState getSynchronizationState() {
		return synchronizationState;
	}

	public boolean isSynchronizing() {
		return synchronizing;
	}

	public void setSynchronizing(boolean sychronizing) {
		this.synchronizing = sychronizing;
	}

	public boolean isNotified() {
		return isNotifiedIncoming;
	}

	public void setNotified(boolean notified) {
		isNotifiedIncoming = notified;
	}

	public String getOwner() {
		return owner;
	}

	public void setOwner(String owner) {
		if (!areEqual(this.owner, owner)) {
			String oldValue = this.owner;
			this.owner = (owner != null) ? owner.intern() : null;
			firePropertyChange("owner", oldValue, owner); //$NON-NLS-1$
		}
	}

	/**
	 * Return the status, such as an error or warning, associated with this task.
	 */
	public IStatus getStatus() {
		return status;
	}

	public void setStatus(IStatus status) {
		this.status = status;
	}

	public final String getTaskId() {
		return taskId;
	}

	public final String getRepositoryUrl() {
		return repositoryUrl;
	}

	@Override
	public final void setHandleIdentifier(String handleIdentifier) {
		throw new RuntimeException("Cannot set the handle identifier of a task, set repository URL instead."); //$NON-NLS-1$
	}

	public final void setRepositoryUrl(String repositoryUrl) {
		this.repositoryUrl = (repositoryUrl != null) ? repositoryUrl.intern() : null;
		super.setHandleIdentifier(RepositoryTaskHandleUtil.getHandle(repositoryUrl, taskId));
	}

	/**
	 * User identifiable key for the task to be used in UI facilities such as label displays and hyperlinked references.
	 * Can return the same as the ID (e.g. in the case of Bugzilla). Can return null if no such label exists.
	 */
	public String getTaskKey() {
		return (taskKey == null) ? taskId : taskKey;
	}

	@Deprecated
	public boolean isSubmitting() {
		return submitting;
	}

	@Deprecated
	public void setSubmitting(boolean submitting) {
		this.submitting = submitting;
	}

	@Override
	public String toString() {
		String taskKey = getTaskKey();
		if (taskKey != null) {
			return taskKey + ": " + summary; //$NON-NLS-1$
		} else {
			return summary;
		}
	}

	public void setActive(boolean active) {
		this.active = active;
	}

	public boolean isActive() {
		return active;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof AbstractTask) {
			return this.getHandleIdentifier().equals(((ITask) obj).getHandleIdentifier());
		} else {
			return false;
		}
	}

	@Override
	public int hashCode() {
		return taskId.hashCode();
	}

	public boolean isCompleted() {
		return completionDate != null;
	}

	/**
	 * @deprecated use setCompletionDate()
	 */
	@Deprecated
	public void setCompleted(boolean completed) {
		if (completed) {
			completionDate = new Date();
		} else {
			completionDate = null;
		}
	}

	@Override
	public String getPriority() {
		return priority;
	}

	public void setPriority(String priority) {
		if (!areEqual(this.priority, priority)) {
			String oldValue = this.priority;
			this.priority = (priority != null) ? priority.intern() : null;
			firePropertyChange("priority", oldValue, priority); //$NON-NLS-1$
		}
	}

	public String getNotes() {
		// TODO: removed check for null once xml updated.
		if (notes == null) {
			notes = ""; //$NON-NLS-1$
		}
		return notes;
	}

	public void setNotes(String notes) {
		this.notes = notes;
	}

	/**
	 * @deprecated Use {@link #getEstimatedTimeHours()} instead
	 */
	@Deprecated
	public int getEstimateTimeHours() {
		return getEstimatedTimeHours();
	}

	public int getEstimatedTimeHours() {
		return estimatedTimeHours;
	}

	public void setEstimatedTimeHours(int estimated) {
		this.estimatedTimeHours = estimated;
	}

	void addParentContainer(AbstractTaskContainer container) {
		containers.add(container);
	}

	void removeParentContainer(AbstractTaskContainer container) {
		containers.remove(container);
	}

	public Set<AbstractTaskContainer> getParentContainers() {
		//return new HashSet<AbstractTaskContainer>(containers);
		return containers;
	}

	@Override
	public String getSummary() {
		return summary;
	}

	public Date getCompletionDate() {
		return completionDate;
	}

	public void setScheduledForDate(DateRange reminderDate) {
		scheduledForDate = reminderDate;
	}

	public DateRange getScheduledForDate() {
		return scheduledForDate;
	}

	public boolean isReminded() {
		return reminded;
	}

	public void setReminded(boolean reminded) {
		this.reminded = reminded;
	}

	public Date getCreationDate() {
		return creationDate;
	}

	public void setCreationDate(Date creationDate) {
		if (!areEqual(this.creationDate, creationDate)) {
			Date oldValue = this.creationDate;
			this.creationDate = creationDate;
			firePropertyChange("creationDate", oldValue, creationDate); //$NON-NLS-1$
		}
	}

	public void setSummary(String summary) {
		Assert.isNotNull(summary);
		if (!areEqual(this.summary, summary)) {
			String oldValue = this.summary;
			this.summary = summary;
			firePropertyChange("summary", oldValue, summary); //$NON-NLS-1$
		}
	}

	public void setCompletionDate(Date completionDate) {
		if (!areEqual(this.completionDate, completionDate)) {
			Date oldValue = this.completionDate;
			this.completionDate = completionDate;
			firePropertyChange("completionDate", oldValue, completionDate); //$NON-NLS-1$
		}
	}

	private boolean areEqual(Object oldValue, Object newValue) {
		return (oldValue != null) ? oldValue.equals(newValue) : oldValue == newValue;
	}

	private void firePropertyChange(String key, Object oldValue, Object newValue) {
//			PropertyChangeEvent event = new PropertyChangeEvent(this, key, oldValue, newValue);
//			for (PropertyChangeListener listener : propertyChangeListeners) {
//				listener.propertyChange(event);
//			}
		changed = true;
	}

	public boolean isChanged() {
		return changed;
	}

	public void setChanged(boolean changed) {
		this.changed = changed;
	}

	/**
	 * @deprecated use {@link TaskActivityManager#isPastReminder(AbstractTask)} instead
	 */
	@Deprecated
	public boolean isPastReminder() {
		if (isCompleted() || scheduledForDate == null || !(getScheduledForDate() instanceof DayDateRange)) {
			return false;
		} else {
			if (/*!internalIsFloatingScheduledDate() && */scheduledForDate.getEndDate().compareTo(
					TaskActivityUtil.getCalendar()) < 0) {
				return true;
			} else {
				return false;
			}
		}
	}

	public String getTaskKind() {
		return taskKind;
	}

	public void setTaskKind(String taskKind) {
		if (!areEqual(this.taskKind, taskKind)) {
			String oldValue = this.taskKind;
			this.taskKind = (taskKind != null) ? taskKind.intern() : null;
			firePropertyChange("taskKind", oldValue, taskKind); //$NON-NLS-1$
		}
	}

	@Override
	public int compareTo(IRepositoryElement taskListElement) {
		return summary.compareTo(((AbstractTask) taskListElement).summary);
	}

	public Date getDueDate() {
		return dueDate;
	}

	public void setDueDate(Date date) {
		if (!areEqual(this.dueDate, date)) {
			Date oldValue = this.dueDate;
			this.dueDate = date;
			firePropertyChange("dueDate", oldValue, date); //$NON-NLS-1$
		}
	}

	@Deprecated
	public boolean isStale() {
		return stale;
	}

	@Deprecated
	public void setStale(boolean stale) {
		this.stale = stale;
	}

	public Date getModificationDate() {
		return modificationDate;
	}

	public void setModificationDate(Date modificationDate) {
		if (!areEqual(this.modificationDate, modificationDate)) {
			Date oldValue = this.modificationDate;
			this.modificationDate = modificationDate;
			firePropertyChange("modificationDate", oldValue, modificationDate); //$NON-NLS-1$
		}
	}

	public boolean isMarkReadPending() {
		return markReadPending;
	}

	public void setMarkReadPending(boolean markReadPending) {
		this.markReadPending = markReadPending;
	}

	public void setTaskKey(String taskKey) {
		if (!areEqual(this.taskKey, taskKey)) {
			String oldValue = this.taskKey;
			this.taskKey = taskKey;
			firePropertyChange("taskKey", oldValue, taskKey); //$NON-NLS-1$
		}
	}

	public synchronized String getAttribute(String key) {
		return (attributeMap != null) ? attributeMap.getAttribute(key) : null;
	}

	public synchronized Map<String, String> getAttributes() {
		if (attributeMap != null) {
			return attributeMap.getAttributes();
		} else {
			return Collections.emptyMap();
		}
	}

	public void setAttribute(String key, String value) {
		String oldValue;
		synchronized (this) {
			if (attributeMap == null) {
				attributeMap = new AttributeMap();
			}
			oldValue = attributeMap.getAttribute(key);
			if (!areEqual(oldValue, value)) {
				attributeMap.setAttribute(key, value);
			} else {
				return;
			}
		}
		firePropertyChange(key, oldValue, value);
	}

	@Override
	public void setUrl(String url) {
		String oldValue = getUrl();
		if (!areEqual(oldValue, url)) {
			super.setUrl(url);
			firePropertyChange("url", oldValue, url); //$NON-NLS-1$
		}
	}

}
