/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.update.core;

import java.net.*;
import java.util.*;

import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.NLS;
import org.eclipse.update.configuration.*;
import org.eclipse.update.core.model.*;
import org.eclipse.update.internal.core.*;

/**
 * Convenience implementation of a site.
 * <p>
 * This class may be instantiated or subclassed by clients.
 * </p> 
 * <p>
 * <b>Note:</b> This class/interface is part of an interim API that is still under development and expected to
 * change significantly before reaching stability. It is being made available at this early stage to solicit feedback
 * from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken
 * (repeatedly) as the API evolves.
 * </p>
 * @see org.eclipse.update.core.ISite
 * @see org.eclipse.update.core.model.SiteModel
 * @since 2.0
 */
public class Site extends SiteModel implements ISiteWithMirrors {

	/**
	 * Default installation path for features
	 * 
	 * @since 2.0
	 */
	public static final String DEFAULT_INSTALLED_FEATURE_PATH = "features/"; //$NON-NLS-1$

	/**
	 * Default installation path for plug-ins and plug-in fragments
	 * 
	 * @since 2.0
	 */
	public static final String DEFAULT_PLUGIN_PATH = "plugins/"; //$NON-NLS-1$

	/**
	 * Default path on a site where packaged features are located
	 * 
	 * @since 2.0
	 */
	public static final String DEFAULT_FEATURE_PATH = "features/"; //$NON-NLS-1$

	/**
	 * Default site manifest file name
	 * 
	 * @since 2.0
	 */
	public static final String SITE_FILE = "site"; //$NON-NLS-1$

	/**
	 * Default site manifest extension
	 * 
	 * @since 2.0
	 */
	public static final String SITE_XML = SITE_FILE + ".xml"; //$NON-NLS-1$

	private ISiteContentProvider siteContentProvider;
	
	private Map featureCache = new HashMap(); // key=URLKey value=IFeature
	
	/**
	 * Constructor for Site
	 */
	public Site() {
		super();
	}

	/**
	 * Compares two sites for equality
	 * 
	 * @param obj site object to compare with
	 * @return <code>true</code> if the two sites are equal, 
	 * <code>false</code> otherwise
	 * @since 2.0
	 */
	public boolean equals(Object obj) {
		if (!(obj instanceof ISite))
			return false;
		if (getURL() == null)
			return false;
		ISite otherSite = (ISite) obj;

		return UpdateManagerUtils.sameURL(getURL(), otherSite.getURL());
	}

	/**
	 * Returns the site URL
	 * 
	 * @see ISite#getURL()
	 * @since 2.0
	 */
	public URL getURL() {
		URL url = null;
		try {
			url = getSiteContentProvider().getURL();
		} catch (CoreException e) {
			UpdateCore.warn(null, e);
		}
		return url;
	}

	/**
	 * Returns the site description.
	 * 
	 * @see ISite#getDescription()
	 * @since 2.0
	 */
	public IURLEntry getDescription() {
		return (IURLEntry) getDescriptionModel();
	}

	/**
	 * Returns an array of categories defined by the site.
	 * 
	 * @see ISite#getCategories()
	 * @since 2.0
	 */
	public ICategory[] getCategories() {
		CategoryModel[] result = getCategoryModels();
		if (result.length == 0)
			return new ICategory[0];
		else
			return (ICategory[]) result;
	}

	/**
	 * Returns the named site category.
	 * 
	 * @see ISite#getCategory(String)
	 * @since 2.0
	 */
	public ICategory getCategory(String key) {
		ICategory result = null;
		boolean found = false;
		int length = getCategoryModels().length;

		for (int i = 0; i < length; i++) {
			if (getCategoryModels()[i].getName().equals(key)) {
				result = (ICategory) getCategoryModels()[i];
				found = true;
				break;
			}
		}

		//DEBUG:
		if (!found) {
			String URLString = (this.getURL() != null) ? this.getURL().toExternalForm() : "<no site url>"; //$NON-NLS-1$
			UpdateCore.warn(NLS.bind(Messages.Site_CannotFindCategory, (new String[] { key, URLString })));
			if (getCategoryModels().length <= 0)
				UpdateCore.warn(Messages.Site_NoCategories);	
		}

		return result;
	}

