/*
 * Copyright 2015 Decebal Suiu
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ro.fortsoft.pf4j;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author Decebal Suiu
 */
public abstract class AbstractExtensionFinder implements ExtensionFinder, PluginStateListener {

    protected static final Logger log = LoggerFactory.getLogger(AbstractExtensionFinder.class);

    protected PluginManager pluginManager;
    protected volatile Map<String, Set<String>> entries; // cache by pluginId

    public AbstractExtensionFinder(PluginManager pluginManager) {
        this.pluginManager = pluginManager;
    }

    public abstract Map<String, Set<String>> readPluginsStorages();

    public abstract Map<String, Set<String>> readClasspathStorages();

    @Override
	public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
        log.debug("Checking extension point '{}'", type.getName());
        if (!isExtensionPoint(type)) {
            log.warn("'{}' is not an extension point", type.getName());

            return Collections.emptyList(); // or return null ?!
        }

		log.debug("Finding extensions for extension point '{}'", type.getName());
        Map<String, Set<String>> entries = getEntries();

        List<ExtensionWrapper<T>> result = new ArrayList<>();
        for (Map.Entry<String, Set<String>> entry : entries.entrySet()) {
            String pluginId = entry.getKey();

            if (pluginId != null) {
                PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId);
                if (PluginState.STARTED != pluginWrapper.getPluginState()) {
                    continue;
                }
            }

            for (String className : entry.getValue()) {
                try {
                    ClassLoader classLoader;
                    if (pluginId != null) {
                        classLoader = pluginManager.getPluginClassLoader(pluginId);
                    } else {
                        classLoader = getClass().getClassLoader();
                    }
                    log.debug("Loading class '{}' using class loader '{}'", className, classLoader);
                    Class<?> extensionClass = classLoader.loadClass(className);

                    log.debug("Checking extension type '{}'", className);
                    if (type.isAssignableFrom(extensionClass) && extensionClass.isAnnotationPresent(Extension.class)) {
                        Extension extension = extensionClass.getAnnotation(Extension.class);
                        ExtensionDescriptor descriptor = new ExtensionDescriptor();
                        descriptor.setOrdinal(extension.ordinal());
                        descriptor.setExtensionClass(extensionClass);

                        ExtensionWrapper extensionWrapper = new ExtensionWrapper<>(descriptor);
                        extensionWrapper.setExtensionFactory(pluginManager.getExtensionFactory());
                        result.add(extensionWrapper);
                        log.debug("Added extension '{}' with ordinal {}", className, extension.ordinal());
                    } else {
                        log.debug("'{}' is not an extension for extension point '{}'", className, type.getName());
                    }
                } catch (ClassNotFoundException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }

        if (entries.isEmpty()) {
        	log.debug("No extensions found for extension point '{}'", type.getName());
        } else {
        	log.debug("Found {} extensions for extension point '{}'", result.size(), type.getName());
        }

        // sort by "ordinal" property
        Collections.sort(result);

		return result;
	}

    @Override
    public Set<String> findClassNames(String pluginId) {
        return getEntries().get(pluginId);
    }

    @Override
	public void pluginStateChanged(PluginStateEvent event) {
        // TODO optimize (do only for some transitions)
        // clear cache
        entries = null;
    }

    private Map<String, Set<String>> readStorages() {
        Map<String, Set<String>> result = new LinkedHashMap<>();

        result.putAll(readClasspathStorages());
        result.putAll(readPluginsStorages());

        return result;
    }

    private boolean isExtensionPoint(Class<?> type) {
        return ExtensionPoint.class.isAssignableFrom(type);
    }

    private Map<String, Set<String>> getEntries() {
        if (entries == null) {
            entries = readStorages();
        }

        return entries;
    }

}

@@ -3031,12 +3031,165 @@
 ass)
- && 
+) %7B%0A                        ExtensionDescriptor descriptor = new ExtensionDescriptor();%0A                        int ordinal = 0;%0A                        if (
 exte
@@ -3267,27 +3267,19 @@
     
-Extension extension
+    ordinal
  = e
@@ -3322,16 +3322,26 @@
 n.class)
+.ordinal()
 ;%0A      
@@ -3362,67 +3362,9 @@
     
-ExtensionDescriptor descriptor = new ExtensionDescriptor();
+%7D
 %0A   
@@ -3406,35 +3406,23 @@
 Ordinal(
-extension.
 ordinal
-()
 );%0A     
@@ -3825,27 +3825,15 @@
 me, 
-extension.
 ordinal
-()
 );%0A 

Fix a bug related to extension HowdyGreeting is not visible on getExtensions()