/*
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.document.ParsableDocument;
import fr.gouv.culture.sdx.document.XMLDocument;
import fr.gouv.culture.sdx.documentbase.DocumentBase;
import fr.gouv.culture.sdx.documentbase.LuceneDocumentBase;
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.framework.FrameworkImpl;
import fr.gouv.culture.sdx.utils.Utilities;
import fr.gouv.culture.sdx.utils.configuration.ConfigurationUtils;
import fr.gouv.culture.sdx.utils.constants.Node;
import fr.gouv.culture.sdx.utils.logging.LoggingUtils;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.parameters.ParameterException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.cocoon.xml.EmbeddedXMLPipe;
import org.apache.cocoon.xml.XMLConsumer;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

/**Retrieves and passes result documents via the xmlConsumer,
 * may strip other information from provided events if parameters are set.
 * Attention: It is possible to receive no events with the correct set of parameters!
 *
 */
public class GetDocumentsTransformation extends AbstractTransformation {

    /**parameter used to determine which sax events are desired*/
    private boolean sendUserFields = true; //by default we will give the user fields

    /**parameter used to determine which sax events are desired*/
    private boolean sendInternalFields = false; //by default we want to remove internal fields

    /**parameter used to determine which sax events are desired*/
    private boolean sendSDXElements = true;  //by default we want to give all elements in the sdx namespace

    /**parameter used to determine which sax events are desired*/
    private boolean sendAllFields = true; //by default we want to give all the fields of a document

    /**boolean to represent our parsing context*/
    private boolean withinSdxElement = false;

    /**boolean to represent our parsing context*/
    private boolean withinField = false;

    /**boolean to represent our parsing context*/
    private boolean withinSdxInternalField = false;

    /**boolean to represent our parsing context*/
    private boolean withinUserField = false;

    /**The name of the field we are manipulating*/
    private String currentField = "";

    //application id for the internal field
    private String appId = "";

    //documentBase id for the internal field
    private String dbId = "";

    //the document id for the internal field
    private String docId = "";

    /**String representation for a transformation parameter*/
    public static final String SEND_USER_FIELDS = "sendUserFields";

    /**String representation for a transformation parameter*/
    public static final String SEND_INTERNAL_FIELDS = "sendInternalFields";

    /**String representation for a transformation parameter*/
    public static final String SEND_SDX_ELEMENTS = "sendSDXElements";

    /**String representation for a transformation parameter*/
    public static final String SEND_ALL_FIELDS = "sendAllFields";

    /**Int to represent the current event*/
    private final int UNKNOWN = -1;

    /**Int to represent the current event*/
    private final int START = 0;

    /**Int to represent the current event*/
    private final int CHARS = 1;

    /**Int to represent the current event*/
    private final int END = 2;

    /**Int to represent the current event*/
    private int currentEvent = UNKNOWN;

    private String sUri = "";
    private String sLocal = "";
    private String sQName = "";
    private Attributes sAttr = null;
    private String eUri = "";
    private String eLocal = "";
    private String eQName = "";
    private char[] c = null;
    private int start = -1;
    private int len = -1;

    /*
     <sdx:result no="1" score="0.09136329" pctScore="100" >
      <sdx:field name="sdxdoctype" indexed="true" tokenized="false" >HTMLDocument</sdx:field>
      <sdx:field name="sdxappid" indexed="true" tokenized="false" >fr.gouv.culture.sdx.sdxworld</sdx:field>
      <sdx:field name="sdxdbid" indexed="true" tokenized="false" >sites</sdx:field>
      <sdx:field name="sdxall" indexed="true" tokenized="false" >1</sdx:field>
      <sdx:field name="sdxdocid" indexed="true" tokenized="false" >http://www.google.com/en</sdx:field>
      <sdx:field name="titre" indexed="true" tokenized="false" >Google</sdx:field>
      </sdx:result>
   */
    /**Could be used to configure this object, but currently has no function
     *
     * @param configuration
     * @throws ConfigurationException
     */
    public void configure(Configuration configuration) throws ConfigurationException {
        super.configure(configuration);
    }


    /**Set's the parameters for this transformation
     *
     * @param p     The parameters
     */
    public void setParameters(Parameters p) {
        //setting params field is set
        super.setParameters(p);
        //loading our basic params
        loadSendParams();
    }

