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

import fr.gouv.culture.oai.AbstractOAIMetadataFormat;
import fr.gouv.culture.oai.OAIObject;
import fr.gouv.culture.oai.util.OAIUtilities;
import fr.gouv.culture.sdx.pipeline.GenericPipeline;
import fr.gouv.culture.sdx.pipeline.Pipeline;
import fr.gouv.culture.sdx.utils.Utilities;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.context.ContextException;
import org.apache.avalon.framework.parameters.Parameters;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import java.util.Enumeration;
import java.util.Hashtable;

public class BasicOAIMetadataFormat extends AbstractOAIMetadataFormat implements Serviceable {

    /**The service manager for this object*/
    protected ServiceManager manager = null;
    /**The ContextKeys for this metadata format*/
    //protected DefaultContext _context = null;
    /**The pipeline for for this object*/
    protected Pipeline pipe = null;
    /**Parameters object containing information about which sdx fields to concatenate*/
    protected Parameters concatenateFields = null;
    /**Object for temporary storage of aggregated sdx fields*/
    protected Hashtable aggregatedFields = null;
    /**Default separator for field concatenation*/
    protected String DEFAULT_CONCATENATED_FIELD_SEPARATOR = " ; ";

    //configuration node names
    /**Configuration node names*/
    /**Configuration node names*/
    protected final String ELEMENT_NAME_OAI_FIELDS = "oai-fields";
    /**Configuration node names*/
    protected final String ELEMENT_NAME_OAI_FIELD = "oai-field";
    /**Configuration node names*/
    protected final String ATTRIBUTE_NAME_NAME = "name";
    /**Configuration node names*/
    protected final String ATTRIBUTE_NAME_METADATA_PREFIX = "metadataPrefix";
    /**Configuration node names*/
    protected final String ATTRIBUTE_NAME_NAMESPACE = "namespace";
    /**Configuration node names*/
    protected final String ATTRIBUTE_NAME_SCHEMA_URL = "schemaUrl";
    /**Configuration node names*/
    protected final String ATTRIBUTE_NAME_ROOT_ELEMENT = "rootElement";
    /**Configuration node names*/
    protected final String ATTRIBUTE_NAME_SDXFIELD = "sdxField";
    /**Configuration node names*/
    protected final String ATTRIBUTE_NAME_REPEATED = "repeated";
    /**Configuration node names*/
    protected final String ATTRIBUTE_NAME_SEPARATOR = "separator";
    /**Configuration node names*/
    protected final String ATTRIBUTE_VALUE_CONCATENATE = "concatenate";


    /**Establishes the service manager for this object*/
    public void service(ServiceManager serviceManager) throws ServiceException {
        this.manager = serviceManager;
    }

    /**Configures this object*/
    public void configure(Configuration configuration) throws ConfigurationException {
        if (configuration != null) {
            //at this point we have an "oai-format" element
            this.formatName = configuration.getAttribute(ATTRIBUTE_NAME_NAME);
            this.prefix = configuration.getAttribute(ATTRIBUTE_NAME_METADATA_PREFIX);
            this.namespace = configuration.getAttribute(ATTRIBUTE_NAME_NAMESPACE);
            this.schemaUrl = configuration.getAttribute(ATTRIBUTE_NAME_SCHEMA_URL);
            this.rootElement = configuration.getAttribute(ATTRIBUTE_NAME_ROOT_ELEMENT, "");

            //configuring the metadata field mappings
            configureFields(configuration);
            //configuring the pipeline if any
            configurePipeline(configuration);

        }

    }

    /**Configures field mappings
     *
     * @param configuration
     * @throws ConfigurationException
     */
    protected void configureFields(Configuration configuration) throws ConfigurationException {
        if (configuration != null) {
            Configuration[] oaiFieldsConf = configuration.getChild(ELEMENT_NAME_OAI_FIELDS, true).getChildren(ELEMENT_NAME_OAI_FIELD);
            if (oaiFieldsConf.length > 0) {
                this.metadataMappings = new Parameters();
                for (int i = 0; i < oaiFieldsConf.length; i++) {
                    Configuration oaiFieldConf = oaiFieldsConf[i];
                    String sdxFieldName = oaiFieldConf.getAttribute(ATTRIBUTE_NAME_SDXFIELD);
                    String oaiFieldName = oaiFieldConf.getAttribute(ATTRIBUTE_NAME_NAME);
                    String repeated = oaiFieldConf.getAttribute(ATTRIBUTE_NAME_REPEATED, ATTRIBUTE_NAME_REPEATED);
                    if (ATTRIBUTE_VALUE_CONCATENATE.equals(repeated)) {
                        if (concatenateFields == null) concatenateFields = new Parameters();
                        String separator = oaiFieldConf.getAttribute(ATTRIBUTE_NAME_SEPARATOR, DEFAULT_CONCATENATED_FIELD_SEPARATOR);
                        concatenateFields.setParameter(sdxFieldName, separator);
                    }
                    this.metadataMappings.setParameter(sdxFieldName, oaiFieldName);
                }
            }
        }

    }

