package org.biomoby.shared.data;

import org.w3c.dom.Element;
import org.biomoby.registry.meta.Registry;
import org.biomoby.shared.*;

/**
 * Note: This class has not been thoroughly tested.  If you find bugs,
 * please e-mail Paul Gordon, gordonp@ucalgary.ca
 *
 * Represents an Xref tag within the CrossReferenceInformation Block (CRIB)
 * of a MOBY Object.  While not strictly a MOBY Object, it contains all of the 
 * information for one (namespace and id), hence it can act as either in this
 * class.  An Xref is a special case of a service associated Object where there
 * can only be one service associated, and the additional fields of a 
 * textual description, a cross reference type, and an "evidence code" 
 * (indicating source and strength of the cross reference).
 *
 * Another unusual aspect to this class is that due to its polymorphism, 
 * toXML() accepts extra XML modes, used only in this class.  By default the 
 * Xref acts (and is therefore printed) like a base MOBY Object, but if you
 * are a service provider creating XML you want it to print as "<Xref...>"
 * in the parent Object's Cross Reference Information Block (CRIB).
 * To acheive this, use the CRIB_XML_MODE_ON and CRIB_XML_MODE_OFF modes
 * around your toXML().  If you are using MobyDataObject derivatives,
 * this is done for you automatically.
 */

public class MobyDataXref extends MobyDataObjectSAI{
    private String serviceDescription;
    private String xrefType;
    private String evidenceCode;
    private boolean isCRIBXML = false;

    public static final int CRIB_XML_MODE_ON = 6766;
    public static final int CRIB_XML_MODE_OFF = 6767;

    public static final String IC = "Inferred by Curator"; 
    public static final String IDA = "Inferred from Direct Assay"; 
    public static final String IEA = "Inferred from Electronic Annotation"; 
    public static final String IEP = "Inferred from Expression Pattern"; 
    public static final String IGI = "Inferred from Genetic Interaction"; 
    public static final String IMP = "Inferred from Mutant Phenotype"; 
    public static final String IPI = "Inferred from Physical Interaction"; 
    public static final String ISS = "Inferred from Sequence or Structral similarity"; 
    public static final String NAS = "Non-traceable Author Statement"; 
    public static final String ND = "No biological Data available"; 
    public static final String RCA = "inferred from Reviewed Computational Analysis"; 
    public static final String TAS = "Traceable Author Statement";
    public static final String NR = "Not Recorded"; 

    public MobyDataXref(MobyNamespace namespace, String id, MobyService service){
	// Anonymous object
	super("", id, new MobyService[] {service}, service.getServiceType().getRegistry());
	// Note: in future maybe we should check that the namespace and the service from from the same registry
	addNamespace(namespace);
    }

    /**
     * Gets the registry implicitly from the service being passed in. 
     */
    public MobyDataXref(String namespace, String id, MobyService service){
	super("", id, new MobyService[] {service}, service.getServiceType().getRegistry());
	addNamespace(MobyNamespace.getNamespace(namespace));
    }

    public MobyDataXref(String namespace, String id, String serviceName, String authURI){
	this(namespace, id, serviceName, authURI, null);
    }

    public MobyDataXref(String namespace, String id, String serviceName, String authURI, Registry registry){
	super("", id, new MobyService[] {MobyService.getService(serviceName, authURI)}, registry);
	addNamespace(MobyNamespace.getNamespace(namespace));
	MobyService s = getService();
	if(s == null){  // the service name given didn't resolve to something in the registry
	    s = new MobyService(serviceName, authURI);  // make an explicit info-void one instead
	    System.err.println("Created token service object for unresolvable xref service '"+serviceName+"'");
	    setServices(new MobyService[]{s});
	}
    }

    public MobyDataXref(Element e) throws MobyException{
	this(e, (Registry) null);
    }

    public MobyDataXref(Element e, Registry registry) throws MobyException{
	this(getNamespace(e).getName(), 
	     getId(e), 
	     MobyPrefixResolver.getAttr(e, "serviceName"), 
	     MobyPrefixResolver.getAttr(e, "authURI"), 
	     registry);

	setEvidenceCodeByAbbrev(MobyPrefixResolver.getAttr(e, "evidenceCode"));
	setXrefType(MobyPrefixResolver.getAttr(e, "xrefType"));

	String d = e.getTextContent();
	if(d != null && d.length() > 0){
	    setDescription(d);
	}
    }