    /**Loads the parameters for this class as specified by the user when setParameter(params) method is called*/
    private void loadSendParams() {
        if (this.getParameters() != null) {
            //loading the params for event generation
            Parameters params = this.getParameters();
            //getting the param names
            String[] paramNames = params.getNames();
            //setting the sendParams if they exist in the params object
            for (int i = 0; i < paramNames.length; i++) {

                try {
                    if (paramNames[i].equals(SEND_SDX_ELEMENTS))
                        this.sendSDXElements = new Boolean(params.getParameter(paramNames[i])).booleanValue();
                    if (paramNames[i].equals(SEND_ALL_FIELDS))
                        this.sendAllFields = new Boolean(params.getParameter(paramNames[i])).booleanValue();
                    if (paramNames[i].equals(SEND_INTERNAL_FIELDS))
                        this.sendInternalFields = new Boolean(params.getParameter(paramNames[i])).booleanValue();
                    if (paramNames[i].equals(SEND_USER_FIELDS))
                        this.sendUserFields = new Boolean(params.getParameter(paramNames[i])).booleanValue();
                } catch (ParameterException e) {
                    //TODO?:should we be throwing and exception or just logging?
                    LoggingUtils.logException(super.getLog(), e);
                }
            }
        }
        /*is this ok, i am not sure as the user could set send all fields to false and set the others to true,
        so i think for now we want a true, but in the future with other types of fields we wont want this?*/
        if ((sendUserFields && sendInternalFields) && !sendAllFields) sendAllFields = true;
        if (!sendUserFields && !sendInternalFields) sendAllFields = false;
        if ((!sendInternalFields | !sendUserFields) && sendAllFields) sendAllFields = false;

    }


    /**Filters elements*/
    public void startElement(String uri, String local, String qName, Attributes attr) throws SAXException {
        setCurrentEvent(START);
        this.sUri = uri;
        this.sLocal = local;
        this.sQName = qName;
        this.sAttr = attr;
        determineParsingPosition(sUri, sLocal, sQName, sAttr);
        generateEvent();
    }

    /**Filters characters*/
    public void characters(char c[], int start, int len) throws SAXException {
        //is this a bad idea here, i think it may be better in the endElem method
        if (withinSdxInternalField) {
            StringBuffer stringBuff = new StringBuffer();       //string buffer to keep internal field element values
            stringBuff.append(c, start, len);
            if (Utilities.checkString(currentField)) {
                if (currentField.equals(LuceneDocumentBase.INTERNAL_FIELD_NAME_SDXAPPID)) appId = stringBuff.toString();
                if (currentField.equals(LuceneDocumentBase.INTERNAL_FIELD_NAME_SDXDOCID)) docId = stringBuff.toString();
                if (currentField.equals(LuceneDocumentBase.INTERNAL_FIELD_NAME_SDXDBID)) dbId = stringBuff.toString();
            }
        }

        setCurrentEvent(CHARS);
        this.c = c;
        this.start = start;
        this.len = len;
        generateEvent();
    }

    /**Filters elements*/
    public void endElement(String uri, String local, String qName) throws SAXException {
        setCurrentEvent(END);
        this.eUri = uri;
        this.eLocal = local;
        this.eQName = qName;
        determineParsingPosition(eUri, eLocal, eQName, null);
        generateEvent();
    }

    private void setCurrentEvent(int event) {
        currentEvent = event;
    }


    private void determineParsingPosition(String uri, String local, String qName, Attributes attr) {
        //resetting our values
        withinSdxElement = withinSdxInternalField = withinUserField = false;
        //determining our current context, if we have a valid string, and assigning values
        if (Utilities.checkString(uri))
            withinSdxElement = uri.equals(Framework.SDXNamespaceURI);

        if (withinSdxElement) {
            if (Utilities.checkString(local) && local.equals(Node.Name.FIELD)) {
                withinField = true;
                if (attr != null) {
                    String fieldName = attr.getValue("", Node.Name.NAME);
                    if (Utilities.checkString(fieldName)) {
                        currentField = fieldName;
                    }
                }
                if (currentField.startsWith("sdx")) {
                    //if we are in a valid internal field, we assign the value we need
                    withinSdxInternalField = true;
                } else
                    withinUserField = true;
            } else {
                withinField = false;
            }
        }
    }

