/*
Copyright (C) 2000-2010  Ministere de la culture et de la communication (France), AJLSM
See LICENCE file
*/
package fr.gouv.culture.sdx.search.lucene.analysis;

import fr.gouv.culture.sdx.exception.SDXException;
import fr.gouv.culture.sdx.exception.SDXExceptionCode;
import fr.gouv.culture.sdx.utils.AbstractSdxObject;
import fr.gouv.culture.sdx.utils.logging.LoggingUtils;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
import org.apache.cocoon.ProcessingException;
import org.apache.excalibur.source.impl.FileSource;
import org.apache.cocoon.components.source.SourceUtil;
import org.xml.sax.SAXException;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Hashtable;
import java.util.Locale;

/**
 * Utility class for managing Lucene analyzers in a multilingual context.
 *
 * <p>
 * Lucene analyzers are used for indexing and querying a Lucene index. An
 * analyzer is specific to a language, so in a multilingual context we need
 * a way to manage analyzers.
 * <p>
 * Most SDX applications will share the same analyzers for popular languages and
 * contexts. In order to reuse resources, we will create only one AnalyzerManager and
 * attach it to the framework.
 * <p>
 * Analyzers used within this manager will each have a unique key. This key
 * will be a combination of the locale and the configuration file used.
 */
public class AnalyzerManager extends AbstractSdxObject {

    public static final String CLASS_NAME_SUFFIX = "AnalyzerManager";
    private Hashtable analyzers = new Hashtable();

    private static String PACKAGE_NAME = "fr.gouv.culture.sdx.search.lucene.analysis";
    private static String CLASS_PREFIX = "Analyzer_";
    private static String DEFAULT_CLASSNAME = PACKAGE_NAME + ".DefaultAnalyzer";
    private static String DEFAULT_KEY = "default";


    /**
     * Builds a manager.
     *
     * <p>
     * Only one manager is needed for a running SDX installation. It is created and managed
     * by the SDX framework.
     */
    public AnalyzerManager() {
    }

    /**
     * Configures the manager.
     *
     * <p>
     * For now, no specific configuration is needed.
     */
    public void configure(Configuration configuration) throws ConfigurationException {
    }

