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

import fr.gouv.culture.sdx.application.Application;
import fr.gouv.culture.sdx.exception.SDXException;
import fr.gouv.culture.sdx.exception.SDXExceptionCode;
import fr.gouv.culture.sdx.framework.Framework;
import fr.gouv.culture.sdx.search.lucene.Field;
import fr.gouv.culture.sdx.search.lucene.FieldList;
import fr.gouv.culture.sdx.search.lucene.analysis.Analyzer;
import fr.gouv.culture.sdx.thesaurus.Concept;
import fr.gouv.culture.sdx.thesaurus.SDXThesaurus;
import fr.gouv.culture.sdx.utils.Utilities;
import fr.gouv.culture.sdx.utils.constants.ContextKeys;
import fr.gouv.culture.sdx.utils.constants.Node;
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 org.apache.avalon.framework.parameters.ParameterException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.lucene.analysis.Token;
import org.apache.lucene.analysis.TokenStream;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import java.io.IOException;
import java.io.StringReader;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;


/**OK Give it a try, after your stylesheet transformation for generation an sdx indexation document
 *This transformation is higly dependent on the properties object passed to the pipeline at configuration time.
 * To make it less dependent, one should provide an "Application" object from which the class can access any
 * needed thesaurus, and a "FieldList" object from which text analysis can be done
 */
public class FieldExpansionTransformation extends AbstractTransformation {

    protected SDXThesaurus thesaurus = null;
    protected String defaultThesaurusId = null;
    protected String PARAM_NAME_DEPTH = "depth";
    protected String PARAM_NAME_LANG = "lang";
    protected String PARAM_NAME_TH = "th";
    protected StringBuffer charBuff = null;
    protected String fieldName = "";
    protected Hashtable expansionParams = null;
    protected HashSet expandedTerms = null;
    protected Application app = null;
    protected FieldList fields = null;

    public void setApplication(Application app) {
        this.app = app;
    }

    public void setFieldList(FieldList fields) {
        this.fields = fields;
    }

    public void setThesaurus(SDXThesaurus thesaurus) {
        this.thesaurus = thesaurus;
    }

    public void setThesaurus(String id) throws SDXException {
        if (Utilities.checkString(id)) {
            if (this.app == null)//if we don't have an app we try to get one
                this.app = Utilities.getApplication(super.getServiceManager(), super.getContext());
            if (app != null)
                this.thesaurus = app.getThesaurus(id);
        }
    }

    public void configure(Configuration configuration) throws ConfigurationException {


        super.configure(configuration);
        //getting the "src" attribute from the configuration
        String th = configuration.getAttribute(Transformation.ATTRIBUTE_TH);

        //TODO:isolate this to allow thesaurus selection based on attribute values
        if (Utilities.checkString(th))
            this.defaultThesaurusId = th;
    }


    /*
    <sdx:field code="fieldName" name="fieldName">some test here
        <sdx:expandField>
            <sdx:relation type="et" depth="0" lang="fr" th="id"/>
            <sdx:relation type="bt" depth="1" lang="fr"/>
            <sdx:relation type="nt" depth="2" lang="fr"/>
            <sdx:relation type="uf" depth="3" lang="fr"/>
        </sdx:expandField>
    </sdx:field>

    */
    public void startElement(String uri, String loc, String raw, Attributes a)
            throws SAXException {
        if (Framework.SDXNamespaceURI.equals(uri)) {
            if (Node.Name.FIELD.equals(loc)) {
                if (a != null) {
                    //setting the field name
                    this.fieldName = a.getValue(Node.Name.NAME);
                    //backward compatibility : try to use old attribute name
                    if (!Utilities.checkString(this.fieldName))
                        this.fieldName = a.getValue(Node.Name.CODE);
                    //if we have a good field name we prepare to collect the value
                    if (Utilities.checkString(this.fieldName))
                        this.charBuff = new StringBuffer();
                }
                super.startElement(uri, loc, raw, a);
            } else if (Node.Name.RELATION.equals(loc)) {
                //we don't want to send this event
                if (expansionParams == null) this.expansionParams = new Hashtable();
                if (expandedTerms == null) this.expandedTerms = new HashSet();
                String relationType = a.getValue(Node.Name.TYPE);
                String depth = a.getValue(Node.Name.DEPTH);
                String lang = a.getValue(Node.Name.LANG);
                String th = a.getValue(Node.Name.TH);
                if (Utilities.checkString(relationType)) {
                    Parameters params = new Parameters();
                    if (Utilities.checkString(depth))
                        params.setParameter(PARAM_NAME_DEPTH, depth);
                    if (Utilities.checkString(lang))
                        params.setParameter(PARAM_NAME_DEPTH, lang);
                    if (Utilities.checkString(th))
                        params.setParameter(PARAM_NAME_TH, th);
                    this.expansionParams.put(relationType, params);
                }
            } else if (!Node.Name.EXPAND_FIELD.equals(loc))
                super.startElement(uri, loc, raw, a);
        } else
            super.startElement(uri, loc, raw, a);
    }

