/*
 * 
 * Copyright 2004 Eric van der Vlist, Dyomedea

    This file is part of XMLfr.

    XMLfr is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    XMLfr is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with XMLfr; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

	Contributors:
		* Eric van der Vlist  (vdv@dyomedea.com)

 */
package org.xmlfr.orbeon;

import java.io.IOException;
import java.util.Map;

import org.apache.lucene.analysis.snowball.SnowballAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.orbeon.oxf.pipeline.api.ExternalContext;
import org.orbeon.oxf.pipeline.api.PipelineContext;
import org.orbeon.oxf.processor.ProcessorInputOutputInfo;
import org.orbeon.oxf.processor.SimpleProcessor;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.xmlfr.lucene.Indexer;

/**
 * @author vdv
 */
public class LuceneProcessor extends SimpleProcessor implements ContentHandler {

	public LuceneProcessor() {
		addInputInfo(new ProcessorInputOutputInfo(INPUT_DATA));
		addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_DATA));
	}

	static String RDF_NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
	static String RSS_NAMESPACE = "http://purl.org/rss/1.0/";
	static String DC_NAMESPACE = "http://purl.org/dc/elements/1.1/";
	static String SR_NAMESPACE = "http://ns.xmlfr.org/searchResults";

	static String RDF_ELEMENT = "RDF";

	static String CHANNEL_ELEMENT = "channel";
	static String LINK_ELEMENT = "link";

	String query;
	String boost;
	String rep;
	String sort;
	int start;
	int perPage;
	Hits hits;
	ExternalContext.Request request;
	PipelineContext context;
	ContentHandler contentHandler;
	Map parameters;

	static Attributes noAttributes = new AttributesImpl();
	
	String getParameter(String name, String defaultValue) {
		Object [] o= (Object []) parameters.get(name);
		if (o == null ) {
			return defaultValue;
		}
		return o[0].toString();
	}

	public void generateData(
		PipelineContext context,
		ContentHandler contentHandler)
		throws SAXException {
		this.context = context;
		this.contentHandler = contentHandler;
		ExternalContext externalContext =
			(ExternalContext) context.getAttribute(
				PipelineContext.EXTERNAL_CONTEXT);
		if (externalContext == null)
			throw new SAXException("Missing external context");
		request = externalContext.getRequest();
		parameters = request.getParameterMap();
		query = getParameter("query", "");
		boost = getParameter("boost", "yes");
		if (boost.equals("yes")) {
			rep = "/var/lib/xmlfr/lucene/boost";
		} else {
			rep = "/var/lib/xmlfr/lucene/noboost";
		}
		sort = getParameter("sort", "none");
		try {
			start = Integer.parseInt(getParameter("start", "0"));
		} catch (java.lang.NumberFormatException e) {
			start = 0;
		}
		try {
			perPage = Integer.parseInt(getParameter("perPage", "50"));
			if (perPage > 50 || perPage < 1) {
				perPage = 50;
			}
		} catch (java.lang.NumberFormatException e) {
			perPage = 50;
		}
		IndexSearcher is;
		if (!query.equals("")) {
			Directory fsDir;
			try {
				fsDir = FSDirectory.getDirectory(rep, false);
				is = new IndexSearcher(fsDir);
			} catch (IOException e3) {
				throw new SAXException(e3);
			}
			Query lquery;
			try {
				lquery =
					QueryParser.parse(
						query,
						"texte",
						new SnowballAnalyzer("French", Indexer.stopWords));
			} catch (ParseException e1) {
				throw new SAXException(e1);
			}
			try {
				if (sort.equals("date")) {
					Sort s = new Sort("date", true);
					hits = is.search(lquery, s);
				} else {
					hits = is.search(lquery);
				}
			} catch (IOException e2) {
				throw new SAXException(e2);
			}

			if (start >= hits.length()) {
				start = hits.length() - perPage;
			}
			if (start < 0) {
				start = 0;
			}
		}
		readInputAsSAX(context, INPUT_DATA, (ContentHandler) this);
	}

	/* (non-Javadoc)
	 * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
	 */
	public void endElement(String uri, String loc, String raw)
		throws SAXException {
		if (contentHandler != null) {
			if (uri.equals(RSS_NAMESPACE) && loc.equals(CHANNEL_ELEMENT)) {
				generateItemSeq();
				generateParameters();
			} else if (uri.equals(RDF_NAMESPACE) && loc.equals(RDF_ELEMENT)) {
				generateItems();
			} else if (uri.equals(RSS_NAMESPACE) && loc.equals(LINK_ELEMENT)) {
				String suffix = "?" + request.getQueryString();
				contentHandler.characters(
					suffix.toCharArray(),
					0,
					suffix.length());
			}
			contentHandler.endElement(uri, loc, raw);
		}
	}

	/**
	 * 
	 */
	private void generateParameters() throws SAXException {
		sendSimpleTypeElement(SR_NAMESPACE, "query", "sr:query", query);
		sendSimpleTypeElement(SR_NAMESPACE, "boost", "sr:boost", boost);
		sendSimpleTypeElement(SR_NAMESPACE, "sort", "sr:sort", sort);
		sendSimpleTypeElement(SR_NAMESPACE, "start", "sr:start", start);
		sendSimpleTypeElement(SR_NAMESPACE, "perPage", "sr:perPage", perPage);
		if (!query.equals("")) {
			sendSimpleTypeElement(
				SR_NAMESPACE,
				"hits",
				"sr:hits",
				hits.length());
		}
	}

	private void sendSimpleTypeElement(
		String namespaceUri,
		String localName,
		String qName,
		int value)
		throws SAXException {
		Integer i = new Integer(value);
		sendSimpleTypeElement(namespaceUri, localName, qName, i.toString());
	}

	/**
	 * 
	 */
	private void generateItemSeq() throws SAXException {
		contentHandler.startElement(
			RSS_NAMESPACE,
			"items",
			"items",
			noAttributes);
		contentHandler.startElement(
			RDF_NAMESPACE,
			"Seq",
			"rdf:Seq",
			noAttributes);
		if (!query.equals(""))
			for (int i = start;
				i < hits.length() && i < start + perPage;
				i++) {
				Document doc;
				try {
					doc = hits.doc(i);
				} catch (IOException e) {
					throw new SAXException(e);
				}
				AttributesImpl atts = new AttributesImpl();
				atts.addAttribute(
					RDF_NAMESPACE,
					"resource",
					"rdf:resource",
					"",
					doc.get("uri"));
				contentHandler.startElement(
					RDF_NAMESPACE,
					"li",
					"rdf:li",
					atts);
				contentHandler.endElement(RDF_NAMESPACE, "li", "rdf:li");
			}
		contentHandler.endElement(RDF_NAMESPACE, "Seq", "rdf:Seq");
		contentHandler.endElement(RSS_NAMESPACE, "items", "items");

	}

	private void generateItems() throws SAXException {
		if (!query.equals(""))
			for (int i = start;
				i < hits.length() && i < start + perPage;
				i++) {
				Document doc;
				try {
					doc = hits.doc(i);
				} catch (IOException e) {
					throw new SAXException(e);
				}
				AttributesImpl atts = new AttributesImpl();
				atts.addAttribute(
					RDF_NAMESPACE,
					"about",
					"rdf:about",
					"",
					doc.get("uri"));
				contentHandler.startElement(
					RSS_NAMESPACE,
					"item",
					"item",
					atts);
				sendSimpleTypeElement(
					RSS_NAMESPACE,
					"link",
					"link",
					doc.get("uri"));
				sendSimpleTypeElement(
					RSS_NAMESPACE,
					"title",
					"title",
					doc.get("titre"));
				sendSimpleTypeElement(
					RSS_NAMESPACE,
					"description",
					"description",
					doc.get("description"));
				sendSimpleTypeElement(
					DC_NAMESPACE,
					"creator",
					"dc:creator",
					doc.get("auteur"));
				sendSimpleTypeElement(
					DC_NAMESPACE,
					"date",
					"dc:date",
					doc.get("date"));
				sendSimpleTypeElement(
					DC_NAMESPACE,
					"type",
					"dc:type",
					doc.get("type"));
				Float r;
				try {
					r = new Float(hits.score(i));
				} catch (IOException e1) {
					throw new SAXException(e1);
				}
				sendSimpleTypeElement(
					SR_NAMESPACE,
					"ranking",
					"sr:ranking",
					r.toString());
				contentHandler.endElement(RSS_NAMESPACE, "item", "item");
			}

	}

	private void sendSimpleTypeElement(
		String uri,
		String name,
		String qname,
		String value)
		throws SAXException {
		if (value != null) {
			contentHandler.startElement(uri, name, qname, noAttributes);
			contentHandler.characters(value.toCharArray(), 0, value.length());
			contentHandler.endElement(uri, name, qname);
		}

	}

	/* (non-Javadoc)
	 * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
	 */
	public void startElement(String uri, String loc, String raw, Attributes a)
		throws SAXException {
		if (contentHandler != null) {
			if (uri.equals(RSS_NAMESPACE) && loc.equals(CHANNEL_ELEMENT)) {
				String about =
					a.getValue(RDF_NAMESPACE, "about")
						+ "?"
						+ request.getQueryString();
				a = new AttributesImpl();
				((AttributesImpl) a).addAttribute(
					RDF_NAMESPACE,
					"about",
					"rdf:about",
					"",
					about);
			}
			contentHandler.startElement(uri, loc, raw, a);
		}
	}

	/* (non-Javadoc)
	 * @see org.xml.sax.ContentHandler#endDocument()
	 */
	public void endDocument() throws SAXException {
		contentHandler.endDocument();
		
	}

	/* (non-Javadoc)
	 * @see org.xml.sax.ContentHandler#startDocument()
	 */
	public void startDocument() throws SAXException {
		contentHandler.startDocument();
		
	}

	/* (non-Javadoc)
	 * @see org.xml.sax.ContentHandler#characters(char[], int, int)
	 */
	public void characters(char[] ch, int start, int length) throws SAXException {
		contentHandler.characters(ch, start, length);
		
	}

	/* (non-Javadoc)
	 * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
	 */
	public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
		contentHandler.ignorableWhitespace(ch, start, length);
		
	}

	/* (non-Javadoc)
	 * @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String)
	 */
	public void endPrefixMapping(String prefix) throws SAXException {
		contentHandler.endPrefixMapping(prefix);
		
	}

	/* (non-Javadoc)
	 * @see org.xml.sax.ContentHandler#skippedEntity(java.lang.String)
	 */
	public void skippedEntity(String name) throws SAXException {
		contentHandler.skippedEntity(name);
		
	}

	/* (non-Javadoc)
	 * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator)
	 */
	public void setDocumentLocator(Locator locator) {
		contentHandler.setDocumentLocator(locator);
		
	}

	/* (non-Javadoc)
	 * @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String, java.lang.String)
	 */
	public void processingInstruction(String target, String data) throws SAXException {
		contentHandler.processingInstruction(target, data);
	}

	/* (non-Javadoc)
	 * @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String, java.lang.String)
	 */
	public void startPrefixMapping(String prefix, String uri) throws SAXException {
		contentHandler.startPrefixMapping(prefix, uri);	
	}
}