    /**
     * Build an Xref based on an existing object.
     * The namespace and id will be kept, everything else ignored as they don't 
     * fit in a base MOBY Object.
     */
    public MobyDataXref(MobyDataObject mdsi, MobyService service){
	super(new MobyDataObject(mdsi), new MobyService[] {service});
    }

    /**
     * Provided by a called service A, describes in plain English(?) the relationship
     * between the Xref (encapsulated in an Object returned by A) and the 
     * cross-referenced service B. 
     */
    public String getDescription(){
	return serviceDescription;
    }
    
    public void setDescription(String desc){
	serviceDescription = desc;
    }

    public void setEvidenceCodeByAbbrev(String abbrev) throws IllegalArgumentException{
	setEvidenceCode(evidenceAbbrevToCode(abbrev));
    }

    public void setEvidenceCode(String code) throws IllegalArgumentException{
	if(code == null || code.length() == 0){
	    throw new IllegalArgumentException("Evidence code for an Xref cannot be blank");
	}
	if(code.equals(IC) || code.equals(IDA) || code.equals(IEA) ||
	   code.equals(IEP) || code.equals(IGI) || code.equals(IMP) ||
	   code.equals(IPI) || code.equals(ISS) || code.equals(NAS) ||
	   code.equals(ND) || code.equals(RCA) || code.equals(TAS) ||
	   code.equals(NR)){
	    evidenceCode = code;
	}
	else{
	    throw new IllegalArgumentException("Evidence code to be set (" + 
					       code + ") was not a valid GO code");
	}
    }

    /**
     * Given one of the static final evidence codes from this class, returns the 2 or 3 letter
     * GO evidence abbreviations.  See http://www.geneontology.org/doc/GO.evidence.html
     */
    public static String evidenceAbbrevToCode(String abbrev) throws IllegalArgumentException{
	if(abbrev == null || abbrev.length() == 0){
	    throw new IllegalArgumentException("Evidence code abbreviation for an Xref cannot be blank");
	}
	if(abbrev.equals("IC")){
	    return IC;
	}
	if(abbrev.equals("IDA")){
	    return IDA;
	}
	if(abbrev.equals("IEA")){
	    return IEA;
	}
	if(abbrev.equals("IEP")){
	    return IEP;
	}
	if(abbrev.equals("IGI")){
	    return IGI;
	}
	if(abbrev.equals("IMP")){
	    return IMP;
	}
	if(abbrev.equals("IPI")){
	    return IPI;
	}
	if(abbrev.equals("ISS")){
	    return ISS;
	}
	if(abbrev.equals("NAS")){
	    return NAS;
	}
	if(abbrev.equals("ND")){
	    return ND;
	}
	if(abbrev.equals("RCA")){
	    return RCA;
	}
	if(abbrev.equals("TAS")){
	    return TAS;
	}
	if(abbrev.equals("NR")){
	    return NR;
	}
	throw new IllegalArgumentException("Evidence abbreviation to expand (" + 
					   abbrev + " was not a valid GO code abbreviation");
    }

    /**
     * Given one of the static final evidence codes from this class, returns the 2 or 3 letter
     * GO evidence abbreviations.  See http://www.geneontology.org/doc/GO.evidence.html
     */
    public static String evidenceCodeToAbbrev(String code) throws IllegalArgumentException{
	if(code == null || code.length() == 0){
	    throw new IllegalArgumentException("Evidence code for an Xref cannot be blank");
	}
	if(code.equals(IC)){
	    return "IC";
	}
	if(code.equals(IDA)){
	    return "IDA";
	}
	if(code.equals(IEA)){
	    return "IEA";
	}
	if(code.equals(IEP)){
	    return "IEP";
	}
	if(code.equals(IGI)){
	    return "IGI";
	}
	if(code.equals(IMP)){
	    return "IMP";
	}
	if(code.equals(IPI)){
	    return "IPI";
	}
	if(code.equals(ISS)){
	    return "ISS";
	}
	if(code.equals(NAS)){
	    return "NAS";
	}
	if(code.equals(ND)){
	    return "ND";
	}
	if(code.equals(RCA)){
	    return "RCA";
	}
	if(code.equals(TAS)){
	    return "TAS";
	}
	if(code.equals(NR)){
	    return "NR";
	}
	throw new IllegalArgumentException("Evidence code to be abbreviated (" + 
					   code + " was not a valid GO code");
    }

