package ca.ucalgary.seahawk.util;

import org.biomoby.registry.meta.Registry;
import org.biomoby.shared.*;
import org.biomoby.shared.data.*;
import org.biomoby.shared.parser.MobyTags;

import javax.xml.parsers.*;

import java.io.*;
import java.net.*;
import java.util.Vector;

public class HTMLUtils{

    public final static String BINARY_DATA_LABEL = "[binary data not shown]";
    public final static String ACCEPTABLE_IMAGE_DATATYPE = "WebFormatImage"; //from the ontology, should eventually be configurable

    public static String encapsulateBinaryData(URL sourceMobyData, String html) throws Exception{
	return encapsulateBinaryData(sourceMobyData, html, null);
    }

    public static String encapsulateBinaryData(URL sourceMobyData, String html, Registry registry) throws Exception{
	MobyContentInstance dataPackage = MobyDataUtils.fromXMLDocument(sourceMobyData, registry);
	// Look for binary data and replace it with an image ref (if its an image
	// displayable in our viewer), or simply "(binary data)".
	for(MobyDataJob job: dataPackage.values()){
	    for(MobyDataInstance primary: job.values()){
		Iterable<MobyDataObject> values = null;
		if(primary instanceof MobyDataObjectSet ||
		   primary instanceof MobyDataObjectVector){
		    values = (Iterable<MobyDataObject>) primary;
		}
		else if(primary instanceof MobyDataObject){
		    values = new Vector<MobyDataObject>();
		    ((Vector<MobyDataObject>) values).add((MobyDataObject) primary);
		}
		else{
		    // ignore secondaries, they can't be binary
		    continue;
		}
		for(MobyDataObject data: values){
		    if(data instanceof MobyDataBytes){
			MobyDataBytes binaryMobyObject = (MobyDataBytes) data;
			//System.err.println("Replacing binary data in display of "+binaryMobyObject.getName());
			
			String encodedData = binaryMobyObject.get(MobyDataBytes.ENCODED_MEMBER_NAME)
			    .getValue().toString();
			if(html.indexOf(encodedData) == -1){
			    System.err.println("Could not find the encoded data in the html given!!!"); 			
			}
			
			// if an image, save and show it
			if(binaryMobyObject.getDataType().inheritsFrom(ACCEPTABLE_IMAGE_DATATYPE)){
			    String imgSuffix = binaryMobyObject.getDataType().getName();
			    if(imgSuffix.length() < 3){
				imgSuffix = ".img";
			    }
			    else{
				imgSuffix = "."+imgSuffix.substring(0, 3).toLowerCase();
			    }
			    File imageFile = File.createTempFile("seahawkSavedImage", imgSuffix);
			    imageFile.deleteOnExit();
			    
			    FileOutputStream fos = new FileOutputStream(imageFile);
			    fos.write(binaryMobyObject.getBytes());
			    fos.close();
			    
			    html = html.replace(encodedData, "<br><img src=\""+imageFile.toURI().toURL()+"\">");
			}
			// Otherwise put a placeholder
			else{
			    html = html.replace(encodedData, BINARY_DATA_LABEL);
			}
		    }
		}
	    }
	}

	return html;
    }

    /**
     * Extracts a URL string from a given piece of text iof that's all it contains.
     * Especially suited for dropped URL's etc. from browsers, as it cleans up the HTML junk
     * browsers wrap links in.
     */
    public static String checkForHyperlinkText(String text){
	if(text.indexOf("<html><a href=\"") == 0 && text.indexOf("</a></html>") == text.length()-11){
	    return text.substring(15, text.indexOf("\"",15));
	}
	else if(text.indexOf("<a href=\"") == 0 && text.indexOf("</a>") == text.length()-4){ //unix
	    return text.substring(9, text.indexOf("\"",9));
	}
	else if(text.indexOf("<!--StartFragment--><a href=\"") != -1 && // windows
		text.indexOf("</a><!--EndFragment-->") != -1){
	    int startHref = text.indexOf("<!--StartFragment--><a href=\"");
	    return text.substring(startHref+29, text.indexOf("\"",startHref+29));
	}
	return null;
    }

    /**
     * Turns a file into a URL if the file was URL shortcut (Windows & Gnome).
     */
    public static URL checkForURLShortcut(File file) throws Exception{
	URL u = null;
	String fileString = file.toURI().toURL().toString().toLowerCase(); 
	if(fileString.lastIndexOf(".url") == fileString.length()-4){
	    LineNumberReader in = new LineNumberReader(
						       new InputStreamReader(file.toURI().toURL().openStream()));
	    for(String line = in.readLine(); line != null; line = in.readLine()){
		int urlIndex = line.indexOf("URL=");
		if(line.indexOf("URL=") == 0){
		    try{
			u = new URL(line.substring(4));
		    } catch(MalformedURLException murle){
			System.err.println("Could not format " +line);
			murle.printStackTrace();
			// Not a URL, but really it should have been.
			// oh well, move on to normal file loading...
		    }
		    break;  // done with this file as a URL
		}
	    }
	}
	return u;
    }

