package org.ecoinformatics.ecogrid.lsid;

import java.io.InputStream;
import java.io.ByteArrayInputStream;

import com.ibm.lsid.LSID;
import com.ibm.lsid.MetadataResponse;
import com.ibm.lsid.MalformedLSIDException;

import com.ibm.lsid.server.LSIDMetadataService;
import com.ibm.lsid.server.LSIDServerException;
import com.ibm.lsid.server.LSIDServiceConfig;

import com.ibm.lsid.server.LSIDRequestContext;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.InputStreamReader;
import java.io.BufferedReader;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import java.util.Hashtable;

import edu.ucsb.nceas.metacat.client.*;
import java.util.ResourceBundle;

public class LSIDAuthorityMetaData implements LSIDMetadataService {

	private LSIDDataLookup lookup = null;
	private static Hashtable currentLSIDs = new Hashtable();

	public void initService(LSIDServiceConfig cf) throws LSIDServerException {
        System.out.println("Starting LSIDAuthorityMetadata (Metacat).");
		lookup = new LSIDDataLookup();
	}

	private static final String RDF_NS =
		"http://www.w3.org/1999/02/22-rdf-syntax-ns#";
	private static final String DC_NS = "http://purl.org/dc/elements/1.1/";
	private static final String I3CP_NS = "urn:lsid:i3c.org:predicates:";
	private static final String I3C_CONTENT = "urn:lsid:i3c.org:types:content";

	public MetadataResponse getMetadata(
		LSIDRequestContext req,
		String[] formats)
		throws LSIDServerException {
		LSID lsid = req.getLsid();
		ByteArrayInputStream theMetadata = doMetadataRequest(lsid);
		return new MetadataResponse(
			theMetadata,
			null,
			MetadataResponse.RDF_FORMAT);
	}

	private ByteArrayInputStream doMetadataRequest(LSID lsid)
		throws LSIDServerException {
		System.out.println("getting metadata for lsid " + lsid.getLsid());
		try {

			String styleSheetName = null;
			styleSheetName = "metacat.xslt";
			LSIDDataLookup myLookup = new LSIDDataLookup();
			InputStream content = myLookup.lsidData(lsid);
			InputStream content2 = myLookup.lsidData(lsid);
			if (!isEML(content2)) {
				content = getEML(lsid);
			}
			content2.close();
			setCurrentLSID(lsid);
			InputStream styleSheet =
				getClass().getResourceAsStream(styleSheetName);
			TransformerFactory factory = TransformerFactory.newInstance();
			Transformer transformer =
				factory.newTransformer(new StreamSource(styleSheet));
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			transformer.transform(
				new StreamSource(content),
				new StreamResult(out));
			content.close();
			clearState();
			return new ByteArrayInputStream(out.toByteArray());
		} catch (Exception e) {
			throw new LSIDServerException(
				e,
				"Error transforming XML for: " + lsid);
		}
	}

	private String getStringFromInputStream(InputStream input) {
		StringBuffer result = new StringBuffer();
		BufferedReader in = new BufferedReader(new InputStreamReader(input));
		String line;
		try {
			while ((line = in.readLine()) != null) {
				result.append(line);
			}
		} catch (IOException e) {
			System.out.println("IOexception " + e);
		}

		return result.toString();

	}

	/**
	 * figure out is this inputstream is an eml document or not
		 */

	/* TODO: need a better way to figure out if this is an eml document */
	private boolean isEML(InputStream input) {

		if (input == null) {
			return false;
		}

		int loop = 0;
		boolean itIsEML = false;
		String line = "";
		try {
			BufferedReader in =
				new BufferedReader(new InputStreamReader(input));
			while ((loop < 20) && (line != null) && (!itIsEML)) {
				line = in.readLine();
				line = line.toLowerCase();
				if (line.indexOf("eml:eml") != -1) {
					itIsEML = true;
				}
				loop++;
			}
		} catch (IOException e) {
			System.out.println("ioerror in LSIDAuthorityMetadata: " + e);
		}
		return itIsEML;
	}

