package org.biomoby.shared.data;

import org.biomoby.registry.meta.Registry;
import org.biomoby.shared.parser.MobyTags;
import org.biomoby.shared.*;
import org.xml.sax.InputSource;
import org.w3c.dom.*;
import java.io.*;

/**
 * This class contains utilities that could be used on many of the data package classes, 
 * such as serialization and deserialization.
 */
public class MobyDataUtils{

    /**
     * Writes the XML version of the contents, with an XML declaration.
     */
    public static boolean toXMLDocument(OutputStream os, MobyContentInstance mci) throws Exception{
	return toXMLDocument(os, mci, true);
    }

    /**
     * Output the XML representation of the data to an OutputStream 
     * (i.e. an output that takes byte data, not character-encoded strings).
     * Note that the character decoding will depend on your runtime environment.
     */
    public static boolean toXMLDocument(OutputStream os, MobyContentInstance mci, boolean includeXMLDeclaration) 
	throws Exception{
	// May want to check character encoding before doing getBytes(), implement this later
	os.write("<?xml version=\"1.0\"?>\n".getBytes());
	os.write(("<moby:MOBY xmlns:moby=\"" + MobyPrefixResolver.MOBY_XML_NAMESPACE + "\" " + 
		             "xmlns=\"" + MobyPrefixResolver.MOBY_XML_NAMESPACE + "\" >\n").getBytes());
	os.write(mci.toXML().getBytes());  
	os.write("\n</moby:MOBY>\n".getBytes());
	return true;
    }

    /**
     * Writes the XML version of the contents, with an XML declaration.
     */
    public static boolean toXMLDocument(Writer writer, MobyContentInstance mci) throws Exception{
	return toXMLDocument(writer, mci, true);
    }

    /**
     * Output the XML representation of the data to a Writer (i.e. an output that understands 
     * character-encoded strings).
     */
    public static boolean toXMLDocument(Writer writer, MobyContentInstance mci, boolean includeXMLDeclaration) 
	throws Exception{
	
	writer.write("<?xml version=\"1.0\"?>\n");
	writer.write(("<moby:MOBY xmlns:moby=\"" + MobyPrefixResolver.MOBY_XML_NAMESPACE + "\" " +   
		                 "xmlns=\"" + MobyPrefixResolver.MOBY_XML_NAMESPACE + "\">\n"));
	writer.write(mci.toXML());  
	writer.write("\n</moby:MOBY>\n");
	return true;
    }

    /**
     * Create a MOBY Java object representation from a MOBY XML payload stored in a String.
     */
    public static MobyContentInstance fromXMLDocument(String xmlData) throws Exception{
	return fromXMLDocument(new StringReader(xmlData), null);
    }

    public static MobyContentInstance fromXMLDocument(String xmlData, Registry registry) throws Exception{
	return fromXMLDocument(new StringReader(xmlData), registry);
    }

    /**
     * Create a MOBY Java object representation from a MOBY XML payload coming from an InputStream 
     * (a byte stream that will becoverted by the parser into character data).
     */
    public static MobyContentInstance fromXMLDocument(InputStream is) throws Exception{
	return fromXMLDocument(is, null);
    }

    public static MobyContentInstance fromXMLDocument(InputStream is, Registry registry) throws Exception{
	// Load an XML document
	javax.xml.parsers.DocumentBuilder docBuilder = null;
	try{
	    javax.xml.parsers.DocumentBuilderFactory dbf = 
		javax.xml.parsers.DocumentBuilderFactory.newInstance();
	    dbf.setNamespaceAware(true);	
	    docBuilder = dbf.newDocumentBuilder();
	}
	catch(javax.xml.parsers.ParserConfigurationException pce){
	    throw new Exception("An XML parser could not be found or configured: " + pce);
	}

	Document domDoc = null;
	try{
	    domDoc = docBuilder.parse(is);
	} catch(org.xml.sax.SAXException saxe){
	    throw new Exception("The XML data could not be parsed (not well-formed): " + saxe);
	} catch(java.io.IOException ioe){
	    throw new Exception("The XML data could not be loaded (I/O problem): " + ioe);
	}

	// Select a node from the document and see if any of the mappings
	// work out, in which case we can check what services we can run
	Element doc_root = domDoc.getDocumentElement();
	return fromXMLDocument(doc_root, registry);
    }

    /**
     * Create a MOBY Java object representation from a MOBY XML payload at the given URL.
     */
    public static MobyContentInstance fromXMLDocument(java.net.URL url) throws Exception{
	return fromXMLDocument(url, null);
    }