    public void characters(char c[], int start, int len)
            throws SAXException {
        //saving our field value for field expansion
        if (this.charBuff != null)
            this.charBuff.append(c, start, len);
        super.characters(c, start, len);
    }


    /*
    <sdx:field code="fieldName" name="fieldName">some test here
        <sdx:expandField>
            <sdx:relation type="et" depth="0" lang="fr" th="id"/>
            <sdx:relation type="bt" depth="1" lang="fr"/>
            <sdx:relation type="nt" depth="2" lang="fr"/>
            <sdx:relation type="uf" depth="3" lang="fr"/>
        </sdx:expandField>
    </sdx:field>

    */
    public void endElement(String uri, String loc, String raw)
            throws SAXException {
        //send parent field
        //do expansion with thesaurus and analysis (if field type of WORD)
        //send expanded terms as fields
        if (Framework.SDXNamespaceURI.equals(uri)) {
            if (Node.Name.FIELD.equals(loc)) {
                super.endElement(uri, loc, raw);

                if (this.expansionParams != null) {
                    String fieldValue = null;
                    if (this.charBuff != null && this.charBuff.length() > 0)
                        fieldValue = this.charBuff.toString();
                    if (Utilities.checkString(fieldValue)) {
                        try {
                            //getting the fieldsDefintion object if not already provided
                            if (this.fields == null)
                                this.fields = (FieldList) super.getContext().get(ContextKeys.SDX.DocumentBase.FIELD_LIST);
                            if (this.fields != null) {
                                Field field = this.fields.getField(fieldName);
                                if (field != null) {
                                    if (field.getFieldType() == Field.WORD) {
                                        TokenStream tokenStream = null;
                                        Analyzer searchTermFieldAnalyzer = field.getAnalyzer();
                                        if (searchTermFieldAnalyzer != null)
                                            tokenStream = searchTermFieldAnalyzer.tokenStream(fieldName, new StringReader(fieldValue));
                                        //if we have a token stream we tokenize the field value for expanded terms
                                        if (tokenStream != null) {
                                            Token token = null;
                                            try {
                                                while ((token = tokenStream.next()) != null) {
                                                    //we expanded the token term and send it as a field value
                                                    String tokenText = token.termText();
                                                    try {
                                                        expandField(fieldName, tokenText, expansionParams);
                                                    } catch (SDXException e) {
                                                        //not going to throw this because we don't want to break the entire expansion
                                                    }
                                                }
                                                tokenStream.close();
                                            } catch (IOException e) {
                                                //not throwing the exception here, because we could have valuable expanded terms already added
                                                new SDXException(super.getLog(), SDXExceptionCode.ERROR_CLOSE_STREAM, null, e);
                                            }

                                        }
                                    } else {//we have a field which isn't of type word so we expand on the full value without tokenizing
                                        expandField(fieldName, fieldValue, expansionParams);
                                    }

                                }
                            }

                        } catch (SDXException e) {
                            //not going to throw this because we don't want to break the entire expansion
                        } catch (ContextException e) {
                            LoggingUtils.logException(getLog(), e);
                        }

                    }
                }
                this.fieldName = "";
                this.charBuff = null;
                this.expansionParams = null;
                this.expandedTerms = null;
            } else if (!Node.Name.EXPAND_FIELD.equals(loc) & !Node.Name.RELATION.equals(loc))
                super.endElement(uri, loc, raw);
        } else
            super.endElement(uri, loc, raw);
    }

