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

import fr.gouv.culture.sdx.documentbase.DefaultIDGenerator;
import fr.gouv.culture.sdx.exception.SDXException;
import fr.gouv.culture.sdx.exception.SDXExceptionCode;
import fr.gouv.culture.sdx.search.lucene.analysis.Analyzer;
import fr.gouv.culture.sdx.search.lucene.analysis.AnalyzerManager;
import fr.gouv.culture.sdx.utils.AbstractSdxObject;
import fr.gouv.culture.sdx.utils.Identifiable;
import fr.gouv.culture.sdx.utils.Utilities;
import fr.gouv.culture.sdx.utils.configuration.ConfigurationUtils;
import fr.gouv.culture.sdx.utils.constants.ContextKeys;
import fr.gouv.culture.sdx.utils.database.Property;
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.context.ContextException;

import java.io.File;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Locale;

/**
 * Definition of fields for a document base.
 */
public class FieldList extends AbstractSdxObject implements Cloneable {

    public static final String CLASS_NAME_SUFFIX = "FieldList";
    /**The analyzer for this field definition.*/
    private Analyzer analyzer;

    /** The list of fields, indexed by names. */
    private Hashtable fieldTable;

    /** Default search field. */
    private String defaultField = "";

    /** Ignore case in field names or not. */
    private boolean ignoreCase = false;

    /**String representation of the 'fieldList' attribute named 'analyzerConf'. */
    private final String ATTRIBUTE_ANALYZER_CONF = "analyzerConf";

    /**String representation of the 'fieldList' attribute named 'analyzerClass'. */
    private final String ATTRIBUTE_ANALYZER_CLASS = "analyzerClass";

    /**String representation of the 'fieldList' attribute named 'analyzerConf'. */
    private String analyzer_conf;

    /**String representation of the 'fieldList' attribute named 'analyzerClass'. */
    private String analyzer_class;

    /**<p>This object is used for indexing; it contains a list of fields for a DocumentBase object
     * and contains defaults for fields that do not specify indexing information.<p>
     *
     */
    public FieldList() {

    }

    /** Sets the configuration options for the field definition.
     *
     * @param configuration      The configuration object from which to build the field definition.
     * @throws ConfigurationException
     */
    public void configure(Configuration configuration) throws ConfigurationException {
        super.configure(configuration);

        if (!Utilities.checkString(getId())) {//if no id try to set one
            String flId = configuration.getAttribute(Identifiable.ConfigurationNode.ID, new DefaultIDGenerator().generate());
            try {
                setId(flId);
            } catch (SDXException e) {
                throw new ConfigurationException(e.getMessage(), e);
            }
        }
        //getting 'analyzerConf' attr, default is null if it doesn't exist
        analyzer_conf = configuration.getAttribute(ATTRIBUTE_ANALYZER_CONF, null);
        //getting 'analyzerClass' attr, default is null if it doesn't exist
        analyzer_class = configuration.getAttribute(ATTRIBUTE_ANALYZER_CLASS, "");

        super.setLocale(Utilities.buildLocale(configuration, null));

        File analyzerConfFile = null;
        try {
            if (analyzer_conf != null) {
                //verifying the attribute, if it was specified
                ConfigurationUtils.checkConfAttributeValue(ATTRIBUTE_ANALYZER_CONF, analyzer_conf, configuration.getLocation());
                //getting the file
                analyzerConfFile = Utilities.resolveFile(null, configuration.getLocation(), super.getContext(), analyzer_conf, false);
            }
        } catch (ConfigurationException e) {
            //we want to continue
            LoggingUtils.logWarn(super.getLog(), null, e);
        } catch (SDXException e) {
            //we want to continue
            LoggingUtils.logWarn(super.getLog(), null, e);
        }

        try {
            AnalyzerManager analyzeMgr = (AnalyzerManager) super.getContext().get(ContextKeys.SDX.Framework.ANALYZER_MGR);
            if (Utilities.checkString(analyzer_class))
                this.analyzer = analyzeMgr.getAnalyzer(analyzer_class, analyzerConfFile);
            else
                this.analyzer = analyzeMgr.getAnalyzer(super.getLocale(), analyzerConfFile);
        } catch (SDXException sdxE) {
            throw new ConfigurationException(sdxE.getMessage(), sdxE.fillInStackTrace());
        } catch (ContextException e) {
            throw new ConfigurationException(e.getMessage(), e);
        }

        //giving the analyzer a super.getLog()
        this.analyzer.enableLogging(super.getLog());

        configureFields(configuration);


    }

    public void configureFields(Configuration configuration) throws ConfigurationException {

        String elementName = Utilities.getElementName(Field.CLASS_NAME_SUFFIX);
        //at this point, we should have a <sdx:fieldList> element containing a list of fields
        Configuration[] fields = new Configuration[configuration.getChildren(elementName).length];
        //getting an array of configuration objects from each of the <sdx:fields> subElements of <sdx:fieldList> element
        fields = configuration.getChildren(elementName);
        //testing to see if we have something
        if (fields == null | fields.length == 0) {
            String[] args = new String[1];
            //getting the location of the configuration file
            args[0] = configuration.getLocation();
            //null super.getLog() passed to prevent double logging
            SDXException sdxE = new SDXException(SDXExceptionCode.ERROR_NO_FIELDS_IN_CONFIG, args);
            LoggingUtils.logWarn(super.getLog(), sdxE.getMessage());
        }

        Field firstField = null;
        //building fields objects
        for (int i = 0; i < fields.length; i++) {
            try {
                //building the field
                Field field = new Field();
                //passing the super.getLog() from the _fieldList
                field.enableLogging(super.getLog());
                //setting properties
                field.contextualize(super.getContext());
                //setting up the field
                field.setUp(super.getLocale(), this.analyzer, null, null);//using the default analyzer class variable and locale for the fieldlist
                //configuring with each configuration object from the array
                field.configure(fields[i]);
                //adding them to this the _fieldList
                add(field);
                if (i == 0) firstField = field;
            } catch (SDXException e) {
                throw new ConfigurationException(e.getMessage(), e);
            } catch (ContextException e) {
                throw new ConfigurationException(e.getMessage(), e);
            }
        }
        //if there is no default field we use the first field
        if (!Utilities.checkString(this.defaultField)) this.defaultField = firstField.getCode();

    }

