package edu.uchsc.ccp.example.obo;

import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

import org.geneontology.oboedit.dataadapter.DefaultOBOParser;
import org.geneontology.oboedit.dataadapter.OBOParseEngine;
import org.geneontology.oboedit.dataadapter.OBOParseException;
import org.geneontology.oboedit.datamodel.LinkedObject;
import org.geneontology.oboedit.datamodel.OBOClass;
import org.geneontology.oboedit.datamodel.OBOProperty;
import org.geneontology.oboedit.datamodel.OBOSession;
import org.geneontology.oboedit.datamodel.impl.OBORestrictionImpl;
import org.geneontology.oboedit.datamodel.impl.SynonymImpl;

/**
 * A utility program for interfacing with the OBO-Edit API.
 * 
 * @author William A Baumgartner Jr.
 * 
 */
public class OboEdit_Util {

	private OBOSession session;

	/**
	 * Constructor
	 * 
	 * @param oboFilePath
	 *            the location of the obo file to parse
	 */
	public OboEdit_Util(String oboFilePath) {
		/* Initialize a new OBOSession object for the input obo file */
		session = getSession(oboFilePath);
	}

	/**
	 * This method taken straight from the OBO-Edit FAQ webpage: http://wiki.geneontology.org/index.php/OBO-Edit:_OBO_Parser_-_Getting_Started
	 * 
	 * @param path
	 * @return an OBOSession object containing the contents of the input obo file
	 */
	public OBOSession getSession(String path) {
		DefaultOBOParser parser = new DefaultOBOParser();
		OBOParseEngine engine = new OBOParseEngine(parser);
		// OBOParseEngine can parse several files at once
		// and create one munged-together ontology,
		// so we need to provide a Collection to the setPaths() method
		Collection<String> paths = new LinkedList<String>();
		paths.add(path);
		engine.setPaths(paths);
		try {
			engine.parse();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (OBOParseException e) {
			e.printStackTrace();
		}
		OBOSession session = parser.getSession();
		return session;
	}

	/**
	 * Prints to standard output the root classes of the ontology that is currently loaded
	 * 
	 */
	public void printRootClasses() {
		/* Grab the root terms */
		Set roots = session.getRoots();

		/* For each root term, print it to standard output */
		for (Object root : roots) {
			System.out.print("Root Class: ");
			if (root instanceof OBOClass) {
				OBOClass rootClass = (OBOClass) root;
				printOboClass(rootClass, false, 0);
			} else {
				warn("Unexpected class found in root set: " + root.getClass().getName());
			}
			System.out.println("-----------------------------------");
		}

	}

	/**
	 * Returns a mapping from Term ID to Term
	 * 
	 * @return a Map where the keys are Term IDs and the values are the terms themselves
	 */
	public Map<String, OBOClass> getID2TermMap() {
		/* All we are doing here is adding generics to the Map for convenience of use */
		Map<String, OBOClass> id2termMap = session.getAllTermsHash();
		return id2termMap;
	}

	/**
	 * Prints some information about a given OBOClass object to standard output.
	 * 
	 * @param oboClass
	 *            the OBOClass to print
	 * @param recurse
	 *            if true, all children of this OBOClass will also be printed
	 * @param indent
	 *            controls the indenting of each successive child level, 0=no indent. For each level after 0, spacing is incremented by 2.
	 */
	public static void printOboClass(OBOClass oboClass, boolean recurse, int indent) {
		/* Initialize the indent variable "space" */
		String space = "";
		for (int i = 0; i < indent; i++) {
			space += "_";
		}

		/* Print the class name, id, isRoot, isObsolete, and definition */
		System.out.println(space + oboClass.getName() + "[" + oboClass.getID() + "]  isRoot:" + oboClass.isRoot() + "  isObsolete:"
				+ oboClass.isObsolete());
		System.out.println(space + "Definition: " + oboClass.getDefinition());

		/* Print the synonym(s) */
		Set synonyms = oboClass.getSynonyms();
		for (Object synonym : synonyms) {
			if (synonym instanceof SynonymImpl) {
				SynonymImpl synonymImpl = (SynonymImpl) synonym;
				System.out.println(space + "Synonym: " + synonymImpl.getText() + " type:" + synonymImpl.getScope());
			}
		}

		/* Print the parent(s) */
		Set parents = oboClass.getParents();
		for (Object parent : parents) {
			if (parent instanceof OBORestrictionImpl) {
				OBORestrictionImpl parentImpl = (OBORestrictionImpl) parent;
				LinkedObject linkedObject = parentImpl.getParent();
				OBOProperty property = parentImpl.getType();
				// System.out.println("Property name: " + property.getName());
				if (linkedObject instanceof OBOClass) {
					OBOClass parentClass = (OBOClass) linkedObject;
					System.out.println(space + property.getName() + " --> " + parentClass.getName() + " [" + parentClass.getID() + "]");
				}
			}
		}

		/* If the flag to recurse is set to true, then process each child of the input OBOClass */
		if (recurse) {
			Set childrenOfRoot = oboClass.getChildren();
			for (Object child : childrenOfRoot) {
				if (child instanceof OBORestrictionImpl) {
					OBORestrictionImpl childImpl = (OBORestrictionImpl) child;
					LinkedObject linkedObject = childImpl.getChild();
					if (linkedObject instanceof OBOClass) {
						OBOClass childClass = (OBOClass) linkedObject;
						printOboClass(childClass, recurse, indent += 2);
					}
				} else {
					warn("Unexpected class found in children set: " + child.getClass().getName());
				}
			}
		}
	}

	/**
	 * Prints a warning message to standard error
	 * 
	 * @param message
	 */
	private static void warn(String message) {
		System.err.println("WARNING -- OboEdit_Util: " + message);
	}

	/**
	 * Run an example application that loads in an ontology (must be the cell ontology for part of this example to work) and exercises the
	 * OboEdit_Util class.
	 * 
	 * @param args
	 *            args[0] = path to the cell.obo file
	 */
	public static void main(String[] args) {
		if (args.length != 1) {
			System.err
					.println("USAGE: java -cp bounce.jar:jhall.jar:oboedit.jar:org.geneontology.jar edu.uchsc.ccp.example.obo.OboEdit_Util cell.obo");
		} else {
			OboEdit_Util oboEditUtil = new OboEdit_Util(args[0]);
			System.out.println("=====================");
			System.out.println("PRINTING ROOT CLASSES");
			System.out.println("=====================");
			oboEditUtil.printRootClasses();
		
			Map<String, OBOClass> id2termMap = oboEditUtil.getID2TermMap();

			System.out.println("==========================");
			System.out.println("PRINTING MUSCLE CELL CLASS");
			System.out.println("==========================");
			/* Get the T cell term [CL:0000084] */
			OBOClass muscleClass;
			String id = "CL:0000187";
			if (id2termMap.containsKey(id)) {
				muscleClass = id2termMap.get(id);

				OboEdit_Util.printOboClass(muscleClass, true, 0);
			} else {
				System.err.println("Class not found for ID: " + id);
			}
		}
	}
}