    protected void expandField(String fieldName, String fieldValue, Hashtable expansionParams) throws SDXException, SAXException {
        if (Utilities.checkString(fieldName) && Utilities.checkString(fieldValue) && expansionParams != null) {

            Enumeration relationTypes = expansionParams.keys();
            if (relationTypes != null) {
                int relation = -1;
                String relationType = (String) relationTypes.nextElement();
                if (relation > -1) {
                    Parameters params = (Parameters) expansionParams.get(relationType);
                    String th = null;
                    try {
                        th = params.getParameter(PARAM_NAME_TH);
                    } catch (ParameterException e) {
                        th = defaultThesaurusId;
                    }
                    if (this.thesaurus == null || !th.equals(thesaurus.getId()))
                        this.setThesaurus(th);
                    Concept[] matches = null;
                    if (this.thesaurus != null){
                        matches = thesaurus.search(fieldValue);
                        if (Utilities.checkString(relationType))
                            relation = thesaurus.getRelationTypeInt(relationType);
                    }
                    if (matches != null) {
                        int depth = -1;
                        String lang = null;
                        try {
                            depth = params.getParameterAsInteger(PARAM_NAME_DEPTH);
                        } catch (ParameterException e) {
                        }
                        try {
                            lang = params.getParameter(PARAM_NAME_LANG);
                        } catch (ParameterException e) {
                        }
                        Concept[] relatedTerms = null;
                        //if we dont have a good depth we use the default
                        if (depth == -1) depth = thesaurus.getDefaultDepth();
                        String[] langs = null;
                        if (Utilities.checkString(lang)) {
                            langs = new String[1];
                            langs[0] = lang;
                        }
                        if (langs != null)
                            relatedTerms = thesaurus.getRelations(matches, relation, depth, langs);
                        else
                            relatedTerms = thesaurus.getRelations(matches, relation, depth);

                        if (relatedTerms != null) {
                            for (int i = 0; i < relatedTerms.length; i++) {
                                Concept relatedTerm = relatedTerms[i];
                                if (relatedTerm != null) {
                                    String rterm = relatedTerm.getValue();
                                    if (Utilities.checkString(rterm)) {
                                        if (!expandedTerms.contains(rterm)) {
                                            sendFieldEvent(fieldName, rterm);
                                            expandedTerms.add(rterm);
                                        }

                                    }
                                }
                            }
                        }

                    }

                }

            }
        }

    }

    private void sendFieldEvent(String fieldName, String value) throws SAXException {
        if (Utilities.checkString(fieldName) && Utilities.checkString(value)) {
            String sdxNsUri = Framework.SDXNamespaceURI;
            String elName = Node.Name.FIELD;
            String elQName = Framework.SDXNamespacePrefix + ":" + elName;

            AttributesImpl atts = new AttributesImpl();
            atts.addAttribute("", Node.Name.NAME, Node.Name.NAME, Node.Type.CDATA, fieldName);

            super.startElement(sdxNsUri, elName, elQName, atts);

            char[] chars = value.toCharArray();
            super.characters(chars, 0, chars.length);

            super.endElement(sdxNsUri, elName, elQName);
        }
    }

    /* (non-Javadoc)
	 * @see fr.gouv.culture.sdx.utils.xml.AbstractSdxXMLPipe#initToSax()
	 */
	protected boolean initToSax() {
		if(!super.initToSax())
			return false;
		else{
			this._xmlizable_objects.put("Name",this.getClass().getName());
			return true;
		}
	}


}
