/**
 * File:    ConfRegistryImpl.java
 * Created: 10.07.2008
 *
 *
/*******************************************************************************
 * Copyright (c) 2008-2009 Open Wide SA <www.openwide.fr>
 *
 * 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:
 *    Marc Dutoo, Open Wide SA 
 *    	- initial API and implementation
 *******************************************************************************/

package org.eclipse.jwt.we.conf.model.aspects.internal;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.util.URI;
import org.eclipse.jwt.we.conf.model.Aspect;
import org.eclipse.jwt.we.conf.model.ConfModel;
import org.eclipse.jwt.we.conf.model.Profile;
import org.eclipse.jwt.we.conf.model.aspects.ConfRegistry;
import org.eclipse.jwt.we.conf.model.plugin.ConfPlugin;
import org.eclipse.jwt.we.conf.model.plugin.internal.Logger;
import org.eclipse.jwt.we.conf.model.resource.ConfModelResourceManager;
import org.eclipse.jwt.we.conf.model.resource.ConfResourceException;
import org.osgi.framework.Bundle;


/**
 * 
 * @version $Id: ConfRegistryImpl.java,v 1.1 2010-05-10 08:27:22 chsaad Exp $
 * @author Marc Dutoo
 *
 */
public class ConfRegistryImpl implements ConfRegistry {
	
	public static final String CONF_EXTENSION_POINT_CONF_FILE = "file"; //$NON-NLS-1$


	/**
	 * The Logger.
	 */
	protected static Logger logger = Logger.getLogger(ConfRegistryImpl.class);

	
    /** confs declared using extension point */
	protected List<ConfModel> declaredConfs = new ArrayList<ConfModel>();
    /** confs discovered under conf/ dir */
	protected List<ConfModel> discoveredConfs = new ArrayList<ConfModel>();
    /** declaredConfs and discoveredConfs */
	protected List<ConfModel> installedConfs = new ArrayList<ConfModel>();

	
	private boolean inited = false;
	
	/* should be model-scoped, doesn't take into account the activated profiles, TODO better */
	//private Map<EClass,List<Aspect>> eclassToAspectsMapCache = new HashMap<EClass,List<Aspect>>();
	/* Cache for models that have embedded conf specifying which profiles should be activated TODO rather in EMF as transient info */
	//private Map<Model,List<Profile>> modelToActivatedProfilesCache = new HashMap<Model,List<Profile>>();
	/** Pointer to cache of all installed profiles, often used, put within the previous one */
	private ArrayList<Profile> installedProfilesCache = new ArrayList<Profile>();
	/** Used to resolve embedded profile -> installed profile */
	private Map<String,Profile> idToInstalledProfileMapCache = new HashMap<String,Profile>();
	/** Used to resolve embedded aspect -> installed aspect */
	private Map<String,Aspect> idToInstalledAspectsMapCache = new HashMap<String,Aspect>();
	

	protected ConfRegistryImpl() {
		super();
	}

	public static ConfRegistry createConfRegistry() {
		ConfRegistryImpl confRegistry = new ConfRegistryImpl();
		confRegistry.init();
		return confRegistry;
	}

	

	public Aspect getInstalledAspect(String aspectId) {
		if (aspectId == null || aspectId.length() == 0) {
			return null;
		}
		return idToInstalledAspectsMapCache.get(aspectId);
	}

	/**
	 * Finds and returns the corresponding Profile
	 * @param profileId
	 * @return the Profile that has the given id
	 */
	public Profile getInstalledProfile(String profileId) {
		if (profileId == null || profileId.length() == 0) {
			return null;
		}
		return idToInstalledProfileMapCache.get(profileId);
	}

	/**
	 * (copy not required since should be loaded at all times)
	 * @return all installed profiles
	 */
	public ArrayList<Profile> getInstalledProfiles() {
		// TODO check if cache ok
		return installedProfilesCache;
	}	


	/**
	 * Adds a discovered conf.
	 * If done explicitly after init, conf caches are rebuilt.
	 * @param confModel
	 */
	public void addDiscoveredConf(ConfModel confModel) {
		// TODO check if not doubling ?
		discoveredConfs.add(confModel);
		installedConfs.add(confModel);
		if (inited) {
			rebuildConfCaches();
		}
	}

