package org.genemania.dw.entity;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeMap;

import org.genemania.dw.util.GenUtil;

/**
 * Local representation for an ontology term. Focused on the PSI-MI OBO.
 * If more than one PSI-MI short label syn, only the first one is returned.
 * Same for xrefs of type regex.
 * PSI-MI xrefs can be modeled in a similar fashion to the expanded syn list.
 * For now, the isObsolete attribute for an entry is treated separetely from
 * the inherited status attribute, which might be used to represented local
 * status for an entry, rather than its status in the external source.
 *
 * @author rashadbadrawi
 */

public class OBOTerm extends ExtResource {

    //general
    public static final String RELATION_ISA = "is-a";
    public static final String RELATION_PARTOF = "part_of";
    public static final String RELATION_REGULATES_NEG = "negatively_regulates";
    public static final String RELATION_REGULATES_POS = "positively_regulates";
    public static final String RELATION_REGULATES = "regulates";

    //PSI-MI specific
    public static final String SOURCE_PSI_MI = "PSIMI";
    public static final String SUBSET_PSI_MI_DRUGGABLE = "Drugable";
    public static final String SUBSET_PSI_MI_SLIM = "PSI-MI slim";
    public static final String XREF_PSI_MI_REGEX = "id-validation-regexp";
    public static final String XREF_PSI_MI_URL = "search-url";
    public static final String SYN_PSI_MI_ALT = "PSI-MI-alternate";
    public static final String SYN_PSI_MI_SHORT = "PSI-MI-short";

    private static ArrayList <String> relationList;
    //key: syn type value: syn list, this is a PSI-MI specific att.
    private TreeMap <String, ArrayList <String>> expandedPSISynMap = new
            TreeMap <String, ArrayList <String>> ();

    private String comment;
    private boolean obsoleteFlag;
    private String defNS;
    private ArrayList <String> subsetList = new ArrayList <String> ();
    private ArrayList <String> namespaceList = new ArrayList <String> ();
    private ArrayList <String> xrefList = new ArrayList <String> ();
    //key: relation type, value: list of applicable IDs
    private TreeMap <String, ArrayList <String>> relationMap = new
            TreeMap <String, ArrayList <String>> ();

    static {
        relationList = new ArrayList <String> ();
        relationList.add (RELATION_ISA);
        relationList.add (RELATION_PARTOF);
        relationList.add (RELATION_REGULATES_NEG);
        relationList.add (RELATION_REGULATES_POS);
        relationList.add (RELATION_REGULATES);
    }

    public OBOTerm () {}

    public void setComment (String comment) {

        GenUtil.validateString(comment);
        this.comment = comment;
    }

    public void setDefNS (String defNS) {

        GenUtil.validateString (defNS);
        this.defNS = defNS;
    }

    public void setIsObsolete (boolean obsoleteFlag) {

        this.obsoleteFlag = obsoleteFlag;
    }

    public void addSubset (String subset) {

        if (!OBOTerm.isValidSubset (source, subset)) {
            throw new IllegalArgumentException ("Unsupported subset: " +
                    source + " " + subset);
        }
        if (!subsetList.contains (subset)) {
            subsetList.add (subset);
        }
    }

    public void addXref (String xref) {

        GenUtil.validateString(xref);
        if (!xrefList.contains (xref)) {
            xrefList.add (xref);
        }
    }

    public void addNamespace (String namespace) {

        GenUtil.validateString(namespace);
        if (!namespaceList.contains (namespace)) {
            namespaceList.add (namespace);
        }
    }

    public void addRelationship (String type, String ID) {

       GenUtil.validateString(type);
       GenUtil.validateString (ID);
       if (!relationList.contains (type)) {
           System.err.println ("Warning: Undocumented relationship type: " + type);
       }
       ArrayList <String> tempList = relationMap.get (type);
       if (tempList == null) {
           tempList = new ArrayList <String> ();
       }
       if (!tempList.contains (ID)) {
           tempList.add (ID);
       }
       //replace existing
       relationMap.put (type, tempList);
    }

    public void addPSIMISyn (String synType, String syn) {

        if (!source.equals (SOURCE_PSI_MI)) {
            throw new UnsupportedOperationException ("Operation only allowed for " +
                    " terms from " + SOURCE_PSI_MI);
        }
        //addSyn (syn);
        GenUtil.validateString (synType);
        if (!SYN_PSI_MI_ALT.equals (synType) &&
            !SYN_PSI_MI_SHORT.equals (synType)) {
            throw new IllegalArgumentException ("Unrecognized syn type: " +
                                                synType);
        }
        ArrayList <String> tempList = expandedPSISynMap.get (synType);
        if (tempList == null) {
            tempList = new ArrayList <String> ();
        }
        if (!tempList.contains (syn)) {
            tempList.add (syn);
        }
        //replace existing
        expandedPSISynMap.put (synType, tempList);
    }

    public String getComment () { return comment; }

    public String getDefNS () { return defNS; }

    public boolean isObsolte () { return obsoleteFlag; }

    public ArrayList <String> getSubsetList () { return subsetList; }

    public ArrayList <String> getXrefList () { return xrefList; }

    public ArrayList <String> getNamespaceList () { return namespaceList; }

