/**
 * Copyright (c) 2010 Symbian Foundation and/or its subsidiary(-ies).
 * All rights reserved.
 * This component and the accompanying materials are made available
 * under the terms of the License "Eclipse Public License v1.0"
 * which accompanies this distribution, and is available
 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
 *
 * Initial Contributors:
 * Symbian Foundation - initial contribution.
 * Contributors:
 * Description:
 * Overview:
 * Details:
 * Platforms/Drives/Compatibility:
 * Assumptions/Requirement/Pre-requisites:
 * Failures and causes:
 */
package org.symbian.tools.tmw.core.internal.runtimes;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.eclipse.wst.common.project.facet.core.IFacetedProject;
import org.eclipse.wst.common.project.facet.core.IProjectFacetVersion;
import org.eclipse.wst.common.project.facet.core.IVersion;
import org.eclipse.wst.common.project.facet.core.IVersionExpr;
import org.eclipse.wst.common.project.facet.core.util.internal.VersionExpr;
import org.eclipse.wst.common.project.facet.core.util.internal.Versionable;
import org.eclipse.wst.jsdt.core.IIncludePathEntry;
import org.symbian.tools.tmw.core.TMWCore;
import org.symbian.tools.tmw.core.internal.projects.LazyIncludePathProvider;
import org.symbian.tools.tmw.core.internal.projects.StaticIncludePathProvider;
import org.symbian.tools.tmw.core.projects.IFacetIncludePathProvider;
import org.symbian.tools.tmw.core.projects.ITMWProject;
import org.symbian.tools.tmw.core.runtimes.IMobileWebRuntime;

@SuppressWarnings("restriction")
public class RuntimeClasspathManager {
    private static final class Version implements IVersion {
        private final String version;

        public Version(String versionString) {
            this.version = versionString;
        }

        public int compareTo(Object o) {
            return version.compareTo(((IVersion) o).getVersionString());
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (!(obj instanceof IVersion)) {
                return false;
            }
            IVersion other = (IVersion) obj;
            if (version == null) {
                return false;
            } else if (!version.equals(other.toString())) {
                return false;
            }
            return true;
        }

        public String getVersionString() {
            return version;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = prime + ((version == null) ? 0 : version.hashCode());
            return result;
        }
    }

    private static final class VersionableObject extends Versionable<Version> {
        @Override
        public String createVersionNotFoundErrMsg(String verstr) {
            return "Version not found";
        }

        @Override
        public String getPluginId() {
            return TMWCore.PLUGIN_ID;
        }
    }

    private static final class VersionedEntry<T> {
        private final T entry;
        private final IVersionExpr versionExpression;

        public VersionedEntry(String expression, T ent) {
            if (expression == null) {
                this.versionExpression = null;
            } else {
                IVersionExpr expr;
                try {
                    expr = new VersionExpr<Version>(new VersionableObject(), expression, null);
                } catch (CoreException e) {
                    expr = null;
                    TMWCore.log(null, e);
                }
                this.versionExpression = expr;
            }
            this.entry = ent;
        }

        public boolean matches(String version) {
            if (versionExpression == null) { // Any version
                return true;
            }
            if (version == null) { // Unspecified
                return false;
            }
            return versionExpression.check(new Version(version));
        }
    }

    private final Map<String, Collection<VersionedEntry<Map<String, Collection<VersionedEntry<IFacetIncludePathProvider[]>>>>>> providers = new HashMap<String, Collection<VersionedEntry<Map<String, Collection<VersionedEntry<IFacetIncludePathProvider[]>>>>>>();
    private boolean ready = false;