	/**
	 * Returns an array of references to features on this site.
	 * 
	 * @see ISite#getFeatureReferences()
	 * @since 2.0
	 */
	public ISiteFeatureReference[] getRawFeatureReferences() {
		FeatureReferenceModel[] result = getFeatureReferenceModels();
		if (result.length == 0)
			return new ISiteFeatureReference[0];
		else
			return (ISiteFeatureReference[]) result;
	}

	/**
	 * @see org.eclipse.update.core.ISite#getFeatureReferences()
	 */
	public ISiteFeatureReference[] getFeatureReferences() {
		// only filter local site
		if (getCurrentConfiguredSite()!=null)
			return filterFeatures(getRawFeatureReferences());
		else 
			return getRawFeatureReferences();
		
	}

	/*
	 * Method filterFeatures.
	 * Also implemented in Feature
	 *  
	 * @param list
	 * @return List
	 */
	private ISiteFeatureReference[] filterFeatures(ISiteFeatureReference[] allIncluded) {
		List list = new ArrayList();
		if (allIncluded!=null){
			for (int i = 0; i < allIncluded.length; i++) {
				ISiteFeatureReference included = allIncluded[i];
				if (UpdateManagerUtils.isValidEnvironment(included))
					list.add(included);
				else{
					if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_WARNINGS){
						UpdateCore.warn("Filtered out feature reference:"+included); //$NON-NLS-1$
					}
				}
			}
		}
		
		ISiteFeatureReference[] result = new ISiteFeatureReference[list.size()];
		if (!list.isEmpty()){
			list.toArray(result);
		}
		