	/**
	 *  this lsid points to a data object - get the
	     *  metadata objects which refer to this document
	     */

	private InputStream getEML(LSID theLSID) {

		InputStream response = null;

		// need to find things with this object in any of the elements
		// using the metacat api
		// get back dataset/docid and dataset/title

		// create the query
		String theQuery = getMetaCatQuery(theLSID);

		// get the metacat record
		Reader metaCatResponse = getMetaCatResponse(theQuery);

		// parse the metadata to get the applicable rdf information
		response = parseMetaCatResponse(metaCatResponse, theLSID);

		return response;

	}

	/** 
	 * given an LSID return a metacat query which will return docs mentioning this LSID
	 */

	private String getMetaCatQuery(LSID lsid) {
        System.out.println("getting Metacat Query for: " + lsid.toString());
		String ns = lsid.getNamespace();
		String id = lsid.getObject();
		String ver = lsid.getRevision();
		String theName = ns + "." + id + "." + ver;

		String theQuery = null;
		theQuery =
			"<?xml version=\"1.0\"?>\n"
				+ "<pathquery version=\"1.2\">\n"
				+ "  <querytitle>"
				+ theName
				+ " search</querytitle>\n"
				+ "  <returnfield>dataset/docid</returnfield>\n"
				+ "  <returnfield>dataset/title</returnfield>\n"
				+ "  <querygroup operator=\"UNION\">\n"
				+ "    <queryterm searchmode=\"contains\" casesensitive=\"false\">\n"
				+ "      <value>"
				+ theName
				+ "</value>\n"
				+ "      <pathexpr>anyfield</pathexpr>\n"
				+ "    </queryterm>\n"
				+ "  </querygroup>\n"
				+ "<pathquery>\n";

		return theQuery;

	}

	/* 
	 * given a query string, query MetaCat and return the response
	 */
	private Reader getMetaCatResponse(String query) {
        System.out.println("Querying the metacat server.");
		//  get the metacat server from the configuration file
		//
		ResourceBundle rb = ResourceBundle.getBundle("metacat");
		String url = rb.getString("metacatserver");
		Reader r = null;
		try {

			Metacat m = MetacatFactory.createMetacatConnection(url);
			r = m.query(new StringReader(query));

		} catch (MetacatInaccessibleException mie) {
			System.out.println("Metacat Inaccessible:\n" + mie.getMessage());
		} catch (Exception e) {
			System.out.println("General exception:\n" + e.getMessage());
		}
		return r;
	}

	/**
	 * Given a reader which is a metacat response, parse it and return the appropriate rdf
	 */
	private InputStream parseMetaCatResponse(Reader reader, LSID theLSID) {
		InputStream response = null;
        System.out.println("Parsing the metacat response.");
		// if there's more than one document, then return rdf listing the documents
		// otherwise get the document and return rdf  based on it

		String contents = getStringFromReader(reader);
		if (numberDocuments(contents) < 1) {
			response = noMetaDataResponse(theLSID);
		} else if (numberDocuments(contents) > 1) {
			response = metaDataList(contents, theLSID);
		} else {
			response = getMetaData(contents, theLSID);
		}
		return response;
	}

	/**
	 *  There's no metadata for this document 
	*/

	private ByteArrayInputStream noMetaDataResponse(LSID theLSID) {
		ResourceBundle rb = ResourceBundle.getBundle("metacat");
		String metadataLabels = rb.getString("metadatalabels");

		String result =
			"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
				+ "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" \n"
				+ "	xmlns:dc=\"http://purl.org/dc/elements/1.1/\" \n"
				+ "	xmlns:pred=\"urn:lsid:i3c.org:predicates:\" xmlns=\"urn:lsid:" 
				+ metadataLabels + ":predicates:\"> \n"
				+ "<rdf:Description rdf:about=\""
				+ theLSID.getLsid()
				+ "\"> \n"
				+ "	<pred:title xmlns:pred=\"http://purl.org/dc/elements/1.1/\">There is no metadata for this LSID.</pred:title>\n"
				+ "</rdf:Description>\n"
				+ "</rdf:RDF>\n";
			

		return new ByteArrayInputStream(result.getBytes());
	}