    /**Generates an appropriate event based upon user params and parsing context*/
    private void generateEvent() throws SAXException {
        if (withinSdxElement) {
            if (sendSDXElements) {
                if (withinField) {
                    //if we are within a field we send the events
                    if (sendAllFields)
                        sendEvent();
                    else {
                        // sendAllFields is false and we want to determine which fields to send based upon the params
                        if (withinSdxInternalField && sendInternalFields)
                            sendEvent();
                        if (withinUserField && sendUserFields)
                            sendEvent();
                    }
                } else {
                    //we are within and sdx element but not a field and we want to send the event
                    //if we are at the end of an element sdx:result, we must send the document before ending the element
                    if (currentEvent == END) {
                        if (Utilities.checkString(eLocal) && eLocal.equals(Node.Name.RESULT)) {
                            try {
                                getDocumentEvents();
                            } catch (SDXException e) {
                                throw new SAXException(e.getMessage(), e);
                            }
                        }
                    }
                    sendEvent();
                }
            } else {

                //we are within an sdx element but dont want to send it

                //we are within and sdx element but not a field and we want to send the event
                //if we are at the end of an element sdx:result, we must send the document before ending the element
                // FG:no sdx:* but the docs
                if (currentEvent == END) {
                    if (Utilities.checkString(eLocal) && eLocal.equals(Node.Name.RESULT)) {
                        try {
                            getDocumentEvents();
                        } catch (SDXException e) {
                            throw new SAXException(e.getMessage(), e);
                        }
                    }
                }
                if (withinSdxInternalField && sendInternalFields)
                    sendEvent();
                if (withinUserField && sendUserFields)
                    sendEvent();
            }
        } else {
            //at this point we are not within and sdx element and we should send it
            //ignoring the elements which are in the SDX namespace, and forwarding the other events
            sendEvent();
        }
    }

    private void sendEvent() throws SAXException {
        switch (currentEvent) {
            case START:
                this.contentHandler.startElement(sUri, sLocal, sQName, sAttr);
                resetEventValues();
                break;
            case CHARS:
                this.contentHandler.characters(c, start, len);
                resetEventValues();
                break;
            case END:
                this.contentHandler.endElement(eUri, eLocal, eQName);
                resetEventValues();
                break;
            default:
                //TODOException?: better error here
                throw new SAXException("unable to determine event to send");
        }
    }

    private void resetEventValues() throws SAXException {
        switch (currentEvent) {
            case START:
                sUri = sLocal = sQName = "";
                sAttr = null;
                break;
            case CHARS:
                c = null;
                start = -1;
                len = -1;
                break;
            case END:
                eUri = eLocal = eQName = "";
                break;
            default:
                //TODOException:better error here
                throw new SAXException("unable to determine event for which values need to be reset");

        }
        setCurrentEvent(UNKNOWN);

    }

    private void getDocumentEvents() throws SDXException {
        ServiceManager manager = super.getServiceManager();
        //TODO?:we need some generic events to wrap around the result
        FrameworkImpl frame = null;
        try {
            ConfigurationUtils.checkServiceManager(manager);
            if (Utilities.checkString(appId) && Utilities.checkString(dbId) && Utilities.checkString(docId)) {
                //getting the framework service
                frame = (FrameworkImpl) manager.lookup(Framework.ROLE);
                if (frame != null) {
                    Application app = frame.getApplicationById(appId);
                    if (app != null) {
                        DocumentBase db = (DocumentBase) app.getSearchable(dbId);
                        if (db != null) {
                            ParsableDocument doc = new XMLDocument(docId);
                            //consumer to strip start and end document events of the documents retrieved and pass them to the transformation's handler
                            XMLConsumer consumer = new EmbeddedXMLPipe(this.contentHandler);
                            //retrieving the doc and passing it to the "xmlconsumer" via the "consumer" of this transformation
                            //passing false because we aren't sure what type of document is represented
                            db.getDocument(doc, consumer, false);
                        }
                    }
                }
            }
        } catch (SDXException e) {
            throw e;
        } catch (ServiceException e) {
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_LOOKUP_FRAMEWORK_SERVICE, null, e);
        } finally {
            if (frame != null) manager.release(frame);
            resetDocRetrievalFields();
        }

    }

    /**Resets our booleans to represent a new state*/
    private void resetDocRetrievalFields() {
        currentField = null;
        docId = null;
        dbId = null;
        appId = null;
    }
    
    /* (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;
		}
	}

}