    /** Adds the internal fields defined in sdx.xconf to the list to allow searching
     * using these fields
     *
     * @param conf      The configuration object containing the list of internal fields
     * @throws SDXException
     * @throws ConfigurationException
     */
    public void addInternalFields(Configuration conf) throws SDXException, ConfigurationException {
        Configuration[] fields = conf.getChildren(Utilities.getElementName(Field.CLASS_NAME_SUFFIX));
        //testing to see if we have something
        if (fields == null | fields.length == 0) {
            String[] args = new String[1];
            //getting the location of the configuration file
            args[0] = conf.getLocation();
            //null super.getLog() passed to prevent double logging
            throw new SDXException(null, SDXExceptionCode.ERROR_NO_FIELDS_IN_CONFIG, args, null);
        }
        for (int i = 0; i < fields.length; i++) {
            try {
                Field field = new Field();
                field.enableLogging(super.getLog());
                //setting properties
                field.contextualize(super.getContext());
                //setting up the field
                field.setUp(super.getLocale(), this.analyzer, null, null);
                field.configure(fields[i]);
                add(field);
            } catch (ContextException e) {
                throw new ConfigurationException(e.getMessage(), e);
            }

        }
    }

    /**Provides access to the analyzer for the field definition.
     *
     * @return The analyzer for the field definition.
     */
    public Analyzer getAnalyzer() {
        return analyzer;
    }

    /**
     *	Returns the default search field.
     */
    public Field getDefaultField() {
        return (Field) this.fieldTable.get(defaultField);
    }

    /**
     *	Returns the field type.
     *
     *	@param	name	The name of the field.
     */
    public int getFieldType(String name) {
        if (name == null) return Field.WORD;		// Default value
        Field f = (Field) fieldTable.get(normalizeName(name));
        if (f == null) return Field.WORD;
        return f.getFieldType();
    }

    /**
     *	Adds a field to the list
     *
     *	@param	field		The field to add.
     */
    public void add(Field field) throws SDXException {
        if (fieldTable == null) fieldTable = new Hashtable();

        if ((Utilities.checkString(defaultField)) & field.isDefault()) {
            //a default field already exists please correct your application configuration file
            String[] args = new String[1];
            args[0] = field.getCode();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_DEFAULT_FIELD_EXISTS, args, null);
        }

        //otherwise we assign a value to the defaultField
        else if (field.isDefault())
            defaultField = field.getCode();

        //adding the field to the data structure
        //we allow overrides so uniqueness is not verified Utilities.isObjectUnique(this.fieldTable, field.getCode(), field);
        fieldTable.put(field.getCode(), field);
    }

    /**
     *	Normalise le nom d'un champ en fonction des regles de capitalisation.
     */
    private String normalizeName(String name) {
        if (name == null || !ignoreCase)
            return name;
        else
            return name.toLowerCase();
    }

    /**
     * Gets a Lucene field from a property.
     *
     *@param prop   The property from which the field will be dervived
     */
    public org.apache.lucene.document.Field getLuceneField(Property prop) throws SDXException {
        Field f = null;

        if (prop != null) f = (Field) fieldTable.get(prop.getName());
        if (f != null)
            return f.getLuceneField(prop.getValue());
        else
            return null;
    }

    /**
     * Returns a field given a field name.
     *
     *@param  name  The field name
     */
    public Field getField(String name) {
        return (Field) this.fieldTable.get(name);
    }

    /**
     * Returns a locale for a given field.
     */
    public Locale getLocale(String name) {
        return ((Field) this.fieldTable.get(name)).getLocale();
    }

    /**Returns all fields in the FieldList*/
    public Enumeration getFields() {
        if (fieldTable != null)
            return fieldTable.elements();
        else
            return null;
    }

    /** Returns a clone of this FieldsDef. */
    public Object clone() {
        try {
            return (FieldList) super.clone();
        } catch (CloneNotSupportedException e) {
            //TODOException:
            throw new RuntimeException("Clone not supported: " + e.getMessage());
        }
    }

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

	/* (non-Javadoc)
	 * @see fr.gouv.culture.sdx.utils.AbstractSdxObject#initToSax()
	 */
	protected boolean initToSax() {
		this._xmlizable_objects.put("ID", this.getId());
		this._xmlizable_objects.put("Encoding", this.getEncoding());
		this._xmlizable_objects.put("XML-Lang", this.getXmlLang());
		this._xmlizable_objects.put("Default_Field",this.defaultField);
		if(this.analyzer != null)
			this._xmlizable_objects.put("Default_Analyser",this.analyzer.getClass().getName());
		else
			this._xmlizable_objects.put("Default_Analyser","none");
		if(this.analyzer_conf != null)
			this._xmlizable_objects.put("Default_Analyser_Configuration",this.analyzer_conf);
		else
			this._xmlizable_objects.put("Default_Analyser_Configuration","none");
		this._xmlizable_objects.put("Ignore_Case",String.valueOf(this.ignoreCase));
		this._xmlizable_objects.put(Utilities.getElementName(Field.CLASS_NAME_SUFFIX),fieldTable);
		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() {
		this._xmlizable_objects.put("Ignore_Case",String.valueOf(this.ignoreCase));
	}


}
