/*
Copyright (C) 2000-2010  Ministere de la culture et de la communication (France), AJLSM
See LICENCE file
*/
/*
 * Created by IntelliJ IDEA.
 * User: rpandey
 * Date: 9 oct. 2002
 * Time: 17:36:00
 * To change template for new class use
 * Code Style | Class Templates options (Tools | IDE Options).
 */
package fr.gouv.culture.sdx.pipeline;

import fr.gouv.culture.sdx.application.Application;
import fr.gouv.culture.sdx.document.Document;
import fr.gouv.culture.sdx.document.HTMLDocument;
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 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.cocoon.ProcessingException;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

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

public class DeleteResultsTransformation extends AbstractTransformation {


    /**The id of the application from which we are permitted to delete documents*/
    private String deleteAppId = "";

    /**boolean to represent our parsing context*/
    private boolean withinSdxInternalField = 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 = "";

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

    // the index in results order
//    private String no = "";

    /**A hashtable holding groups of documents per document base*/
    private Hashtable deletions = null;

    /*
     <sdx:result no="1" score="0.09136329" pctScore="100" >
      <sdx:field name="sdxdoctype" indexed="true" tokenized="false" value="HTMLDocument"/>
      <sdx:field name="sdxappid" indexed="true" tokenized="false" value="fr.gouv.culture.sdx.sdxworld"/>
      <sdx:field name="sdxdbid" indexed="true" tokenized="false" value="sites"/>
      <sdx:field name="sdxall" indexed="true" tokenized="false" value="1"/>
      <sdx:field name="sdxdocid" indexed="true" tokenized="false" value="http://www.google.com/en"/>
      <sdx:field name="titre" indexed="true" tokenized="false" value="Google"/>
      </sdx:result>
   */

