/*******************************************************************************
 * Copyright (c) 2012 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.commons.core.storage;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.internal.commons.core.CommonsCorePlugin;
import org.eclipse.osgi.util.NLS;

/**
 * @author Steffen Pingel
 */
public class CommonStore {

	/**
	 * Delays writing of mementos to avoid blocking UI thread.
	 */
	private class FlushJob extends Job {

		public FlushJob() {
			super("Flush context mementos"); //$NON-NLS-1$
			setSystem(true);
			setPriority(Job.SHORT);
		}

		@Override
		protected IStatus run(IProgressMonitor monitor) {
			flushPending();
			return Status.OK_STATUS;
		}

	}

	private static final long FLUSH_DELAY = 500;

	private boolean scheduled;

	private FlushJob flushJob;

	private final Map<File, CommonStorable> storableByLocation;

	private File location;

	public CommonStore(File location) {
		Assert.isNotNull(location);
		this.storableByLocation = new HashMap<File, CommonStorable>();
		this.location = location;
	}

	public synchronized ICommonStorable get(IPath path) {
		File file = getFile(path);
		CommonStorable storable = storableByLocation.get(file);
		if (storable == null) {
			storable = new CommonStorable(this, file);
			storableByLocation.put(file, storable);
		}
		return storable;
	}

	public File getLocation() {
		return location;
	}

	public void setLocation(File location) {
		Assert.isNotNull(location);
		this.location = location;
	}

	public void stop() {
		synchronized (this) {
			if (flushJob != null) {
				flushJob.cancel();
				flushJob = null;
			}
		}
		flushPending();
	}

	private File getFile(IPath path) {
		return getFile(path, true);
	}

	private File getFile(IPath path, boolean create) {
		File file = new File(location, path.toOSString());
		if (create && !file.getParentFile().exists()) {
			file.getParentFile().mkdirs();
		}
		return file;
	}

	synchronized void schedule() {
		if (!scheduled) {
			if (flushJob == null) {
				flushJob = new FlushJob();
			}
			flushJob.schedule(FLUSH_DELAY);
		}
	}

	synchronized void flushPending() {
		MultiStatus status = new MultiStatus(CommonsCorePlugin.ID_PLUGIN, 0, "Failed to save storable", null); //$NON-NLS-1$
		for (CommonStorable memento : storableByLocation.values()) {
			if (memento.isDirty()) {
				IStatus result = memento.flush();
				status.add(result);
			}
		}
		if (!status.isOK()) {
			StatusHandler.log(status);
		}
	}

	synchronized void release(CommonStorable storable) {
		storableByLocation.remove(storable.getPath());
	}

	public void move(IPath oldPath, IPath newPath) throws CoreException {
		File oldFile = getFile(oldPath, false);
		// TODO lock hierarchy and throw an exception if oldFile is in use 
		if (oldFile.exists()) {
			File newFile = getFile(newPath, false);
			newFile.getParentFile().mkdirs();
			if (!oldFile.renameTo(newFile)) {
				throw new CoreException(new Status(IStatus.ERROR, CommonsCorePlugin.ID_PLUGIN, NLS.bind(
						"The target path ''{0}'' already exists", newPath)));
			}
		}
	}

}
