/*************************************************************************************
 * Copyright (c) 2013 Red Hat, Inc. 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:
 *     JBoss by Red Hat - Initial implementation.
 ************************************************************************************/
package org.jboss.tools.stacks.core.model;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Properties;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.jboss.jdf.stacks.client.DefaultStacksClientConfiguration;
import org.jboss.jdf.stacks.client.StacksClient;
import org.jboss.jdf.stacks.client.StacksClientConfiguration;
import org.jboss.jdf.stacks.client.messages.StacksMessages;
import org.jboss.jdf.stacks.model.Stacks;
import org.jboss.jdf.stacks.parser.Parser;
import org.jboss.tools.foundation.core.ecf.URLTransportUtility;
import org.jboss.tools.foundation.core.jobs.BarrierProgressWaitJob;
import org.jboss.tools.foundation.core.jobs.BarrierProgressWaitJob.IRunnableWithProgress;
import org.jboss.tools.stacks.core.StacksCoreActivator;
import org.jboss.tools.stacks.core.Trace;

/**
 * A StacksManager is in charge of retrieving a file from a URL or standard location
 * and returning a jdf.stacks model object generated via the stacks client. 
 */
public class StacksManager {

	/**
	 * @deprecated
	 */
	private static final String STACKS_URL_PROPERTY = "org.jboss.examples.stacks.url";
	
	private static final String URL_PROPERTY_STACKS = "org.jboss.tools.stacks.url_stacks";
	private static final String URL_PROPERTY_PRESTACKS = "org.jboss.tools.stacks.url_prestacks";
	
	
	private static final String STACKS_URL;
	private static final String PRESTACKS_URL;
	
	// Declare the types of stacks available for fetch
	public enum StacksType {
		STACKS_TYPE, PRESTACKS_TYPE
	}
	
	// Load the default stacks url and prestacks url from a sysprop or jar
	static {
		STACKS_URL = System.getProperty(URL_PROPERTY_STACKS, System.getProperty(STACKS_URL_PROPERTY, System.getProperty(StacksClientConfiguration.REPO_PROPERTY, getStacksDefaultUrlFromJar())));
		PRESTACKS_URL = System.getProperty(URL_PROPERTY_PRESTACKS, System.getProperty("jdf.prestacks.client.repo", getPreStacksDefaultUrlFromJar()));
	}
	
	/**
	 * Fetch the default stacks model. 
	 * 
	 * @param monitor
	 * @return
	 */
	public Stacks getStacks(IProgressMonitor monitor) {
		Stacks[] all = getStacks("Fetching JBoss Stacks", monitor, StacksType.STACKS_TYPE);
		if( all != null && all.length > 0) 
			return all[0];
		return null;
	}
	
	/**
	 * Fetch an array of stacks models where each element represents one of the StacksType urls
	 * 
	 * @param jobName
	 * @param monitor
	 * @param types
	 * @return
	 */
	public Stacks[] getStacks(String jobName, IProgressMonitor monitor, StacksType... types) {
		if( types == null )
			return new Stacks[0];
		Trace.trace(Trace.STRING_FINEST, "Request received for " + types.length + " stacks types.");
		ArrayList<Stacks> ret = new ArrayList<Stacks>(types.length);
		monitor.beginTask(jobName, types.length * 100);
		for( int i = 0; i < types.length; i++ ) {
			switch(types[i] ) {
			case STACKS_TYPE:
				Trace.trace(Trace.STRING_FINEST, "Loading Stacks Model from " + STACKS_URL);
				Stacks s = getStacks(STACKS_URL, jobName, new SubProgressMonitor(monitor, 50));
				if( s == null && !monitor.isCanceled()) {
					StacksCoreActivator.pluginLog().logWarning("Stacks from "+ STACKS_URL +" can not be read, using client mechanism instead");
					s = getDefaultStacksFromClient(new SubProgressMonitor(monitor, 50));
				}
				if( s != null )
					ret.add(s);
				break;
			case PRESTACKS_TYPE:
				// Pre-stacks has no fall-back mechanism at this time
				Trace.trace(Trace.STRING_FINEST, "Loading Stacks Model from " + PRESTACKS_URL);
				Stacks s2 = getStacks(PRESTACKS_URL, jobName, new SubProgressMonitor(monitor, 100));
				if( s2 != null )
					ret.add(s2);
				break;
			default:
				break;
			}
		}
		monitor.done();
		return (Stacks[]) ret.toArray(new Stacks[ret.size()]);
	}
	
	
	/**
	 * Fetch the stacks model representing a given arbitrary url. 
	 * The remote file will be cached only until the system exits. 
	 * 
	 * @param url
	 * @param monitor
	 * @return
	 */
	public Stacks getStacks(String url, IProgressMonitor monitor) {
		return getStacks(url, url, URLTransportUtility.CACHE_UNTIL_EXIT, monitor);
	}
	
	/**
	 * Fetch the stacks model for a given url. Cache the remote file 
	 * with a duration representing forever, or, until the remote file is newer. 
	 * 
	 * @param url  The url
	 * @param jobName Job name for display purposes
	 * @param monitor
	 * @return
	 */
	protected Stacks getStacks(String url, String jobName, IProgressMonitor monitor) {
		return getStacks(url, jobName, URLTransportUtility.CACHE_FOREVER, monitor);
	}
	