    public static MobyContentInstance fromXMLDocument(java.net.URL url, Registry registry) throws Exception{
	// Load an XML document
	javax.xml.parsers.DocumentBuilder docBuilder = null;
	try{
	    javax.xml.parsers.DocumentBuilderFactory dbf = 
		javax.xml.parsers.DocumentBuilderFactory.newInstance();
	    dbf.setNamespaceAware(true);	
	    docBuilder = dbf.newDocumentBuilder();
	}
	catch(javax.xml.parsers.ParserConfigurationException pce){
	    throw new Exception("An XML parser could not be found or configured: " + pce);
	}

	Document domDoc = null;
	try{
	    domDoc = docBuilder.parse(url.toURI().toString());
	} catch(org.xml.sax.SAXException saxe){
	    throw new Exception("The XML data could not be parsed (not well-formed): " + saxe);
	} catch(java.io.IOException ioe){
	    throw new Exception("The XML data could not be loaded (I/O problem): " + ioe);
	}

	// Select a node from the document and see if any of the mappings
	// work out, in which case we can check what services we can run
	Element doc_root = domDoc.getDocumentElement();
	return fromXMLDocument(doc_root, registry);
    }

    /**
     * Create a MOBY Java object representation from a MOBY XML payload coming from a Reader (character data).
     */
    public static MobyContentInstance fromXMLDocument(Reader reader) throws Exception{
	return fromXMLDocument(reader, null);
    }

    public static MobyContentInstance fromXMLDocument(Reader reader, Registry registry) throws Exception{
	// Load an XML document
	javax.xml.parsers.DocumentBuilder docBuilder = null;
	try{
	    javax.xml.parsers.DocumentBuilderFactory dbf = 
		javax.xml.parsers.DocumentBuilderFactory.newInstance();
	    dbf.setNamespaceAware(true);	
	    docBuilder = dbf.newDocumentBuilder();
	}
	catch(javax.xml.parsers.ParserConfigurationException pce){
	    throw new Exception("An XML parser could not be found or configured: " + pce);
	}

	Document domDoc = null;
	try{
	    domDoc = docBuilder.parse(new InputSource(reader));
	} catch(org.xml.sax.SAXException saxe){
	    throw new Exception("The XML data could not be parsed (not well-formed): " + saxe);
	} catch(java.io.IOException ioe){
	    throw new Exception("The XML data could not be loaded (I/O problem): " + ioe);
	}

	// Select a node from the document and see if any of the mappings
	// work out, in which case we can check what services we can run
	Element doc_root = domDoc.getDocumentElement();
	return fromXMLDocument(doc_root, registry);
    }


    /**
     * Create a MOBY Java object representation from a MOBY XML payload represented in a DOM.
     *
     * @param doc_root the document's base MOBY tag
     */
    public static MobyContentInstance fromXMLDocument(Element doc_root) throws Exception{
	return fromXMLDocument(doc_root, null);
    }

    /**
     * Create a MOBY Java object representation from a MOBY XML payload represented in a DOM.
     *
     * @param doc_root the document's base MOBY tag
     * @param registry the Mony Central from which to retrieve the data type definitions, uses default registry if null 
     */
    public static MobyContentInstance fromXMLDocument(Element doc_root, Registry registry) throws Exception{
	if(doc_root == null){
	    throw new MobyException("The passed in element was null");
	}
	if(!MobyTags.MOBY.equals(doc_root.getLocalName())){
	    throw new MobyException("The XML document's root element (local " + 
				    doc_root.getLocalName() +
				    ", qualified " + doc_root.getNodeName() + 
				    " ) was not " + MobyTags.MOBY);
	}
	NodeList envelope = null;
	if(!MobyPrefixResolver.MOBY_XML_NAMESPACE.equals(doc_root.getNamespaceURI())){
	   if(!MobyPrefixResolver.MOBY_XML_NAMESPACE_INVALID.equals(doc_root.getNamespaceURI())){
	       throw new MobyException("The XML document's root element namespace (" + 
				       doc_root.getNamespaceURI() +
				       ") is not the MOBY namespace " + 
				       MobyPrefixResolver.MOBY_XML_NAMESPACE);
	   }
	   System.err.println("Invalid namespace used for root element (was " + doc_root.getNamespaceURI() + 
			      ", but should be " + MobyPrefixResolver.MOBY_XML_NAMESPACE + 
			      ", proceeding anyway");
	   envelope = doc_root.getElementsByTagNameNS(MobyPrefixResolver.MOBY_XML_NAMESPACE_INVALID, 
						      MobyTags.MOBYCONTENT);
	}
	else{
	    envelope = doc_root.getElementsByTagNameNS(MobyPrefixResolver.MOBY_XML_NAMESPACE, 
						       MobyTags.MOBYCONTENT);
	}

	if(envelope.getLength() == 0){
	    throw new MobyException("The document's " + MobyTags.MOBY + 
				    " element does not contain a " +
				    MobyTags.MOBYCONTENT + " tag in the namspace " + 
				    MobyPrefixResolver.MOBY_XML_NAMESPACE);
	}
	if(envelope.getLength() > 1){
	    throw new MobyException("The document's " + MobyTags.MOBY + 
				    " element contains more than one (" + 
				    envelope.getLength() + ") " +
				    MobyTags.MOBYCONTENT + " tag in the namspace " + 
				    MobyPrefixResolver.MOBY_XML_NAMESPACE);
	}

	MobyContentInstance contents = new MobyContentInstance((Element) envelope.item(0), registry);

	return contents;
    }
}
