package de.renew.plugin.locate;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Vector;

import de.renew.plugin.CollectionLister;
import de.renew.plugin.PluginProperties;


/**
 * This class, given a URL, creates a list of Files where plugins may be located.
 * It provides a check if the given URL is a directory and whether it exists;
 * subclasses must implement the methods getPluginFiles() and getConfigsFromDirectory().
 */
public abstract class PluginFileFinder implements PluginLocationFinder {
    /**
     * Logger of this class for logging purposes as specified by Apache Log4j
     */
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(PluginFileFinder.class);
    private URL _pluginBase;

    /**
     * The URL is supposed to be a local file.
     * If null is given, the present directory is used as a default.
     *
     * @param basedir   A <code>URL</code> pointing to a location where plugins
     *                  are to be located, or <code>null</code>
     */
    public PluginFileFinder(URL basedir) {
        if (basedir == null) {
            try {
                basedir = new File(System.getProperty("user.dir")).toURI().toURL();
            } catch (MalformedURLException e) {
                LOGGER.error("PluginFileFinder: now, that IS strange.");
            }
        }
        _pluginBase = basedir;
    }

    /**
     * Returns the base location for this <code>PluginLocationFinder</code>,
     * that is the location where new plugins are to be located.
     *
     * @return  A <code>URL</code> pointing to a local file where plugins
     *          are being searched for
     */
    protected URL getPluginBase() {
        return _pluginBase;
    }

    @Override
    public String toString() {
        return "a PluginFileFinder looking below " + _pluginBase;
    }

    /**
     * Returns a Collection which contains PluginProperties.
     * It searches in the local file system:
     * the subdirectories of the base given to the constructor
     * are searched for configurations (by calling the getPluginConfigurations()
     * method that needs to by implemented by subclasses).
     */
    @Override
    public Collection<PluginProperties> findPluginLocations() {
        Collection<PluginProperties> urls = new Vector<>();

        try {
            // use the given url to look for plugin directories
            URL url = getPluginBase();
            LOGGER.debug(getClass().getName() + ": plugin base is " + url.toExternalForm());
            File dir = convert(url, true);
            if (dir == null) {
                // Error message has already been printed by convert().
                return new Vector<>();
            }

            if (!dir.exists()) {
                LOGGER.error("plugin directory " + dir + " not found.");
                return new Vector<>();
            }

            if (!dir.isDirectory()) {
                return new Vector<>();
            }


            // collect list of all directories (where the plugins
            // should be located)
            File[] plugins = getPluginFiles(dir);

            urls = getPluginConfigurations(plugins);

            LOGGER.debug(urls.size() + " plugin locations found.");
            LOGGER.debug(CollectionLister.toString(urls));
        } catch (RuntimeException e) {
            LOGGER.error("something went wrong looking for a plugin location.");
        }
        return urls;
    }

    /**
     * {@inheritDoc}
     * <p>
     * The <code>url</code> is converted into a <code>File</code> object,
     * if possible.  The file is then checked against the usual rules of
     * this finder, if it denotes a plugin.  Relative file URLs are
     * resolved against this finder's base directory.  However, an absolute
     * <code>url</code> is allowed point anywhere.
     * </p>
     **/
    @Override
    public PluginProperties checkPluginLocation(URL url) {
        if (url == null || (!"file".equals(url.getProtocol()))) {
            return null;
        }
        URL resolved;
        try {
            resolved = new URL(_pluginBase, url.getPath());
        } catch (MalformedURLException e) {
            LOGGER.error("PluginFileFinder: Strange URL \"" + url + "\": " + e, e);
            resolved = url;
        }
        File asFile = convert(resolved, false);
        if (asFile != null) {
            if (asFile.isFile() && asFile.getName().contains("plugin.cfg")) {
                asFile = asFile.getParentFile();
            }
            Collection<PluginProperties> coll = getPluginConfigurations(new File[] { asFile });
            if (!coll.isEmpty()) {
                return coll.iterator().next();
            }
        }
        return null;
    }

    private File convert(URL url, boolean verbose) {
        File asFile = null;
        try {
            asFile = new File(new URI(url.toExternalForm()));
        } catch (URISyntaxException e) {
            if (verbose) {
                LOGGER.error("PluginFileFinder: Strange URL \"" + url + "\": " + e, e);
            }
            LOGGER.debug("PluginFileFinder: Strange URL \"" + url + "\": " + e, e);
        } catch (IllegalArgumentException e) {
            if (verbose) {
                LOGGER.error(
                    "PluginFileFinder: Not a file? URL \"" + url + "\": " + e.getMessage(), e);
            }
            LOGGER.debug("PluginFileFinder: Not a file? URL \"" + url + "\": " + e.getMessage(), e);
        } catch (NullPointerException e) {
            if (verbose) {
                LOGGER.error("PluginFileFinder: " + e, e);
            }
            LOGGER.debug("PluginFileFinder: " + e, e);
        }
        return asFile;
    }

    /**
     * This method must be implemented by subclasses so that a list of files
     * is created based on the given file (which is guaranteed to be a directory).
     * This file list will later be the argument to the getConfigsFromDirectories()
     * method.
     *
     * @param dir the base directory where to look.
     * @return a list of files in which a plugin configuration may be found.
     */
    protected abstract File[] getPluginFiles(File dir);

    /**
     * This method is used to create plugin configuration objects
     * (of type <code>PluginProperties</code>).
     *
     * @param fileList  The array of plugins, for which to create the
     *                  <code>PluginProperties</code> objects
     * @return a Collection containing instances of PluginProperties
     */
    protected abstract Collection<PluginProperties> getPluginConfigurations(File[] fileList);

    /**
     * Finds the Plugin Properties.
     *
     * @param provides The name of provided dependency we want to find
     * @return PluginProperties or it can be null
     */
    @Override
    public PluginProperties findPluginByProvides(String provides) {
        Collection<PluginProperties> pluginLocations = findPluginLocations();
        for (PluginProperties pluginProperties : pluginLocations) {
            if (pluginProperties.getProvisions().contains(provides)) {
                return pluginProperties;
            }
        }
        return null;
    }

    @Override
    public PluginProperties findPluginByName(String pluginName) {
        Collection<PluginProperties> pluginLocations = findPluginLocations();
        for (PluginProperties pluginProperties : pluginLocations) {
            if (pluginProperties.getName().equals(pluginName)) {
                return pluginProperties;
            }
        }
        return null;
    }

    @Override
    public Collection<URL> getLocations() {
        Collection<URL> location = new ArrayList<>();
        location.add(_pluginBase);
        return location;
    }
}