	/**
	 *  There's more than one metdata document
	     */

	private ByteArrayInputStream metaDataList(String contents, LSID theLSID) {
		ResourceBundle rb = ResourceBundle.getBundle("metacat");
		String metadataLabels = rb.getString("metadatalabels");

		String result =
			"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
				+ "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" \n"
				+ "	xmlns:dc=\"http://purl.org/dc/elements/1.1/\" \n"
				+ "	xmlns:pred=\"urn:lsid:i3c.org:predicates:\" xmlns=\"urn:lsid:"
				+ metadataLabels + ":predicates:\"> \n"
				+ "<rdf:Description rdf:about=\""
				+ theLSID.getLsid()
				+ "\"> \n"
				+ "	<pred:title xmlns:pred=\"http://purl.org/dc/elements/1.1/\">There is more than one metadata document for this LSID - which confuses me right now.  Try again soon and I'll be less confused.</pred:title>\n"
				+ "</rdf:Description>\n"
				+ "</rdf:RDF>\n";

		return new ByteArrayInputStream(result.getBytes());
	}

	/**
	 *  There's just one metadata document 
	     */

	private ByteArrayInputStream getMetaData(String contents, LSID theLSID) {
		String paramString = "<param name=\"dataset/title\">";
		ByteArrayInputStream result = null;

		if (contents.indexOf(paramString) == -1) {
			return noMetaDataResponse(theLSID);
		} else {
			String parts[] = contents.split(paramString);
			String parts2[] = parts[1].split("</param>");
			try {
				LSID newLSID = new LSID(parts2[0]);
				result = doMetadataRequest(newLSID);

			} catch (MalformedLSIDException e) {
				System.out.println("problem generating LSID: " + e);
				e.printStackTrace();
			} catch (LSIDServerException e) {
				System.out.println("problem generating LSID: " + e);
				e.printStackTrace();
			}
		}
		return result;
	}

	/**
	 *  Find out how many contents are in this metacat response
	 *  I'm just using string stuff for this - sort of lame, but does the trick
	 *  more cool would be to use xml stuff
	 */
	private int numberDocuments(String contents) {

		String[] docSplit = contents.split("<document>");
		return (docSplit.length - 1);
	}

	/**
	 * Given a reader, return a string
	 */
	private String getStringFromReader(Reader reader) {
		StringBuffer response = new StringBuffer();

		try {
			BufferedReader bufReader = new BufferedReader(reader);

			String line = null;
			while ((line = bufReader.readLine()) != null) {
				response.append(line);
			}
			bufReader.close();

		} catch (IOException e) {
			System.out.println("error getting string from reader " + e);
		}
		return response.toString();
	}

	/**
	 * set the LSID for the current thread
	 */
	static void setCurrentLSID(LSID lsid) {
		currentLSIDs.put(Thread.currentThread(), lsid);
	}

	static void clearState() {
		currentLSIDs.remove(Thread.currentThread());
	}

	/**
	 * get the current LSID for the given thread, for use in XSLT so return a string
	 */
	//	public static String getLSID() throws MalformedLSIDException {
	//		return ((LSID)currentLSIDs.get(Thread.currentThread())).toString();
	//	}

	public static String getLSID(
		org.apache.xalan.extensions.XSLProcessorContext foo,
		org.apache.xalan.templates.ElemExtensionCall bar)
		throws MalformedLSIDException {
		return ((LSID) currentLSIDs.get(Thread.currentThread())).toString();
	}

	public static String getLSID() throws MalformedLSIDException {
		return ((LSID) currentLSIDs.get(Thread.currentThread())).toString();
	}
}
