package ca.ucalgary.seahawk.gui;

import ca.ucalgary.seahawk.util.HTMLUtils;
import org.biomoby.shared.MobyPrefixResolver;
import org.biomoby.shared.data.*;
import org.biomoby.shared.parser.MobyTags;

import org.w3c.dom.*;
import javax.xml.parsers.*;

import java.io.*;
import java.net.*;
import java.util.*;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;

/*
 * This class allows Seahawk to accept drop events from the native
 * windowing system, so dropped files, URLs and string can be loaded into 
 * tabs. Dragging from Seahawk tabs is supported by the SeahawkTransferable class.
 */
public class FileAndTextTransferHandler extends TransferHandler {
    private DataFlavor fileFlavor, stringFlavor;
    private MobyContentGUI gui;
    private boolean openDataInNewTab;

    /**
     * By default, opens pasted data in a new tab of the GUI
     */
    public FileAndTextTransferHandler(MobyContentGUI mcg) {
	this(mcg, true);
    }

    public FileAndTextTransferHandler(MobyContentGUI mcg, boolean openNewDataInNewTab) {
       gui = mcg;
       openDataInNewTab = openNewDataInNewTab;
       fileFlavor = DataFlavor.javaFileListFlavor;
       stringFlavor = DataFlavor.stringFlavor;
    }
   @SuppressWarnings("unchecked") // explicit java.util.List<java.io.File> for t.getTransferData(DataFlavor.javaFileListFlavor)
    public boolean importData(JComponent c, Transferable t) {
        if (!canImport(c, t.getTransferDataFlavors())) {
            System.err.println("Cannot drop data into Seahawk: " + t);
            return false;
        }

	// The clipboard is a special case: we want to append data,
	// not replace (also assuming you told us not to open data in a new tab).
	MobyContentClipboard clipboard = null;
	if(!openDataInNewTab){
	    for(Component container = c;
		container != null;
		container = container.getParent()){
		if(container instanceof MobyContentClipboard){
		    clipboard = (MobyContentClipboard) container;
		    break;
		}
	    }	    
	}

        // Should be done in another thread to not block UI
        try {
            if (hasFileFlavor(t.getTransferDataFlavors())) {
                // Load any dragged file as a URL
                for (File file: (java.util.List<File>) t.getTransferData(DataFlavor.javaFileListFlavor)) {
		    // Windows Web shortcut?
		    URL u = HTMLUtils.checkForURLShortcut(file);
		    // Was it a shortcut file after all?
		    if(u != null){
			if(Boolean.getBoolean("moby.debug")){
			    System.err.println("Dropped item appears to be a URL shortcut...");
			}
			if(clipboard != null){
			    clipboard.addCollectionData(u);
			}
			else{
			    gui.loadPaneFromURL(u, openDataInNewTab);
			}
		    }
		    // Any other type of file is loaded as-is
		    else{
			if(Boolean.getBoolean("moby.debug")){
			    System.err.println("Dropped item appears to be a file...");
			}
			if(clipboard != null){
			    clipboard.addCollectionData(file.toURI().toURL());
			}
			else{
			    gui.loadPaneFromURL(file.toURI().toURL(), openDataInNewTab);
			}
		    }
                }
                return true;
            } else if (hasTextFlavor(t.getTransferDataFlavors())) {
                // Make a string out of the text
		boolean NOT_JUST_PLAIN_TEXT = false;
		String text = convertToString(t, NOT_JUST_PLAIN_TEXT);

		// Is it an address bar icon dragged from firefox?  Strip the surrounding html
		String linktext = HTMLUtils.checkForHyperlinkText(text);

                // First see if it's a URL
                URL u = null;
                try{
                   u = new URL(linktext == null ? text : linktext);
                } catch(MalformedURLException murle){
                   // Not a URL, oh well, move on to string handling...
                }
                if(u != null){
		   if(Boolean.getBoolean("moby.debug")){
		       System.err.println("Dropped item appears to be a URL...");
		   }
		   if(clipboard != null){
		       clipboard.addCollectionData(u);
		   }
		   else{
		       gui.loadPaneFromURL(u, openDataInNewTab);
		   }
                   return true;
	        }

		// For the purposes of the clipboard, if it's not a URL, we want to parse
		// the text fopr MOBY data, rather than display the HTML or RTF, etc.
		if(clipboard != null){
		    boolean PLAIN_TEXT = true;
		    text = convertToString(t, PLAIN_TEXT);
		}

		// If the text looks like a MOBY XML payload, be nice and create
		// the proper object to load
		MobyContentInstance content = HTMLUtils.checkForMobyXML(text);
		if(content != null){
		    if(Boolean.getBoolean("moby.debug")){
			System.err.println("Dropped item appears to be moby xml...");
		    }
		    if(clipboard != null){
			clipboard.addCollectionData(content);
		    }
		    else{
			gui.loadPaneFromObject(content, openDataInNewTab);
		    }
		    return true;
		}

                // Otherwise save the data to a temp file and load it as a URL
		String suffix = "";
		if(text.indexOf("{\\rtf") == 0){
		    suffix = ".rtf";
		}
		else if(text.indexOf("<") != -1 && text.indexOf(">") > text.indexOf("<") && 
			text.indexOf("/") != -1){
		    suffix = ".html";
		}
                File savedFile = File.createTempFile("seahawkDataDrop", suffix);
	        savedFile.deleteOnExit();
	        FileOutputStream out = new FileOutputStream(savedFile);
		if(suffix.equals(".html")){
		    // Sometimes you get ?? at the start of pasted html, we'll clean it up
		    if(text.indexOf("??") == 0){
			text = text.substring(2);
		    }
		    if(text.indexOf("<html") == -1){
			// Add html tag around the contents if it's hypertext, so it's a full document and
			// JEditorPane doesn't do funny stuff.
			out.write("<html>".getBytes());
			out.write(text.getBytes());
			out.write("</html>".getBytes());
		    }
		}
		else{
		    // Treat as plain text if there are no hyperlinks
		    out.write(text.getBytes());
		}
                out.close();
                if(clipboard != null){
		    clipboard.addCollectionData(savedFile.toURI().toURL());
		}
		else{
		    gui.loadPaneFromURL(savedFile.toURI().toURL(), openDataInNewTab);
		}
                return true;
            }
	    else{
		System.err.println("Cannot drop data into Seahawk, data was neither a file, nor text");
	    }
        } catch (UnsupportedFlavorException ufe) {
            System.out.println("importData: unsupported data flavor:"+ufe);
        } catch (IOException ioe) {
	    ioe.printStackTrace();
            System.out.println("importData: I/O exception: "+ioe);
        } catch (Exception e){
	    e.printStackTrace();
	    System.out.println("importData: General exception: "+e);
	}
        return false;
    }