    /**Set's the id of the application from which the document's should be deleted
     *
     * @param appId
     */
    public void setApplicationId(String appId) throws SDXException {
        //this is a core piece of information for this class to work properly
        if (!Utilities.checkString(appId))
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_DELETE_APP_ID, null, null);
        this.deleteAppId = appId;
    }

    /**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);
    }

    /**Filters elements*/
    public void startElement(String uri, String local, String qName, Attributes attr) throws SAXException {
        if (qName.equals(Framework.SDXNamespacePrefix + ":" + Node.Name.RESULTS))
            this.contentHandler.startElement(Framework.SDXNamespaceURI, Node.Name.DELETIONS, Framework.SDXNamespacePrefix + ":" + Node.Name.DELETIONS, attr);

        if (qName.equals(Framework.SDXNamespacePrefix + ":" + Node.Name.QUERY))
            this.contentHandler.startElement(uri, local, qName, attr);

        determineParsingPosition(uri, local, qName, attr);
    }

    /**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);
            //TODO:handle this in value attribute
            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();
                if (currentField.equals(LuceneDocumentBase.INTERNAL_FIELD_NAME_SDXDOCTYPE)) doctype = stringBuff.toString();
            }
        }

    }

    public void endElement(String uri, String local, String qName) throws SAXException {

        if (qName.equals(Framework.SDXNamespacePrefix + ":" + Node.Name.QUERY))
            this.contentHandler.endElement(uri, local, qName);

        try {
            if (isCurrentAppDeleteApp() && qName.equals(Framework.SDXNamespacePrefix + ":" + Node.Name.RESULT))
                queueDocumentForDeletion();//this method will do nothing until we have our necessary parameters
        } catch (SDXException e) {
            throw new SAXException(e.getMessage(), e);
        }
        //at the end of the result set we make the deletion
        if (qName.equals(Framework.SDXNamespacePrefix + ":" + Node.Name.RESULTS)) {
            try {
                deleteDocuments();
            } catch (SDXException e) {
                throw new SAXException(e.getMessage(), e);
            } finally {
                this.contentHandler.endElement(Framework.SDXNamespaceURI, Node.Name.DELETIONS, Framework.SDXNamespacePrefix + ":" + Node.Name.DELETIONS);

            }
        }
    }

    private void deleteDocuments() throws SDXException, SAXException {
        ServiceManager manager = super.getServiceManager();
        FrameworkImpl frame = null;
        try {
            ConfigurationUtils.checkServiceManager(manager);
            frame = (FrameworkImpl) manager.lookup(Framework.ROLE);
            if (frame != null) {
                Application app = frame.getApplicationById(this.deleteAppId);
                if (app != null) {
                    Enumeration docbaseIds = null;
                    if (this.deletions != null)
                        docbaseIds = this.deletions.keys();
                    if (docbaseIds != null) {
                        while (docbaseIds.hasMoreElements()) {
                            DocumentBase docbase = null;
                            Document[] docs = null;
                            String docbaseId = "";
                            ArrayList listDocs = null;

                            //getting the next document base id
                            docbaseId = (String) docbaseIds.nextElement();

                            if (Utilities.checkString(docbaseId)) {
                                listDocs = (ArrayList) this.deletions.get(docbaseId);
                                docbase = app.getDocumentBase(docbaseId);
                            }

                            if (listDocs != null) {
                                listDocs.trimToSize();
                                docs = new Document[listDocs.size()];
                                docs = (Document[]) listDocs.toArray(docs);
                            }

                            docbase = app.getDocumentBase(docbaseId);
                            if (docbase != null && docs != null) {
                                try {
                                    docbase.delete(docs, this.contentHandler);
                                } catch (ProcessingException e) {
                                    throw new SAXException(e.getMessage(), e);
                                }
                            }
                        }
                    }
                }
            }
        } catch (ServiceException e) {
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_LOOKUP_FRAMEWORK_SERVICE, null, e);
        } finally {
            if (frame != null) manager.release(frame);
            this.deletions = new Hashtable();
        }

    }

    private void determineParsingPosition(String uri, String local, String qName, Attributes attr) {
        //resetting our values
        boolean withinSdxElement = this.withinSdxInternalField = 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)) {
                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;
                }
            }
        }
    }

    private synchronized void queueDocumentForDeletion() throws SDXException, SAXException {
        //if we have all the parameters we need we try to delete the document
        if (Utilities.checkString(appId) && Utilities.checkString(dbId) &&
                Utilities.checkString(docId) && Utilities.checkString(doctype)) {
            ParsableDocument doc = null;
            //building the correct type of document
            if (doctype.equalsIgnoreCase(Document.DOCTYPE_HTML))
                doc = new HTMLDocument(docId);
            else if (doctype.equalsIgnoreCase(Document.DOCTYPE_XML))
                doc = new XMLDocument(docId);
            //TODO:maybe in the future we need to add more checks for building other types of parsable documents
            //adding the document to the deletion queue
            if (this.deletions == null) this.deletions = new Hashtable();
            if (!this.deletions.containsKey(this.dbId))
                this.deletions.put(this.dbId, new ArrayList());

            ArrayList dbDocs = (ArrayList) this.deletions.get(this.dbId);
            if (dbDocs != null)
                dbDocs.add(doc);
            resetDocDeletionFields();
        }

    }

    // FIXME : private method never used locally. [MP]
    private void sendInfoElement() throws SAXException {
        // SAX element creation-rbp12/03/02

        //Creation of local variables which are later passed into startElement() and endElement() methods
        String sdxNsUri = Framework.SDXNamespaceURI;
        String sdxNsPrefix = Framework.SDXNamespacePrefix;

        String localName = Node.Name.DELETION;
        String qName = sdxNsPrefix + ":" + localName;
        AttributesImpl atts = new AttributesImpl();
        // atts.addAttribute("", ConfigurationNode.Name.NO, ConfigurationNode.Name.NO, ConfigurationNode.Type.CDATA, this.no);
        atts.addAttribute("", Node.Name.ID, Node.Name.ID, Node.Type.CDATA, docId);
        atts.addAttribute("", Node.Name.DB_ID, Node.Name.DB_ID, Node.Type.CDATA, dbId);
        atts.addAttribute("", Node.Name.APPID, Node.Name.APPID, Node.Type.CDATA, appId);
        atts.addAttribute("", Node.Name.DOCTYPE, Node.Name.DOCTYPE, Node.Type.CDATA, doctype);

        //startElement() method is called for "deletion" and local variables are passed
        super.contentHandler.startElement(sdxNsUri, localName, qName, atts);
        //endElement() method is called for "deletion" and local variables are passed
        super.contentHandler.endElement(sdxNsUri, sdxNsPrefix, qName);
    }

    /**Resets our booleans to represent a new state*/
    private void resetDocDeletionFields() {
        currentField = "";
        docId = "";
        dbId = "";
        appId = "";
        doctype = "";
    }

    /**verifies if the current appId from parsing is the same as our deleteAppId
     *
     * throws   A SDXException if the deleteAppId is useless for comparison
     */
    private boolean isCurrentAppDeleteApp() throws SDXException {

        if (!Utilities.checkString(deleteAppId))
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_DELETE_APP_ID, null, null);

        if (deleteAppId.equalsIgnoreCase(appId))
            return true;
        else
            return false;
    }

    /* (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;
		}
	}
    
}