	/**
	 * Adds a declared conf.
	 * If done explicitly after init, conf caches are rebuilt.
	 * TODO listener to plugin registry
	 * @param confModel
	 */
	public void addDeclaredConf(ConfModel confModel) {
		// TODO check if not doubling ?
		declaredConfs.add(confModel);
		installedConfs.add(confModel);
		if (inited) {
			rebuildConfCaches();
		}
	}
	
	public List<ConfModel> getDeclaredConfs() {
		return declaredConfs;
	}

	public List<ConfModel> getDiscoveredConfs() {
		return discoveredConfs;
	}

	public List<ConfModel> getInstalledConfs() {
		return installedConfs;
	}


	
	/**
	 * Inits :
	 * inits the aspectsFactory,
	 * loads discovered and declared confs.
	 * May be overriden.
	 */
	protected void init() {
		this.inited = false;
		collectConfModels();
		rebuildConfCaches();
		this.inited = true;
	}

	protected void rebuildConfCaches() {
		rebuildInstalledProfilesCache();
		rebuildIdToInstalledProfileMapCache();
		rebuildIdToInstalledAspectMapCache();
	}

	protected void rebuildInstalledProfilesCache() {
		installedProfilesCache.clear();
	    for (ConfModel confModel : getInstalledConfs()) {
			installedProfilesCache.addAll(confModel.getProfiles());
	    }
	}

	protected void rebuildIdToInstalledProfileMapCache() {
		idToInstalledProfileMapCache.clear();
	    for (Profile profile : this.getInstalledProfiles()) {
	    	idToInstalledProfileMapCache.put(profile.getName(), profile);	
	    }
	}

	protected void rebuildIdToInstalledAspectMapCache() {
		idToInstalledAspectsMapCache.clear();
	    for (Profile profile : this.getInstalledProfiles()) {
	    	idToInstalledProfileMapCache.put(profile.getName(), profile);
		    for (Aspect aspect : profile.getAspects()) {
		    	idToInstalledAspectsMapCache.put(aspect.getId(), aspect);
		    }	
	    }
	}
	
	
	
	/**
	 * Collects all confs (declared and discovered) and loads them.
	 * 
	 */
	protected void collectConfModels() {
		try {
			// load all external views from extension point
			loadDeclaredConfs();
		
			// load all discovered confs in conf/ dir
			loadDiscoveredConfs();
			
			// TODO merge loaded confs into loaded conf, or with a profiles map
			
		} catch (Exception ex) {
			logger.severe("Error collecting ConfModels", ex); //$NON-NLS-1$
		}
	}


	/**
	 * Discovers and load all conf files under the conf directory.
	 * @throws IOException
	 */
	public void loadDiscoveredConfs() throws IOException {
		// getting conf dir, in bundle or fs
		URL confpath = ConfPlugin.getPlugin().getBundle().getEntry(CONFDIRECTORY); // NO npex on getBundle(), not interesting because rather declared
		// TODO conf dir in JWT install dir, maybe as relative path in extpoint ?

		File confdir = null;
		if (confpath != null) {
			URL conffullpath = FileLocator.resolve(confpath);
			if (conffullpath != null) {
				confdir = (new Path(FileLocator.toFileURL(confpath).getPath())).toFile();
				// viewdir = new File(viewsfullpath.getPath()); fixed for .jar support
			}
		}
		if (confdir == null) {
			confdir = new File(CONFDIRECTORY);
		}
		
		if (!confdir.exists() || !confdir.isDirectory() || !confdir.canRead()) {
			logger.debug("Conf discovery directory at " + confdir.getAbsolutePath() //$NON-NLS-1$
					+ " can't be read, aborting conf discovery"); //$NON-NLS-1$
			return;
		}

		// load recursively all internal views in the jwt-we view dir
		loadDiscoveredConfs(confdir);
	}
	