	/**
	 * Fetch the stacks model for the given url. 
	 * 
	 * @param url
	 * @param jobName
	 * @param cacheType
	 * @param monitor
	 * @return
	 */
	protected Stacks getStacks(String url, String jobName, int cacheType, IProgressMonitor monitor) {
		return getStacksFromURL(url, jobName, cacheType, monitor);
	}
	
	protected Stacks getStacksFromURL(String url, String jobName, int cacheType, IProgressMonitor monitor) {
	
		Stacks stacks = null;
		try {
			Trace.trace(Trace.STRING_FINEST, "Locating or downloading file for " + url);
			File f = getCachedFileForURL(url, jobName, cacheType, monitor);
			return getStacksFromFile(f);
		} catch (Exception e) {
			StacksCoreActivator.pluginLog().logError("Can't access or parse  " + url, e ); //$NON-NLS-1$
		}
		return stacks;
	}
	
	protected Stacks getStacksFromFile(File f) throws IOException {
		if (f != null && f.exists()) {
			Trace.trace(Trace.STRING_FINEST, "Local file for url exists");
			FileInputStream fis = null;
			try {
				fis = new FileInputStream(f);
				Parser p = new Parser();
				return p.parse(fis);
			} finally {
				close(fis);
			}
		}
		return null;
	}
	
	private Stacks getDefaultStacksFromClient(IProgressMonitor monitor) {
		if (!monitor.isCanceled()) {
			final StacksClient client = new StacksClient(new DefaultStacksClientConfiguration(), new JBTStacksMessages());
			IRunnableWithProgress barrierRunnable = new IRunnableWithProgress() {
				public Object run(IProgressMonitor monitor) throws Exception {
					StacksCoreActivator.pluginLog().logWarning("BarrierProgressWaitJob - loading Stacks Client values");
					return client.getStacks();
				}
			};
			BarrierProgressWaitJob fromClientJob = new BarrierProgressWaitJob("Load stacks using stacks client", barrierRunnable);
			fromClientJob.schedule();
			fromClientJob.monitorSafeJoin(monitor);
			Throwable t = fromClientJob.getThrowable();
			Object ret = fromClientJob.getReturnValue();
			if( t != null ) {
				StacksCoreActivator.pluginLog().logError(t);
			}
			return (Stacks)ret;
		}
		return null;
	}
	
	
	/**
	 * Fetch a local cache of the remote file. 
	 * If the remote file is newer than the local, update it. 
	 * 
	 * @param url
	 * @param jobName
	 * @param monitor
	 * @return
	 */
	protected File getCachedFileForURL(String url, String jobName, IProgressMonitor monitor) throws CoreException {
		 return getCachedFileForURL(url, jobName, URLTransportUtility.CACHE_FOREVER, monitor);
	}
	
	/**
	 * Fetch a local cache of the remote file. 
	 * If the remote file is newer than the local, update it. 
	 * 
	 * @param url   A url to fetch the stacks model from
	 * @param jobName  A job name passed into the downloader for display purposes
	 * @param cacheType  An integer representing the duration to cache the downloaded file
	 * @param monitor A file representign the model
	 * @return
	 */
	protected File getCachedFileForURL(String url, String jobName, int cacheType, IProgressMonitor monitor) throws CoreException {
		 return new URLTransportUtility().getCachedFileForURL(url, jobName, cacheType, monitor);
	}

	
	/*
	 * Read the stacks.yaml location from inside our client jar
	 */
	private static String getStacksDefaultUrlFromJar() {
		return getUrlFromJar(StacksClientConfiguration.REPO_PROPERTY);
	}
	
	private static String getPreStacksDefaultUrlFromJar() {
		return getUrlFromJar(StacksClientConfiguration.PRESTACKS_REPO_PROPERTY);
	}

	private static String getUrlFromJar(String prop) {
		InputStream is = null;
		try {
			is = StacksManager.class.getResourceAsStream("/org/jboss/jdf/stacks/client/config.properties"); //$NON-NLS-1$
			Properties p = new Properties();
			p.load(is);
			return p.getProperty(prop);
		} catch (Exception e) {
			StacksCoreActivator.pluginLog().logWarning("Can't read stacks url from the stacks-client.jar", e); //$NON-NLS-1$
		} finally {
			close(is);
		}
		return null;
	}
	
	private static class JBTStacksMessages implements StacksMessages {
		public void showDebugMessage(String arg0) {
			Trace.trace(Trace.STRING_FINER, arg0);
		}
		public void showInfoMessage(String arg0) {
			Trace.trace(Trace.STRING_INFO, arg0);
		}
		public void showErrorMessage(String arg0) {
			StacksCoreActivator.pluginLog().logError(arg0);
		}
		public void showErrorMessageWithCause(String arg0, Throwable t) {
			StacksCoreActivator.pluginLog().logError(arg0, t);
		}
		public void showWarnMessage(String arg0) {
			StacksCoreActivator.pluginLog().logWarning(arg0);
		}
		
	}
	
	/*
	 * Close an inputstream
	 */
	private static void close(InputStream is) {
		if( is != null ) {
			try {
				is.close();
			} catch(IOException ie) {
				// IGNORE
			}
		}
	}
	
}