    /**
     * Provided by a called service A, indicates the source and strength of 
     * the relationship between the Xref (encapsulated in an Object 
     * returned by A) and the cross-referenced service B. 
     * The indication is made using one of the GeneOntology evidence types
     * enumerated in this class.
     */
    public String getEvidenceCode(){
	return evidenceCode;
    }
    
    /**
     * At the moment, no check is made for the validity of the type string
     * in the cross reference ontology, mostly because I don't know how to 
     * access it properly.
     */
    public void setXrefType(String type){
	xrefType = type;
    }

    public String getXrefType(){
	return xrefType;
    }

    public MobyDataXref clone(){
	return new MobyDataXref(getNamespace(), getId(), getService());
    }

    public MobyService getService(){
	return mobyServices == null ? null : mobyServices[0];
    }

    /**
     * If more than one service is provided, only the first will be taken.
     */
    public void setServices(MobyService[] services){
	if(services == null){
	    mobyServices = null;
	}
	else if(services.length == 1){
	    mobyServices = services;
	}
	else{
	    MobyService[] service = new MobyService[1];
	    service[0] = services[0];
	    mobyServices = service;
	}
    }

    /**
     * If the XML mode is one of the constants defined by MobyDataInstance,
     * this method calls its superclass equivalent (i.e. acts and prints like a Moby
     * Object).  
     *
     * If you are a service provider formulating an XML response object's Cross 
     * Reference Information Block, you will want to call this method with 
     * CRIB_XML_MODE_ON before calling toXML(), and with CRIB_XML_MODE_OFF 
     * afterwards (which restores the previous SERVICE or CENTRAL mode setting).  
     * If you are using MobyDataObject derivatives, this mode setting 
     * is done for you automatically.
     */
    public void setXmlMode(int mode) throws IllegalArgumentException{
	// Since this class is polymorphic, it has a special xml mode
	// known only to itself.
	if(mode == CRIB_XML_MODE_ON){
	    isCRIBXML = true;
	}
	else if(mode == CRIB_XML_MODE_OFF){
	    isCRIBXML = false;
	}
	else{
	    dataInstance.setXmlMode(mode);
	}
    }
    
    /**
     * Values passed here are essentially ignored when a namespace already exists,
     * because an Xref can only have one.  When thwe Xref is acting an an Object though, 
     * the extra namespaces could matter.
     */
    public void addNamespace(MobyNamespace ns){
	dataInstance.addNamespace(ns);
    }

    public MobyNamespace[] getNamespaces(){
	return dataInstance.getNamespaces();
    }

    /**
     * Since an Xref can only have one namespace, this method returns the one
     * that will be used in Xref-style XML output.  The first namespace in the
     * namespace array is used, if available.
     */
    public MobyNamespace getNamespace(){
	MobyNamespace[] mn = getNamespaces(); 
	return mn == null ? null : mn[0];
    }

    public String toString(){
	return dataInstance.toString();
    }

    public String toXML(){
	// If we are a service provider it should look like an Xref
	if(isCRIBXML){
	    MobyService service = getService();
	    MobyNamespace[] myNamespaces = getNamespaces();
	    return "<Xref namespace='"+(myNamespaces == null ? null : myNamespaces[0])+
		"' id='"+getId()+
		"' authURI='"+(service == null ? null : service.getAuthority())+
		"' serviceName='"+(service == null ? null : service.getName()) +
		"' evidenceCode='"+ evidenceCodeToAbbrev(evidenceCode)+
		"' xrefType='"+xrefType+"'>"+
		serviceDescription+"</Xref>";
	}
	// If we're not in the CRIB, act as if it's a base Object
	else{
	    return super.toXML();
	}
    }
}