    /**
     * By turning the tool tip text into HTML, we can make it multiline!
     */
    public static String htmlifyToolTipText(String text, int maxLineLength){
	if(text == null){
	}

	if(text.length() < maxLineLength || maxLineLength < 2){
	    return "<html>"+text+"</html>";
	}

	StringBuffer result = new StringBuffer("<html>");
	int lineCharCount = 0;
	String[] tokens = text.split("[ \t\n]");
	for(String word: tokens){
	    while(word.length()+1 > maxLineLength){  //single word is bigger than preset width, hyphenate
		int cutPoint = maxLineLength - lineCharCount - 1;
		if(cutPoint < word.length()-1){
		    cutPoint = word.length()-1;
		}
		if(cutPoint < 0){
		    break;
		}
		result.append(word.substring(0, cutPoint)+"-<br>");
		lineCharCount = 0;
		word = word.substring(cutPoint);
	    }
	    if(lineCharCount != 0 && lineCharCount + word.length() > maxLineLength){
		result.append("<br>" + word + " ");
		lineCharCount = word.length()+1;
		continue;
	    }
	    result.append(word+" ");
	    lineCharCount += word.length()+1;
	}
	result.append("</html>");

	return result.toString();
    }

    /**
     * Given an HTML fragment (e.g. cut'n'pasted from a browser), see if we unescape the
     * HTML we get a MOBY XML block (e.g. from the biomoby.org web site examples).
     */
    public static MobyContentInstance checkForMobyXML(String text){
	if(//(text.indexOf("moby:") != -1 || text.indexOf("articleName=\"") != -1) &&
	   text.indexOf("namespace=\"") == -1 ||
	   text.indexOf("id=\"") == -1){
	    return null;
	}

	MobyContentInstance content = null;
	String xmltext = text;
	if(xmltext.indexOf("??") == 0){
	    xmltext = xmltext.substring(2);
	}

	// See if the xml needs unescaping (e.g. if preformatted HTML dropped from Firefox or Thunderbird)
	int preStart = xmltext.indexOf("<pre");
	if(preStart == 0 || preStart == 2 || //unix
	   xmltext.indexOf("--StartFragment--") != -1){ //windows
	    xmltext = xmltext.substring(preStart, xmltext.indexOf("</pre")); 
	    xmltext = xmltext.substring(xmltext.indexOf(">")+1);			
	    xmltext = xmltext.replaceAll("&gt;", ">");
	    xmltext = xmltext.replaceAll("&lt;", "<");
	    xmltext = xmltext.replaceAll("&amp;", "&");
	}
	xmltext = xmltext.trim();  // remove leading and trailing spaces
	try{
	    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
	    dbf.setNamespaceAware (true);
	    DocumentBuilder db = dbf.newDocumentBuilder();
	    if(xmltext.indexOf("xmlns:moby") == -1){
		// Ensure that the moby: prefix is declared in the first tag
		xmltext = xmltext.replaceFirst("<([A-Za-z0-9_][^> \t\n]*)", "<$1 xmlns:moby=\""+
					       MobyPrefixResolver.MOBY_XML_NAMESPACE+"\"");
	    }
	    if(xmltext.indexOf("<?xml") != 0){
		xmltext = "<?xml version=\"1.0\"?>\n" + xmltext;
	    }
		
	    org.w3c.dom.Element data = db.parse(new ByteArrayInputStream(xmltext.getBytes())).getDocumentElement();
	    if(data.getLocalName().equals(MobyTags.MOBY)){
		content = MobyDataUtils.fromXMLDocument(data, SeahawkOptions.getRegistry());
	    }
	    else if(data.getLocalName().equals(MobyTags.MOBYCONTENT)){
		content = new MobyContentInstance(data, SeahawkOptions.getRegistry());
	    }
	    else if(data.getLocalName().equals(MobyTags.MOBYDATA)){
		content = new MobyContentInstance();
		content.parseDataGroup(data, SeahawkOptions.getRegistry());
	    }
	    else{
		// Any other data can be processed by the base object class 
		content = new MobyContentInstance(MobyDataObject.createInstanceFromDOM(data, SeahawkOptions.getRegistry()), 
						  "Drag 'n' Drop data");
	    }
	} catch(Exception e){
	    e.printStackTrace();
	    System.err.println("This issue may or may not be related to your choice of registry ("+
			       (SeahawkOptions.getRegistry() == null ? "MOBY default" : SeahawkOptions.getRegistry().getSynonym())
			       +")");
	    System.err.println(xmltext);
	}
	return content;
    }

    /**
     * Convenience method to grab all data from a URL into a String.
     */
    public static String getURLContents(URL u) throws IOException{
	StringBuffer buffer = new StringBuffer();
	LineNumberReader reader = new LineNumberReader(new InputStreamReader(u.openStream()));
	for(String line = reader.readLine(); line != null; line = reader.readLine()){
	    buffer.append(line+"\n");
	}
	return buffer.toString();
    }

    /**
     * Convenience method to grab all data from a URL as a byte array (in case it's not text)
     */
    public static byte[] getURLContentBytes(URL u) throws IOException{
	InputStream urlStream = u.openStream();

	byte[] byteBufferChunk = new byte[1024];
	ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
	for(int r = urlStream.read(byteBufferChunk, 0, 1024);
	    r != -1; 
	    r = urlStream.read(byteBufferChunk, 0, 1024)){
	    byteBuffer.write(byteBufferChunk, 0, r);
	}
	return byteBuffer.toByteArray();
    }

    /**
     * Convenience method to grab all data from a input stream into a String.
     */
    public static String getInputStreamContents(InputStream resourceStream) throws IOException{
	StringBuffer xml = new StringBuffer();
	    
	// Copy data verbatim 1 k at a time
	byte[] dataBuffer = new byte[1024];
	for(int numBytesRead = resourceStream.read(dataBuffer); 
	    numBytesRead != -1; 
	    numBytesRead = resourceStream.read(dataBuffer)){
	    xml.append(new String(dataBuffer, 0, numBytesRead));
	}

	return xml.toString();
    }

}