    /**Configures the pipeline for process documents and search fields
     *
     * @param configuration
     * @throws ConfigurationException
     */
    protected void configurePipeline(Configuration configuration) throws ConfigurationException {
        if (configuration != null) {
            //at this point, we should have a <sdx:pipeline> element
            Configuration pipeConf = configuration.getChild(Utilities.getElementName(Pipeline.CLASS_NAME_SUFFIX), false);
            if (pipeConf != null) {
                if (this.metadataMappings != null)//TODOException : a better message
                    throw new ConfigurationException("both field mappings and a pipeline are not supported, only one or the other");
            }
            //testing if we have something
            //creating the pipeline and assigning the class field
            this.pipe = new GenericPipeline();
            //setting the super.getLog() for the pipeline object
            this.pipe.enableLogging(this.logger);
            //giving the object the cocoon service manager
            try {
                this.pipe.service(this.manager);
                this.pipe.contextualize(Utilities.createNewReadOnlyContext(_context));
            } catch (ServiceException e) {
                throw new ConfigurationException(e.getMessage(), e);
            } catch (ContextException e) {
                throw new ConfigurationException(e.getMessage(), e);
            }
            if (pipeConf != null) {
                //configuring the pipeline object from 'application.xconf'
                this.pipe.configure(pipeConf);
            }
        }


    }

    /**Sends an element to the object's consumer
     * doing any aggregation and/or concatenation
     * @param origName
     * @param value
     * @throws SAXException
     */
    public void sendElement(String origName, String value) throws SAXException {
        if (OAIUtilities.checkString(origName) && OAIUtilities.checkString(value)) {
            if (this.concatenateFields != null && this.concatenateFields.isParameter(origName))
                aggregateFields(origName, value);
            else
                prepareAndSendElement(origName, value);
        }
    }


    /**Aggregates field values to temporary storage
     *
     * @param fieldName
     * @param valueToConcat
     */
    protected void aggregateFields(String fieldName, String valueToConcat) {
        String aggreatedValue = null;
        if (aggregatedFields == null)
            aggregatedFields = new Hashtable();
        else
            aggreatedValue = (String) aggregatedFields.get(fieldName);
        String separator = concatenateFields.getParameter(fieldName, DEFAULT_CONCATENATED_FIELD_SEPARATOR);
        if (Utilities.checkString(aggreatedValue))
            aggreatedValue += separator + valueToConcat;
        else
            aggreatedValue = valueToConcat;
        aggregatedFields.put(fieldName, aggreatedValue);
    }

    /**Sends all aggregatged fields*/
    protected void sendAggregatedFields() throws SAXException {
        if (this.aggregatedFields != null) {
            Enumeration keys = this.aggregatedFields.keys();
            if (keys != null) {
                while (keys.hasMoreElements()) {
                    String fieldName = (String) keys.nextElement();
                    if (Utilities.checkString(fieldName)) {
                        String fieldValue = (String) this.aggregatedFields.remove(fieldName);
                        if (Utilities.checkString(fieldValue))
                            prepareAndSendElement(fieldName, fieldValue);
                    }
                }
                //resetting the hashtable
                this.aggregatedFields = null;
            }
        }
    }

    /**Sends any aggregated fields before sending the END
     * event for the root element of the metadata format
     * @throws SAXException
     */
    public void endMetadataFormatRootElement() throws SAXException {
        sendAggregatedFields();
        super.endMetadataFormatRootElement();
    }

    /**Retrieves the pipeline for processing
     * documents into metadata
     *
     * @return
     */
    public Pipeline getPipeline() {
        return this.pipe;
    }

    /**Retrieves a special pipeline for
     * adding an attribute to the root element
     * of the metadata format
     *
     * @return
     */
    public Pipeline getAddRootAttributePipe() {
        return new AddRootAttributePipe(super.namespace, super.schemaUrl);
    }

    /**Adds the "schemaLocation" attribute to the root element of
     * a metadata format
     *
     */
    private class AddRootAttributePipe extends GenericPipeline {

        int elementCount = 0;
        String schemaLocVal = "";

        public AddRootAttributePipe(String ns, String schemaLoc) {
            this.schemaLocVal = ns + " " + schemaLoc;
        }

        public void startElement(String uri, String loc, String raw, Attributes a)
                throws SAXException {
            elementCount++;

            if (elementCount == 1) {
                AttributesImpl new_atts = new AttributesImpl(a);
//                if ( /*isXmlnsXsiPresent*/new_atts.getIndex(OAIObject.Node.Name.XMLNS+":"+OAIObject.Node.Prefix.XSI) < 0 )
//                	new_atts.addAttribute("", OAIObject.Node.Name.XMLNS + ":" + OAIObject.Node.Prefix.XSI, OAIObject.Node.Name.XMLNS + ":" + OAIObject.Node.Prefix.XSI, OAIObject.Node.Type.CDATA, OAIObject.Node.Xmlns.XSI);
                if ( /*isSchemaLocationPresent*/new_atts.getIndex(OAIObject.Node.Name.Qualified.XSI_SCHEMA_LOCATION) < 0 )
                    new_atts.addAttribute(OAIObject.Node.Xmlns.XSI, "schemaLocation", OAIObject.Node.Name.Qualified.XSI_SCHEMA_LOCATION, OAIObject.Node.Type.CDATA, this.schemaLocVal);
                super.startElement(uri, loc, raw, new_atts);

            } else
                super.startElement(uri, loc, raw, a);
        }


    }

}