    public String convertToString(Transferable t, boolean get_plain_text) throws Exception{
	System.err.println("Converting transferable " + t + "  to text");
	StringBuffer textBuffer = new StringBuffer();
	Reader reader = get_plain_text ? DataFlavor.getTextPlainUnicodeFlavor().getReaderForText(t) :
	    DataFlavor.selectBestTextFlavor(t.getTransferDataFlavors()).getReaderForText(t);
	char[] buffer = new char[4096];
	for(int charsRead = reader.read(buffer); charsRead != -1; charsRead = reader.read(buffer)){
	    textBuffer.append(buffer, 0, charsRead);
	}
	// Some readers put funny nulls before each char (8->16 bit char encoding goof)
	return textBuffer.toString().replaceAll("\00", "");  
    }

    /**
     * Overrides same method in parent class, tells the dropper if we'll accept the data
     * before the drop is actually attempted.
     */
    public boolean canImport(JComponent comp, DataFlavor[] flavors) {
        if (hasFileFlavor(flavors)) { return true; }
        if (hasTextFlavor(flavors)) { return true; }
        return false;
    }

    protected boolean hasFileFlavor(DataFlavor[] flavors) {
        for (DataFlavor flavor: flavors) {
            if (flavor.isFlavorJavaFileListType()) {
                return true;
            }
        }
        return false;
    }

    protected boolean hasTextFlavor(DataFlavor[] flavors) {
        for (DataFlavor flavor: flavors) {
            if (flavor.isFlavorTextType()) {
                return true;
            }
        }
        return false;
    }

    /** Our components are copy-only */
    public int getSourceActions(JComponent c){
	return TransferHandler.COPY;
    }

    protected Transferable createTransferable(JComponent c) {
	if(c instanceof MobyContentPane){
	    return new SeahawkTransferable((MobyContentPane) c);
	}
	else{
	    return null;
	}
    }

    public void exportDone(JComponent c, Transferable t, int action) {
	if (t instanceof SeahawkTransferable){
	    // Do unification
	    ((SeahawkTransferable) t).exportDone();
	}
    }

}