		return result;	
	}

	/**
	 * Returns a reference to the specified feature on this site.
	 * 
	 * @see ISite#getFeatureReference(IFeature)
	 * @since 2.0
	 */
	public ISiteFeatureReference getFeatureReference(IFeature feature) {

		if (feature == null) {
			UpdateCore.warn("Site:getFeatureReference: The feature is null"); //$NON-NLS-1$
			return null;
		}

		ISiteFeatureReference[] references = getFeatureReferences();
		ISiteFeatureReference currentReference = null;
		for (int i = 0; i < references.length; i++) {
			currentReference = references[i];
			if (UpdateManagerUtils.sameURL(feature.getURL(), currentReference.getURL()))
				return currentReference;
		}

		UpdateCore.warn("Feature " + feature + " not found on site" + this.getURL()); //$NON-NLS-1$ //$NON-NLS-2$
		return null;
	}

	/**
	 * Returns an array of plug-in and non-plug-in archives located
	 * on this site
	 * 
	 * @see ISite#getArchives()
	 * @since 2.0
	 */
	public IArchiveReference[] getArchives() {
		ArchiveReferenceModel[] result = getArchiveReferenceModels();
		if (result.length == 0)
			return new IArchiveReference[0];
		else
			return (IArchiveReference[]) result;
	}

	/**
	 * Returns the content provider for this site.
	 * 
	 * @see ISite#getSiteContentProvider()
	 * @since 2.0
	 */
	public ISiteContentProvider getSiteContentProvider() throws CoreException {
		if (siteContentProvider == null) {
			throw Utilities.newCoreException(Messages.Site_NoContentProvider, null);	
		}
		return siteContentProvider;
	}

	/**
	 * Returns the default type for a packaged feature supported by this site
	 * 
	 * @see ISite#getDefaultPackagedFeatureType()
	 * @since 2.0
	 */
	public String getDefaultPackagedFeatureType() {
		return DEFAULT_PACKAGED_FEATURE_TYPE;
	}

	/**
	 * Returns an array of entries corresponding to plug-ins installed
	 * on this site.
	 * 
	 * @see ISite#getPluginEntries()
	 * @since 2.0
	 */
	public IPluginEntry[] getPluginEntries() {
		throw new UnsupportedOperationException();
	}

	/**
	 * Returns the number of plug-ins installed on this site
	 * 
	 * @see ISite#getPluginEntryCount()
	 * @since 2.0
	 */
	public int getPluginEntryCount() {
		throw new UnsupportedOperationException();
	}

	/**
	 * Returns an array of entries corresponding to plug-ins that are
	 * installed on this site and are referenced only by the specified
	 * feature. 
	 * 
	 * @see ISite#getPluginEntriesOnlyReferencedBy(IFeature)	 * 
	 * @since 2.0
	 */
	public IPluginEntry[] getPluginEntriesOnlyReferencedBy(IFeature feature) throws CoreException {

		IPluginEntry[] pluginsToRemove = new IPluginEntry[0];
		if (feature == null)
			return pluginsToRemove;

		// get the plugins from the feature
		IPluginEntry[] entries = feature.getPluginEntries();
		if (entries != null) {
			// get all the other plugins from all the other features
			Set allPluginID = new HashSet();
			ISiteFeatureReference[] features = getFeatureReferences();
			if (features != null) {
				for (int indexFeatures = 0; indexFeatures < features.length; indexFeatures++) {
					IFeature featureToCompare = null;
					try {
						featureToCompare = features[indexFeatures].getFeature(null);
					} catch (CoreException e) {
						UpdateCore.warn(null, e);
					}
					if (!feature.equals(featureToCompare)) {
						IPluginEntry[] pluginEntries = features[indexFeatures].getFeature(null).getPluginEntries();
						if (pluginEntries != null) {
							for (int indexEntries = 0; indexEntries < pluginEntries.length; indexEntries++) {
								allPluginID.add(pluginEntries[indexEntries].getVersionedIdentifier());
							}
						}
					}
				}
			}

			// create the delta with the plugins that may be still used by other configured or unconfigured feature
			List plugins = new ArrayList();
			for (int indexPlugins = 0; indexPlugins < entries.length; indexPlugins++) {
				if (!allPluginID.contains(entries[indexPlugins].getVersionedIdentifier())) {
					plugins.add(entries[indexPlugins]);
				}
			}

			// move List into Array
			if (!plugins.isEmpty()) {
				pluginsToRemove = new IPluginEntry[plugins.size()];
				plugins.toArray(pluginsToRemove);
			}
		}

		return pluginsToRemove;
	}

	/**
	 * Adds a new plug-in entry to this site.
	 * This implementation always throws UnsupportedOperationException
	 * because this implementation does not support the install action.
	 * 
	 * @see ISite#addPluginEntry(IPluginEntry)
	 * @exception java.lang.UnsupportedOperationException
	 * @since 2.0
	 */
	public void addPluginEntry(IPluginEntry pluginEntry) {
		throw new UnsupportedOperationException();
	}

	/**
	 * Get download size for the specified feature on this site.
	 * This implementation always throws UnsupportedOperationException
	 * because this implementation does not support the install action.
	 * 
	 * @see ISite#getDownloadSizeFor(IFeature)
	 * @exception java.lang.UnsupportedOperationException
	 * @since 2.0
	 */
	public long getDownloadSizeFor(IFeature feature) {
		throw new UnsupportedOperationException();
	}

	/**
	 * Get install size for the specified feature on this site.
	 * This implementation always throws UnsupportedOperationException
	 * because this implementation does not support the install action.
	 * 
	 * @see ISite#getInstallSizeFor(IFeature)
	 * @exception java.lang.UnsupportedOperationException
	 * @since 2.0
	 */
	public long getInstallSizeFor(IFeature feature) {
		throw new UnsupportedOperationException();
	}

	/**
	 * Install the specified feature and all optional features on this site.
	 * This implementation always throws UnsupportedOperationException
	 * because this implementation does not support the install action.
	 * 
	 * @see ISite#install(IFeature, IVerificationListener, IProgressMonitor)
	 * @exception InstallAbortedException when the user cancels the install
	 * @exception CoreException
	 * @exception java.lang.UnsupportedOperationException
	 * @since 2.0
	 */
	public IFeatureReference install(IFeature sourceFeature, IVerificationListener verificationListener, IProgressMonitor progress) throws InstallAbortedException, CoreException {
		throw new UnsupportedOperationException();
	}

	/**
	 * Install the specified feature and listed optional features on this site.
	 * This implementation always throws UnsupportedOperationException
	 * because this implementation does not support the install action.
	 * 
	 * @see ISite#install(IFeature, IVerificationListener, IProgressMonitor)
	 * @exception InstallAbortedException when the user cancels the install
	 * @exception CoreException
	 * @exception java.lang.UnsupportedOperationException
	 * @since 2.0
	 */
	public IFeatureReference install(IFeature sourceFeature, IFeatureReference[] optionalFeatures, IVerificationListener verificationListener, IProgressMonitor progress) throws InstallAbortedException, CoreException {
		throw new UnsupportedOperationException();
	}

	/**
	 * Install the specified feature on this site using the content consumer as 
	 * a context to install the feature in.
	 * This implementation always throws UnsupportedOperationException
	 * because this implementation does not support the install action.
	 * 
	 * @param sourceFeature feature to install
	 * @param parentContentConsumer content consumer of the parent feature
	 * @param parentVerifier verifier of the parent feature
	 * @param verificationListener install verification listener
	 * @param progress install monitor, can be <code>null</code>
	 * @exception InstallAbortedException when the user cancels the install
	 * @exception CoreException
	 * @exception java.lang.UnsupportedOperationException 
	 * @since 2.0 
	 */
	public IFeatureReference install(IFeature sourceFeature, IFeatureReference[] optionalFeatures, IFeatureContentConsumer parentContentConsumer, IVerifier parentVerifier, IVerificationListener verificationListener, IProgressMonitor progress)
		throws CoreException {
		throw new UnsupportedOperationException();
	}

	/**
	 * Remove (uninstall) the specified feature from this site.
	 * This implementation always throws UnsupportedOperationException
	 * because this implementation does not support the remove action.
	 * 
	 * @see ISite#remove(IFeature, IProgressMonitor)
	 * @exception java.lang.UnsupportedOperationException
	 * @since 2.0
	 */
	public void remove(IFeature feature, IProgressMonitor progress) throws CoreException {
		throw new UnsupportedOperationException();
	}

	/**
	 * Sets the site content provider.
	 * 
	 * @see ISite#setSiteContentProvider(ISiteContentProvider)
	 * @since 2.0
	 */
	public void setSiteContentProvider(ISiteContentProvider siteContentProvider) {
		this.siteContentProvider = siteContentProvider;
	}
	/**
	 * @see ISite#getCurrentConfiguredSite()
	 */
	public IConfiguredSite getCurrentConfiguredSite() {
		return (IConfiguredSite) getConfiguredSiteModel();
	}

	/**
	 * @see org.eclipse.update.core.ISite#createFeature(String, URL)
	 * @deprecated
	 */
	public IFeature createFeature(String type, URL url) throws CoreException {
		return createFeature(type,url,null);
	}

	/**
	 * @see org.eclipse.update.core.ISite#createFeature(String, URL,
	 * IProgressMonitor)
	 */
	public IFeature createFeature(String type, URL url, IProgressMonitor monitor) throws CoreException {

		// First check the cache
		URLKey key = new URLKey(url);
		IFeature feature = (IFeature) featureCache.get(key);
		if (feature != null) return feature;

		// Create a new one
		if (type == null || type.equals("")) { //$NON-NLS-1$
			// ask the Site for the default type
			type = getDefaultPackagedFeatureType();
		}

		IFeatureFactory factory = FeatureTypeFactory.getInstance().getFactory(type);
		feature = factory.createFeature(url, this, monitor);
		if (feature != null) {
			// Add the feature to the cache
			featureCache.put(key, feature);
		}
		return feature;
	}

	protected void removeFeatureFromCache(URL featureURL) {
		URLKey key = new URLKey(featureURL);
		featureCache.remove(key);
	}

	/**
	 * Return an array of mirror update sites.
	 * 
	 * @return an array of mirror update sites
	 * @since 2.0
	 */
	public IURLEntry[] getMirrorSiteEntries() {
		URLEntryModel[] result = getMirrorSiteEntryModels();
		if (result.length == 0)
			return new IURLEntry[0];
		else
			return (IURLEntry[]) result;
	}
}