    public ArrayList <String> getPSIMISynList (String synType) {

        if (!source.equals (SOURCE_PSI_MI)) {
            throw new UnsupportedOperationException ("Operation only allowed for " +
                    " terms from " + SOURCE_PSI_MI);
        }
        return expandedPSISynMap.get (synType);
    }

    public ArrayList <String> getRelationshipList (String type) {

        return relationMap.get (type);
    }

    public static final boolean isValidSubset (String source, String subset) {

        GenUtil.validateString (source);
        GenUtil.validateString (subset);
        if (!OBOTerm.SOURCE_PSI_MI.equals (source)) {
            //System.err.println ("Warning: Unsupported OBO source: " + source);
            return true;                              //pass for all
        }
        if (OBOTerm.SOURCE_PSI_MI.equals (source)) {
            if (OBOTerm.SUBSET_PSI_MI_DRUGGABLE.equals (subset) ||
                OBOTerm.SUBSET_PSI_MI_SLIM.equals (subset)) {
                return true;
            }
        }

        return false;
    }

    public boolean isValidXRefFormat (String assocID) {

       if (!OBOTerm.SOURCE_PSI_MI.equals (source)) {
           throw new UnsupportedOperationException("Operation only allowed for " +
                    " terms from " + SOURCE_PSI_MI);
       }
        boolean found = false;
        for (int i = 0; i < xrefList.size(); i++) {
            if (xrefList.get (0).contains(OBOTerm.XREF_PSI_MI_REGEX)) {
                found = true;
                String regex = xrefList.get (0).
                               substring (OBOTerm.XREF_PSI_MI_REGEX.length () + 1);
                if (assocID.matches(regex)) {
                    return true;
                } else {
                    return false;
                }

            }
        }

        if (!found) {
            System.err.println ("No xref format found in term: " + name);
        }

        return false;
    }

    public String getPSIMIShortLabel () {

       if (!OBOTerm.SOURCE_PSI_MI.equals (source)) {
           throw new UnsupportedOperationException("Operation only allowed for " +
                    " terms from " + SOURCE_PSI_MI);
       }

       return expandedPSISynMap.get (OBOTerm.SYN_PSI_MI_SHORT) != null ?
              expandedPSISynMap.get (OBOTerm.SYN_PSI_MI_SHORT).get (0) : getName();
    }

    @Override
    public String toString () {

        String str = source;
        str += GenUtil.TAB;

        str += ID;
        str += GenUtil.TAB;

        if (name != null) {
            str += name;
        } else {
            str += GenUtil.NA;
        }
        str += GenUtil.TAB;

        if (definition != null) {
            str += definition;
        } else {
            str += GenUtil.NA;
        }
        str += GenUtil.TAB;

        if (comment != null) {
            str += comment;
        } else {
            str += GenUtil.NA;
        }
        str += GenUtil.TAB;

        str += obsoleteFlag;
        str += GenUtil.TAB;

        if (getSubsetList ().size () > 0) {
            str = GenUtil.addList (str, getSubsetList());
        } else {
            str += GenUtil.NA;
        }
        str += GenUtil.TAB;

        if (getNamespaceList ().size() > 0) {
            str = GenUtil.addList(str, getNamespaceList());
        } else {
            if (getDefNS() != null) {
                str += getDefNS();
            } else {
                str += GenUtil.NA;
            }
        }
        str += GenUtil.TAB;

        if (getXrefList ().size() > 0) {
            str = GenUtil.addList(str, getXrefList());
        } else {
            str += GenUtil.NA;
        }
        str += GenUtil.TAB;

        if (getSynList ().size() > 0) {
            str = GenUtil.addList(str, getSynList());
        } else {
            str += GenUtil.NA;
        }
        str += GenUtil.TAB;

        //possibly pull into a helper method.
        Iterator iterator = relationMap.keySet().iterator();
        int cnt = 0;
        while (iterator.hasNext ()) {
            String type = (String) iterator.next ();
            ArrayList <String> tempList = relationMap.get (type);
            if (cnt++ > 0) {
                str += GenUtil.SEMICOLON;
            }
            str += type + "(";
            for (int i = 0; i < tempList.size (); i++) {
                if (i > 0) {
                    str += GenUtil.SEMICOLON;
                }
                str += tempList.get (i);
            }
            str += ")";
        }
        if (cnt == 0) {
            str += GenUtil.NA;
        }
        str += GenUtil.TAB;
        
        if (!getSource().equals (OBOTerm.SOURCE_PSI_MI)) {
            return str;
        }
        System.out.println (getSource());
        //same
        iterator = expandedPSISynMap.keySet().iterator();
        cnt = 0;
        while (iterator.hasNext ()) {
            String synType = (String) iterator.next ();
            ArrayList <String> tempList = expandedPSISynMap.get (synType);
            if (cnt++ > 0) {
                str += GenUtil.SEMICOLON;
            }
            str += synType + "(";
            for (int i = 0; i < tempList.size (); i++) {
                if (i > 0) {
                    str += GenUtil.SEMICOLON;
                }
                str += tempList.get (i);
            }
            str += ")";
        }
        if (cnt == 0) {
            str += GenUtil.NA;
        }
        str += GenUtil.TAB;

        return str;
    }
}