    /**
     *  Adds an analyzer given a locale and a configuration file.
     *
     * @param   locale                  The givene locale, cannot be null
     * @param   configurationFile       An XML file for configuring the analyzer (may be null)
     */
    public Analyzer getAnalyzer(Locale locale, File configurationFile) throws SDXException {

        // If the locale is null we will use the default locale of the system
        if (locale == null) locale = Locale.getDefault();

        // First build a key for this analyzer : locale information (language, country, variant + absolute path of the file)
        String filename = "";
        if (configurationFile != null) filename = configurationFile.getAbsolutePath();
        String key = locale.getLanguage() + ":" + locale.getCountry() + ":" + locale.getVariant() + ":" + filename;

        // If we already have an analyzer built, then return
        if (analyzers.get(key) != null) return (Analyzer) analyzers.get(key);

        // We must then find the class name
        String language = locale.getLanguage();
        String country = locale.getCountry();
        String variant = locale.getVariant();
        String className = PACKAGE_NAME + "." + CLASS_PREFIX + language + "_" + country + "_" + variant;
        Class c = null;
        try {
            // We first try the language + country + variant class name
            c = Class.forName(className);
        } catch (ClassNotFoundException e) {
            className = PACKAGE_NAME + "." + CLASS_PREFIX + language + "_" + country;
            try {
                // Now let's try language + country
                c = Class.forName(className);
            } catch (ClassNotFoundException e1) {
                // Then we try only the language code
                className = PACKAGE_NAME + "." + CLASS_PREFIX + language;
                try {
                    c = Class.forName(className);
                } catch (ClassNotFoundException e2) {
                    // We will use the default analyzer
                    LoggingUtils.logWarn(super.getLog(), "Cannot find an analyzer for locale " + locale.getDisplayName() + ", will use default analyzer", null);
                    className = DEFAULT_CLASSNAME;
                    try {
                        c = Class.forName(className);
                        key = DEFAULT_KEY;
                    } catch (ClassNotFoundException e3) {
                        // If this happens, we have a serious problem, since SDX can't find one of its own classes...
                        String[] args = new String[1];
                        args[0] = className;
                        throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_LOAD_ANALYZER, args, e3);
                    }
                }
            }
        }
        // If we found a class, we will keep track of the analyzer
        if (c != null) {
            setAnalyzer(key, c, configurationFile);
            return (Analyzer) analyzers.get(key);
        } else {
            String[] args = new String[1];
            args[0] = className;
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_LOAD_ANALYZER, args, null);
        }
    }

    /**
     * Adds an analyzer using a specific class name and a configuration file.
     *
     * @param   className           The name of the class to use.
     * @param   configurationFile   The configuration file for the analyzer (may be null)
     */
    public Analyzer getAnalyzer(String className, File configurationFile) throws SDXException {

        String key = null;
        Class c = null;
        String filename = "";
        if (configurationFile != null) filename = configurationFile.getAbsolutePath();

        if (className == null) {
            // No class name, so we will use the default one
            className = DEFAULT_CLASSNAME;
            key = DEFAULT_KEY;
            if (analyzers.get(key) != null) return (Analyzer) analyzers.get(key);
        } else {
            // The key will be the class name and the absolute path of the configuration file.
            key = className + ":" + filename;
            if (analyzers.get(key) != null) return (Analyzer) analyzers.get(key);
            try {
                c = Class.forName(className);
            } catch (ClassNotFoundException e) {
                className = DEFAULT_CLASSNAME;
                key = DEFAULT_KEY;
                try {
                    c = Class.forName(className);
                } catch (ClassNotFoundException e1) {
                    String[] args = new String[1];
                    args[0] = className;
                    throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_LOAD_ANALYZER, args, e1);
                }
            }
        }
        setAnalyzer(key, c, configurationFile);
        return (Analyzer) analyzers.get(key);
    }

    /**
     * Sets up an analyzer from a class and a configuration file.
     *
     * @param   key         The analyzer key.
     * @param   aClass      The class to use.
     * @param   cFile       The configuration file.
     */
    private void setAnalyzer(String key, Class aClass, File cFile) throws SDXException {
        // We must load the file to build a configuration object
        Configuration conf = null;
        if (cFile != null && cFile.exists()) {
            try {
                DefaultConfigurationBuilder confBuilder = new DefaultConfigurationBuilder();
//                FileSource fs = new FileSource(cFile.getAbsolutePath(), manager);
                FileSource fs = new FileSource(cFile.toURI().toURL().toString());
                //conf = confBuilder.build(fs.getInputSource());
				conf = confBuilder.build(SourceUtil.getInputSource(fs));
            } catch (SAXException e) {
                String[] args = new String[1];
                args[0] = key;
                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_LOAD_ANALYZER_CONFIG, args, e);
            } catch (MalformedURLException e) {
                String[] args = new String[1];
                args[0] = key;
                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_LOAD_ANALYZER_CONFIG, args, e);
            } catch (ProcessingException e) {
                String[] args = new String[1];
                args[0] = key;
                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_ANALYZER_CONFIG_FILE_READ, args, e);
            } catch (IOException e) {
                String[] args = new String[1];
                args[0] = key;
                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_ANALYZER_CONFIG_FILE_READ, args, e);
            } catch (ConfigurationException e) {
                String[] args = new String[1];
                args[0] = key;
                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_ANALYZER_CONFIG_FILE_READ, args, e);
            }
        }

        // Then build an instance of the analyzer
        try {
            Object o = aClass.newInstance();
            if (o == null) {
                String[] args = new String[1];
                args[0] = aClass.getName();
                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_NEW_OBJECT_INSTANCE_NULL, args, null);
            }
            if (o instanceof Analyzer) {
                ((Analyzer) o).enableLogging(super.getLog());
                //null case is handled in configure method, the stop tables are built from defaults in no configuration exists
                ((Analyzer) o).configure(conf);
//                if (conf != null) ((Analyzer) o).configure(conf);
                analyzers.put(key, o);
            } else {
                //the object doesn't implement our interface
                String[] args = new String[3];
                args[0] = "Analyzer";
                args[1] = aClass.getName();
                args[2] = key;
                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_CLASS_NOT_INSTANCEOF_SDX_INTERFACE, args, null);
            }
        } catch (InstantiationException e) {
            String[] args = new String[1];
            args[0] = key;
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_SET_ANALYZER, args, e);
//            if (super.getLog() != null) super.getLog().error("Unable to load an instance of class " + aClass.getName() + " for analyzer " + key, e);
        } catch (IllegalAccessException e) {
            String[] args = new String[1];
            args[0] = key;
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_SET_ANALYZER, args, e);
//            if (super.getLog() != null) super.getLog().error("Unable to configure class " + aClass.getName() + " for analyzer " + key, e);
        } catch (ConfigurationException e) {
            String[] args = new String[1];
            args[0] = key;
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_SET_ANALYZER, args, e);
//            if (super.getLog() != null) super.getLog().error("Error configuring analyzer " + key, e);
        }
    }

    protected String getClassNameSuffix() {
        return AnalyzerManager.CLASS_NAME_SUFFIX;
    }

	/* (non-Javadoc)
	 * @see fr.gouv.culture.sdx.utils.AbstractSdxObject#initToSax()
	 */
	protected boolean initToSax() {
		return true;
	}

	/**Init the LinkedHashMap _xmlizable_volatile_objects with the objects in order to describ them in XML
	 * Some objects need to be refresh each time a toSAX is called*/
	protected void initVolatileObjectsToSax() {
	
	}

}
