package ca.ucalgary.seahawk.gui;

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

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

/**
 * A visual representation of a primary data object in Seahawk, intended
 * for being a placeholder for data if it is not instantiated yet, allowing
 * the user to drop data onto it via a MobyObjectTransferHandler
 * (hence the implementation of MobyObjectReceiver).
 */
public class MobyDataObjectWidget extends JLabel implements MobyObjectReceiver, MobyContentProducer{
    public static final int PARAM_TOOLTIP_WIDTH = 50;
    public static final int MAX_DATA_DESC = 30;
    public static final Color INSTANTIATED_FG_COLOUR = Color.DARK_GRAY;
    public static final Color UNINSTANTIATED_FG_COLOUR = Color.RED;
    public static final String INSTANTIATED_ICON_RESOURCE = "ca/ucalgary/seahawk/resources/images/checkmark.gif";
    public static final String UNINSTANTIATED_ICON_RESOURCE = "ca/ucalgary/seahawk/resources/images/attention.gif";

    private static Icon attentionIcon;
    private static Icon okIcon;

    private MobyPrimaryData targetData;
    private MobyPrimaryData actualData;
    private String name;

    private Vector<ActionListener> listeners;

    /**
     * C-tor for instantiated data.
     */
    public MobyDataObjectWidget(String articleName, MobyClient client, 
				MobyDataInstance data, boolean editable) throws IllegalArgumentException{
	super("", SwingConstants.CENTER);
	listeners = new Vector<ActionListener>();
	name = articleName;
	if(!(data instanceof MobyPrimaryData)){
	    throw new IllegalArgumentException("The data passed to MobyDataObjectWidget's constructor was not a " +
					       "MobyPrimaryData & MobyDataInstance as expected, received " +
					       data.getClass().getName());
	}
	targetData = (MobyPrimaryData) data;
	actualData = (MobyPrimaryData) data;
	setForeground(INSTANTIATED_FG_COLOUR);
	setIcon(getInstantiatedIcon());

	setLabelByData();
	
	// Are we going to allow new dropped data to supplant the current data?
	if(editable){
	    //System.err.println("Registering handler for the data");
	    setTransferHandler(MobyObjectTransferHandler.getHandler(client));
	}	

	// Enables copying of MOBY data to the clipboard
	addMouseListener(new DragMouseAdapter());
    }

    // Shamelessly copied from the Java DnD tutorial's LabalDND.java
    private class DragMouseAdapter extends MouseAdapter {
        public void mousePressed(MouseEvent e) {
            JComponent c = (JComponent)e.getSource();
            TransferHandler handler = c.getTransferHandler();
            handler.exportAsDrag(c, e, TransferHandler.COPY);
        }
    }

    private Icon getInstantiatedIcon(){
	if(okIcon == null){
	    try{
		okIcon = new ImageIcon(getClass().getClassLoader().getResource(INSTANTIATED_ICON_RESOURCE));
	    } catch(Exception e){
		e.printStackTrace();
		System.err.println("Could not load the warning icon resource " +
				   INSTANTIATED_ICON_RESOURCE + " for MobyDataObjectWidget: "+e);
	    }
	}
	return okIcon;
    }

    private Icon getUninstantiatedIcon(){
	if(attentionIcon == null){
	    try{
		attentionIcon = new ImageIcon(getClass().getClassLoader().getResource(UNINSTANTIATED_ICON_RESOURCE));
	    } catch(Exception e){
		e.printStackTrace();
		System.err.println("Could not load the attention icon resource " +
				   UNINSTANTIATED_ICON_RESOURCE + " for MobyDataObjectWidget: "+e);
	    }
	}
	return attentionIcon;
    }

    public MobyDataObjectWidget(String articleName, MobyClient client, 
				MobyPrimaryData data) throws IllegalArgumentException{
	this(articleName, client, data.getDataType(), data.getNamespaces(),
	     (data instanceof MobyPrimaryDataSet));
    }

    /**
     * C-tor for uninstantiated data.
     */
    public MobyDataObjectWidget(String articleName, MobyClient client, 
				MobyDataType dataType, MobyNamespace[] nss, 
				boolean isaCollection) throws IllegalArgumentException{
	super("", SwingConstants.CENTER);
	name = articleName;
	listeners = new Vector<ActionListener>();

	if(isaCollection){
	    MobyPrimaryDataSet collection = new MobyPrimaryDataSet(articleName);
	    collection.setDataType(dataType);
	    targetData = collection;
	}
	else{
	    MobyPrimaryDataSimple simple = new MobyPrimaryDataSimple(articleName);
	    simple.setDataType(dataType);
	    targetData = simple;		
	}
	if(nss != null){
	    targetData.setNamespaces(nss);
	}

	setLabelByData();
	setForeground(UNINSTANTIATED_FG_COLOUR);
	setIcon(getUninstantiatedIcon());

	// registers for callback when data dropped onto the component
	setTransferHandler(MobyObjectTransferHandler.getHandler(client));
    }

