/*******************************************************************************
 * Copyright (c) 2004, 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.tasks.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants;

/**
 * @author Steffen Pingel
 * @author Benjamin Muskalla
 * @since 3.8
 */
public abstract class RepositoryClientManager<T, C extends Serializable> implements IRepositoryListener {

	/**
	 * Delegates to <code>repositoryConfigurationClass</code>'s class loader for accessing classes.
	 */
	private class OsgiAwareObjectInputStream extends ObjectInputStream {

		public OsgiAwareObjectInputStream(InputStream in) throws IOException {
			super(in);
		}

		@Override
		protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
			try {
				return repositoryConfigurationClass.getClassLoader().loadClass(desc.getName());
			} catch (Exception e) {
				return super.resolveClass(desc);
			}
		}

	}

	private final Map<String, T> clientByUrl = new HashMap<String, T>();

	private final Map<String, C> respoitoryConfigurationByUrl = new HashMap<String, C>();

	private final File cacheFile;

	private TaskRepositoryLocationFactory locationFactory;

	private final Class<C> repositoryConfigurationClass;

	public RepositoryClientManager(File cacheFile, Class<C> repositoryConfigurationClass) {
		Assert.isNotNull(cacheFile);
		this.cacheFile = cacheFile;
		this.repositoryConfigurationClass = repositoryConfigurationClass;
		readCache();
	}

	public synchronized T getClient(TaskRepository repository) {
		Assert.isNotNull(repository);
		T client = clientByUrl.get(repository.getRepositoryUrl());
		if (client == null) {
			C data = respoitoryConfigurationByUrl.get(repository.getRepositoryUrl());
			if (data == null) {
				data = createRepositoryConfiguration(repository);
				respoitoryConfigurationByUrl.put(repository.getRepositoryUrl(), data);
			}

			client = createClient(repository, data);
			clientByUrl.put(repository.getRepositoryUrl(), client);
		}
		return client;
	}

	protected C createRepositoryConfiguration(TaskRepository repository) {
		try {
			return repositoryConfigurationClass.newInstance();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	protected abstract T createClient(TaskRepository taskRepository, C data);

	public void repositoriesRead() {
		// ignore
	}

	public synchronized void repositoryAdded(TaskRepository repository) {
		removeClient(repository);
		respoitoryConfigurationByUrl.remove(repository.getRepositoryUrl());
	}

	private void removeClient(TaskRepository repository) {
		clientByUrl.remove(repository.getRepositoryUrl());
	}

	public synchronized void repositoryRemoved(TaskRepository repository) {
		removeClient(repository);
		respoitoryConfigurationByUrl.remove(repository.getRepositoryUrl());
	}

	public synchronized void repositorySettingsChanged(TaskRepository repository) {
		removeClient(repository);
	}

	@SuppressWarnings("unchecked")
	public void readCache() {
		if (cacheFile == null || !cacheFile.exists()) {
			return;
		}

		ObjectInputStream in = null;
		try {
			in = new OsgiAwareObjectInputStream(new FileInputStream(cacheFile));
			int size = in.readInt();
			for (int i = 0; i < size; i++) {
				String url = (String) in.readObject();
				C data = (C) in.readObject();
				if (url != null && data != null) {
					respoitoryConfigurationByUrl.put(url, data);
				}
			}
		} catch (Throwable e) {
			handleError("The respository configuration cache could not be read", e); //$NON-NLS-1$
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					// ignore
				}
			}
		}

	}

	protected void handleError(String message, Throwable e) {
		StatusHandler.log(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, message, e));
	}

	public void writeCache() {
		if (cacheFile == null) {
			return;
		}

		ObjectOutputStream out = null;
		try {
			out = new ObjectOutputStream(new FileOutputStream(cacheFile));
			out.writeInt(respoitoryConfigurationByUrl.size());
			for (String url : respoitoryConfigurationByUrl.keySet()) {
				out.writeObject(url);
				out.writeObject(respoitoryConfigurationByUrl.get(url));
			}
		} catch (IOException e) {
			handleError("The respository configuration cache could not be written", e); //$NON-NLS-1$
		} finally {
			if (out != null) {
				try {
					out.close();
				} catch (IOException e) {
					// ignore
				}
			}
		}
	}

	public TaskRepositoryLocationFactory getLocationFactory() {
		return locationFactory;
	}

	public void setLocationFactory(TaskRepositoryLocationFactory locationFactory) {
		this.locationFactory = locationFactory;
	}

	public void repositoryUrlChanged(TaskRepository repository, String oldUrl) {
		// ignore
	}

}