	/**
	 * Discovers and load all conf files in the given directory
	 * and its subdirs. Errors are warned of, then skipped.
	 * @param confdir
	 */
	protected void loadDiscoveredConfs(File confdir) {
		for (int i = 0; i < confdir.listFiles().length; i++) {
			File discoveredConfFile = confdir.listFiles()[i];
			
			if (!discoveredConfFile.isFile()) {
				if (!discoveredConfFile.isDirectory() || !discoveredConfFile.canRead()) {
					logger.debug("Conf subdirectory at " + discoveredConfFile.getAbsolutePath() //$NON-NLS-1$
							+ " can't be read, skipping"); //$NON-NLS-1$
					continue;
				}
				loadDiscoveredConfs(discoveredConfFile);
				continue;
			}

			String discoveredConfFileName = discoveredConfFile.getAbsolutePath();
			String ext = discoveredConfFileName.substring(discoveredConfFileName.lastIndexOf(".") + 1); //$NON-NLS-1$
			if (ext.equals(CONFEXTENSION)) {
				// load the conf
				ConfModel confModel = null;
				try {
					confModel = loadConf(URI.createFileURI(discoveredConfFile.getAbsolutePath()));
				} catch (ConfResourceException e) {
					logger.warning("Error while looking for discovered conf file at " //$NON-NLS-1$
							+ discoveredConfFile.getAbsolutePath() + ", skipping", e); //$NON-NLS-1$
					continue;
				}
				if (confModel == null) {
					logger.warning("No conf in discovered conf file at " //$NON-NLS-1$
							+ discoveredConfFile.getAbsolutePath() + ", skipping"); //$NON-NLS-1$
					continue;
				}
				
				// TODO tag the conf as discovered
				addDiscoveredConf(confModel);
			}
		}
	}

	/**
	 * Loads and returns the given conf file.
	 * 
	 * @param f The conf file
	 * 
	 * @return The conf model, or null if not a conf
	 * @throws ConfResourceException if error loading it
	 */
	public ConfModel loadConf(URI uri) throws ConfResourceException {
		return ConfModelResourceManager.INSTANCE.loadConfModel(uri, null, null, false);
	}


	/**
	 * Loads declared conf files, i.e. that are registered in
	 * a plugin.xml using the conf extension point.
	 * Errors are severly warned of then skipped.
	 */
	public void loadDeclaredConfs() {
		logger.debug("Getting conf extensions at point " + CONF_EXTENSION_POINT_ID); //$NON-NLS-1$

		try {
			// get the extension point registry
			IExtensionRegistry registry = Platform.getExtensionRegistry();
			IExtensionPoint confExtensionPoint = registry.getExtensionPoint(CONF_EXTENSION_POINT_ID);

			// exit if there are no extensions registered or no configurable elements
			if (confExtensionPoint == null || confExtensionPoint.getConfigurationElements() == null) {
				logger.debug("No declared conf extension found at point " + CONF_EXTENSION_POINT_ID); //$NON-NLS-1$
				return;
			}

			// read declared confs extension point
			for (IConfigurationElement point : confExtensionPoint.getConfigurationElements()) {
				// read the conf file entry in the extension point
				String confFileName = point.getAttribute(CONF_EXTENSION_POINT_CONF_FILE);

				logger.info("Found ConfModel extension at point " + CONF_EXTENSION_POINT_ID //$NON-NLS-1$
						+ " : conf file " + confFileName); //$NON-NLS-1$

				// resolve the conf file in bundles
				Bundle bundle = Platform.getBundle(point.getContributor().getName());
				URL confPath = bundle.getEntry(confFileName);
				if (confPath == null) {
					logger.severe("Conf file " + confFileName + " could not be found"); //$NON-NLS-1$ //$NON-NLS-2$
					continue;
				}
				
				// load the conf
				ConfModel confModel = null;
				URI confUri = URI.createURI(confPath.toURI().toString());
				try {
					confModel = loadConf(confUri);
				} catch (ConfResourceException e) {
					logger.severe("Error while looking for declared conf file at " //$NON-NLS-1$
							+ confUri.toString() + " by contributor " //$NON-NLS-1$
							+ point.getContributor().getName() + ", skipping", e); //$NON-NLS-1$
					continue;
				}
				if (confModel == null) {
					logger.severe("No conf in declared conf file at " //$NON-NLS-1$
							+ confUri.toString() + " by contributor " //$NON-NLS-1$
							+ point.getContributor().getName() + ", skipping"); //$NON-NLS-1$
					continue;
				}
				
				// TODO tag the conf as declared and not discovered
				addDeclaredConf(confModel);
			}

		} catch (Exception e) {
			logger.severe("Error loading external confs", e); //$NON-NLS-1$
		}
	}
		
}