    public Map<String, Collection<VersionedEntry<IFacetIncludePathProvider[]>>> collectFaceletEntries(
            IConfigurationElement[] elements) {
        final Map<String, Collection<VersionedEntry<IFacetIncludePathProvider[]>>> faceletsToProviders = new HashMap<String, Collection<VersionedEntry<IFacetIncludePathProvider[]>>>();
        for (IConfigurationElement element : elements) {
            if ("facet-include-path".equals(element.getName())) {
                final IFacetIncludePathProvider[] providers = collectProviders(element.getChildren());
                final VersionedEntry<IFacetIncludePathProvider[]> versionedEntry = new VersionedEntry<IFacetIncludePathProvider[]>(
                        element.getAttribute("version"), providers);
                final String id = element.getAttribute("facelet-id");
                Collection<VersionedEntry<IFacetIncludePathProvider[]>> provs = faceletsToProviders.get(id);
                if (provs == null) {
                    provs = new LinkedList<RuntimeClasspathManager.VersionedEntry<IFacetIncludePathProvider[]>>();
                    faceletsToProviders.put(id, provs);
                }
                provs.add(versionedEntry);
            }
        }
        return faceletsToProviders;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private synchronized void collectProviders() {
        if (!ready) {
            final IConfigurationElement[] elements = Platform.getExtensionRegistry().getConfigurationElementsFor(
                    TMWCore.PLUGIN_ID, "runtimeIncludePath");
            final Map<String, Collection<VersionedEntry<IFacetIncludePathProvider[]>>> entries = collectFaceletEntries(elements);
            final VersionedEntry<Map<String, Collection<VersionedEntry<IFacetIncludePathProvider[]>>>> entry = new VersionedEntry(
                    null, entries);
            providers.put(null, Collections.singleton(entry));
            for (IConfigurationElement element : elements) {
                if ("runtime-include-path".equals(element.getName())) {
                    final String id = element.getAttribute("id");
                    Collection<VersionedEntry<Map<String, Collection<VersionedEntry<IFacetIncludePathProvider[]>>>>> versions = providers
                            .get(id);
                    if (versions == null) {
                        versions = new LinkedList<RuntimeClasspathManager.VersionedEntry<Map<String, Collection<VersionedEntry<IFacetIncludePathProvider[]>>>>>();
                        providers.put(id, versions);
                    }
                    final Map<String, Collection<VersionedEntry<IFacetIncludePathProvider[]>>> facetProviders = new HashMap<String, Collection<VersionedEntry<IFacetIncludePathProvider[]>>>();
                    final IFacetIncludePathProvider[] collection = collectProviders(element.getChildren());
                    final VersionedEntry<IFacetIncludePathProvider[]> ent = new VersionedEntry(null, collection);
                    facetProviders.put(null, Collections.singleton(ent));
                    facetProviders.putAll(collectFaceletEntries(element.getChildren()));
                    final VersionedEntry<Map<String, Collection<VersionedEntry<IFacetIncludePathProvider[]>>>> ver = new VersionedEntry<Map<String, Collection<VersionedEntry<IFacetIncludePathProvider[]>>>>(
                            element.getAttribute("version"), facetProviders);
                    versions.add(ver);
                }
            }
            ready = true;
        }
    }

    private IFacetIncludePathProvider[] collectProviders(IConfigurationElement[] children) {
        final Collection<IFacetIncludePathProvider> providerCollection = new LinkedList<IFacetIncludePathProvider>();
        for (IConfigurationElement element : children) {
            if ("include-path-entry".equals(element.getName())) {
                providerCollection.add(new StaticIncludePathProvider(element));
            } else if ("include-path-provider".equals(element.getName())) {
                providerCollection.add(new LazyIncludePathProvider(element));
            }
        }
        return providerCollection.toArray(new IFacetIncludePathProvider[providerCollection.size()]);
    }

    private <T> Collection<T> getMatchingEntries(String id, String version,
            Map<String, Collection<VersionedEntry<T>>> map) {
        final Collection<VersionedEntry<T>> entries = new LinkedList<VersionedEntry<T>>();
        Collection<VersionedEntry<T>> versionedEntries = map.get(null);
        if (versionedEntries != null) {
            entries.addAll(versionedEntries);
        }
        if (id != null) {
            versionedEntries = map.get(id);
            if (versionedEntries != null) {
                entries.addAll(versionedEntries);
            }
        }
        final Collection<T> res = new LinkedList<T>();
        for (VersionedEntry<T> versionedEntry : entries) {
            if (versionedEntry.matches(version)) {
                res.add(versionedEntry.entry);
            }
        }
        return res;
    }

    public IIncludePathEntry[] getProjectClasspathEntries(IFacetedProject project) {
        collectProviders();
        final ITMWProject p = TMWCore.create(project.getProject());
        if (p != null) {
            final IMobileWebRuntime runtime = p.getTargetRuntime();
            final Map<String, Collection<VersionedEntry<IFacetIncludePathProvider[]>>> facetToEntry;
            if (runtime != null) {
                facetToEntry = join(getMatchingEntries(runtime.getId(), runtime.getVersion(), providers));
            } else {
                facetToEntry = join(getMatchingEntries(null, null, providers));
            }
            final Collection<IFacetIncludePathProvider[]> entries = getMatchingEntries(null, null, facetToEntry);
            final Set<IProjectFacetVersion> facets = project.getProjectFacets();
            for (IProjectFacetVersion facet : facets) {
                entries.addAll(getMatchingEntries(facet.getProjectFacet().getId(), facet.getVersionString(),
                        facetToEntry));
            }
            final Collection<IIncludePathEntry> pathEntries = new HashSet<IIncludePathEntry>();
            for (IFacetIncludePathProvider[] providerArray : entries) {
                for (IFacetIncludePathProvider provider : providerArray) {
                    pathEntries.addAll(Arrays.asList(provider.getEntries(p)));
                }
            }
            return pathEntries.toArray(new IIncludePathEntry[pathEntries.size()]);
        }
        return new IIncludePathEntry[0];
    }

    private <T, V> Map<T, Collection<V>> join(Collection<Map<T, Collection<V>>> maps) {
        final Map<T, Collection<V>> res = new HashMap<T, Collection<V>>();
        for (Map<T, Collection<V>> map : maps) {
            for (Map.Entry<T, Collection<V>> entry : map.entrySet()) {
                if (res.containsKey(entry.getKey())) {
                    res.get(entry.getKey()).addAll(entry.getValue());
                } else {
                    res.put(entry.getKey(), new LinkedList<V>(entry.getValue()));
                }
            }
        }
        return res;
    }
}