    private String getTypeLabel(MobyPrimaryData data){
	String expected = data.getDataType().getName();
	if(MobyTags.MOBYOBJECT.equals(expected)){
	    MobyNamespace[] nss = data.getNamespaces();
	    if(nss != null && nss.length > 0){
		expected = nss[0].getName();
		for(int i = 1; i < nss.length; i++){
		    expected += " or " + nss[i].getName();
		}
	    }
	}
	return expected;
    }

    protected void setLabelByData(){
	String labelText = "[Uninitialized Object]";
	String tipText = "[No tip is currently available for this data]";
	if(actualData == null){
	    MobyDataType dataType = targetData.getDataType();
	    if(targetData instanceof MobyPrimaryDataSet){
		labelText = name+": awaiting Collection("+getTypeLabel(targetData)+"), drop data here to continue";
	    }
	    else{
		labelText = name+": awaiting "+getTypeLabel(targetData)+", drop data here to continue";
	    }
	    tipText = "Please drop data of type "+dataType.getName()+
		" onto here: "+dataType.getDescription();
	}
	else{
	    MobyDataType dataType = actualData.getDataType();
	    if(targetData instanceof MobyPrimaryDataSet){
		labelText = name+": Collection("+getTypeLabel(actualData)+") -- "+actualData.getName();
	    }
	    else{
		String display = actualData.getName();
		if(display == null || display.trim().length() == 0){
		    if(MobyTags.MOBYOBJECT.equals(actualData.getDataType().getName())){
			display = actualData.getId();
		    }
		    else{
			display = ((MobyDataInstance) actualData).getObject().toString();
		    }
		}
		if(display.length() > MAX_DATA_DESC){
		    display = display.substring(0, MAX_DATA_DESC-3)+"...";
		}
		labelText = name+": "+getTypeLabel(actualData)+" -- "+display;
	    }
	    tipText = dataType.getDescription();
	    if(tipText == null || tipText.length() == 0){
		tipText = dataType.getComment();		
	    }
	    if(tipText == null || tipText.length() == 0){
		tipText = "[No description for the datatype " + dataType.getName()  + " was available]";
	    }
	}

	setText(labelText);
	setToolTipText(HTMLUtils.htmlifyToolTipText(tipText, PARAM_TOOLTIP_WIDTH));
    }

    /**
     * Any object registering themselves here will receive a callback when the value of the
     * data changes.
     */
    public void addActionListener(ActionListener al){
	if(!listeners.contains(al)){
	    listeners.add(al);
	}
    }

    public boolean removeActionListener(ActionListener al){
	return listeners.remove(al);
    }

    public String getName(){
	return name;
    }

    public MobyPrimaryData getData(){
	if(actualData != null){
	    return actualData;
	}
	else{
	    return targetData;
	}
    }

    /**
     * Callback routine MobyObjectTransferHandler calls when suitable 
     * data has been dropped on this widget.  Could be called programmatically
     * too to change the value of the data.
     */
    public void consumeMobyObject(String incomingName, MobyDataInstance incomingData){
	//System.err.println("Received data "+incomingData);
	if(!(incomingData instanceof MobyPrimaryData)){
	    System.err.println("MobyDataObjectWidget received a data class it could " +
			       "not consume (ignoring): " + actualData.getClass().getName());
	    return;
	}

	actualData = (MobyPrimaryData) incomingData;
	setForeground(INSTANTIATED_FG_COLOUR);
	setIcon(getInstantiatedIcon());
	setLabelByData();

	if(listeners == null || listeners.size() == 0){
	    return;
	}

	repaint();
	ActionEvent actionEvent = new ActionEvent(this, 0, incomingName);
	for(ActionListener listener: listeners){
	    listener.actionPerformed(actionEvent);
	}
    }

    /**
     * List of data that we are willing to consume in consumeMobyObject() at the given moment.
     * This could, for example a MobyDataObjectSet of MobyDataComposites of data type DNASequences, 
     * or a single MobyDataComposite of data type FASTA_AA.
     */
    public java.util.Map<String, org.biomoby.shared.MobyPrimaryData> getAcceptableData(){
	Map<String,org.biomoby.shared.MobyPrimaryData> acceptableData = 
            new TreeMap<String,org.biomoby.shared.MobyPrimaryData>();
	acceptableData.put(name, targetData);
	return acceptableData;
    }

    /**
     * Used to send data out to the clipboard.
     */
    public MobyContentInstance exportMobyContent(){
	return null;
    }
}
