package freenet.clients.fcp;

import java.util.Date;

import freenet.client.async.ClientRequester;
import freenet.client.events.SplitfileProgressEvent;
import freenet.clients.fcp.ClientRequest.Persistence;
import freenet.keys.FreenetURI;
import freenet.l10n.NodeL10n;

/** The status of a request. Cached copy i.e. can be accessed outside the database thread
 * even for a persistent request.
 * 
 * Methods that change the status should be package-local, and called either
 * within freenet.clients.fcp, or via RequestStatusCache. Hence we should be 
 * able to lock the RequestStatusCache and be confident that nothing is going
 * to change under us.
 * 
 * @author toad 
 */
public abstract class RequestStatus implements Cloneable {
	
	private final String identifier;
	private boolean hasStarted;
	private boolean hasFinished;
	private boolean hasSucceeded;
	private short priority;
	private int totalBlocks;
	private int minBlocks;
	private int fetchedBlocks;
	/** @see ClientRequester#latestSuccess */
	private Date latestSuccess;
	private int fatallyFailedBlocks;
	private int failedBlocks;
	/* @see ClientRequester#latestFailure */
	private Date latestFailure;
	private boolean isTotalFinalized;
	private final Persistence persistence;
	
	/** The download or upload has finished.
	 * @param success Did it succeed? */
	synchronized void setFinished(boolean success) {
		this.latestSuccess = new Date();
		this.hasFinished = true;
		this.hasSucceeded = success;
		this.hasStarted = true;
		this.isTotalFinalized = true;
	}
	
	synchronized void restart(boolean started) {
		// See ClientRequester.getLatestSuccess() for why this defaults to current time.
		this.latestSuccess = new Date();
		this.hasFinished = false;
		this.hasSucceeded = false;
		this.hasStarted = started;
		this.isTotalFinalized = false;
	}
	
	/** Constructor for creating a status from a request that has already started, e.g. on
	 * startup. We will also create status when a request is created. */
	RequestStatus(String identifier, Persistence persistence, boolean started, boolean finished, 
			boolean success, int total, int min, int fetched, Date latestSuccess, int fatal,
			int failed, Date latestFailure, boolean totalFinalized, short prio) {
		this.identifier = identifier;
		this.hasStarted = started;
		this.hasFinished = finished;
		this.hasSucceeded = success;
		this.priority = prio;
		this.totalBlocks = total;
		this.minBlocks = min;
		this.fetchedBlocks = fetched;
		// clone() because Date is mutable
		this.latestSuccess
			= latestSuccess != null ? (Date)latestSuccess.clone() : null;
		this.fatallyFailedBlocks = fatal;
		this.failedBlocks = failed;
		// clone() because Date is mutable
		this.latestFailure
			= latestFailure != null ? (Date)latestFailure.clone() : null;
		this.isTotalFinalized = totalFinalized;
		this.persistence = persistence;
	}
	
	public boolean hasSucceeded() {
		return hasSucceeded;
	}

	public boolean hasFinished() {
		return hasFinished;
	}

	public short getPriority() {
		return priority;
	}

	public String getIdentifier() {
		return identifier;
	}

	public int getTotalBlocks() {
		return totalBlocks;
	}

	public boolean isTotalFinalized() {
		return isTotalFinalized;
	}

	public int getMinBlocks() {
		return minBlocks;
	}

	public int getFetchedBlocks() {
		return fetchedBlocks;
	}

	/** @deprecated Use {@link #getLastSuccess()} instead. */
	@Deprecated
	public long getLastActivity() {
		return latestSuccess != null ? latestSuccess.getTime() : 0;
	}

	public Date getLastSuccess() {
		// clone() because Date is mutable.
		return latestSuccess != null ? (Date)latestSuccess.clone() : null;
	}
	
	public Date getLastFailure() {
		// clone() because Date is mutable.
		return latestFailure != null ? (Date)latestFailure.clone() : null;
	}

	/** Get the original URI for a fetch or the final URI for an insert. */
	public abstract FreenetURI getURI();

	public abstract long getDataSize();

	public boolean isPersistentForever() {
		return persistence == Persistence.FOREVER;
	}

	public boolean isPersistent() {
		return persistence != Persistence.CONNECTION;
	}

	public int getFatalyFailedBlocks() {
		return fatallyFailedBlocks;
	}

	public int getFailedBlocks() {
		return failedBlocks;
	}

	public boolean isStarted() {
		return hasStarted;
	}

	public abstract String getFailureReason(boolean longDescription);

	public synchronized void updateStatus(SplitfileProgressEvent event) {
		this.failedBlocks = event.failedBlocks;
		this.fatallyFailedBlocks = event.fatallyFailedBlocks;
		// clone() because Date is mutable
		this.latestFailure = event.latestFailure != null ? (Date)event.latestFailure.clone() : null;
		this.fetchedBlocks = event.succeedBlocks;
		// clone() because Date is mutable
		this.latestSuccess = event.latestSuccess != null ? (Date)event.latestSuccess.clone() : null;
		this.isTotalFinalized = event.finalizedTotal;
		this.minBlocks = event.minSuccessfulBlocks;
		this.totalBlocks = event.totalBlocks;
	}
	public synchronized void setPriority(short newPriority) {
		this.priority = newPriority;
	}

	public synchronized void setStarted(boolean started) {
		this.hasStarted = started;
	}

	/** Get the preferred filename, from the URI, the filename, etc. 
	 * @return A filename or null if not enough information to give one. */
	public abstract String getPreferredFilename();

	/** Get the preferred filename, from the URI or the filename etc.
	 * @return A filename, including the localised version of "unknown" if
	 * we don't know enough.
	 */
	public String getPreferredFilenameSafe() {
		String ret = getPreferredFilename();
		if(ret == null)
			return NodeL10n.getBase().getString("RequestStatus.unknownFilename");
		else
			return ret;
	}

	public RequestStatus clone() {
		try {
			return (RequestStatus) super.clone();
		} catch (CloneNotSupportedException e) {
			throw new Error(e);
		}
	}

}
