package biotree;

import java.io.IOException;

import org.json.simple.parser.ParseException;

import search.BST;

public class BioTree {
	//FIXME: replace with a single kd-tree
	private static BST<Integer, TaxonNode> idNodes = new BST<Integer, TaxonNode>();
	private static BST<String, TaxonNode> strNodes = new BST<String, TaxonNode>();
	private static BST<String, Integer> incorrectNames = new BST<String, Integer>();
	
	public static void main(String[] args) throws IOException, ParseException {
		BioTree.processRecord("Micropterus dolomieui");
	}
	
	/**
	 * Initialize species abstract object
	 */
	public static void init() {
		idNodes = new BST<Integer, TaxonNode>();
	}

	/**
	 * Reads the BioTree from a file written by write().
	 * TODO: Implement
	 * 
	 * @param fn
	 *            Filename to read from
	 */
	public static void init(String fn) {

	}

	/**
	 * Writes the BioTree BST to a file.
	 * TODO: Implement
	 * 
	 * @param fn
	 *            Filename to write to
	 */
	public static void write(String fn) {

	}

	/**
	 * Process a record. Adds classification to tree if it doesn't exist.
	 * Returns the taxonId of the new / existing record.
	 * 
	 * @param taxonId The taxonId of the possible new entry
	 * @return taxonId of new species entry
	 */
	public static Integer processRecord(int taxonId) {
		//pass taxonId directly to function to add / increment it
		if (processTaxonId(taxonId)) return null;
		return taxonId;
	}
	
	/**
	 * Process a record. Adds classification to tree if it doesn't exist.
	 * Returns the taxonId of the new / existing record.
	 * 
	 * @param scientificName The scientific name of the possible new entry
	 * @return taxonId of new / existing entry
	 * @throws IOException 
	 * @throws ParseException 
	 */
	public static Integer processRecord(String scientificName) throws IOException, ParseException {
		//reverse lookup based on name, try adding the found taxonId.
		System.out.println("Processing " + scientificName);
		TaxonNode res = strNodes.get(scientificName);
		if (res == null) {
			System.out.println("Not already found.");
			Integer incorrectNameId = incorrectNames.get(scientificName);
			if (incorrectNameId != null) if (incorrectNameId == -1) return null;
			res = idNodes.get(incorrectNameId);
		}
		Integer taxonId = null;
		//if the taxonId was not found in the local database
		if (res == null) {
			try {
				taxonId = WormsAPI.nameToID(scientificName);
			} catch (Exception e) {
				taxonId = WormsAPI.nameToRecordID(scientificName);
				if (taxonId != null)
					incorrectNames.put(scientificName, taxonId);
				else {
					incorrectNames.put(scientificName, -1);
					
				}
			}
		}
		else
			taxonId = res.getTaxonId();
		if (taxonId == null) return null;
		if (taxonId == -999)
			taxonId = WormsAPI.fuzzyNameToID(scientificName);
		if (processTaxonId(taxonId)) return null;
		return taxonId;
	}
	
	/**
	 * Process a new entry if it doesn't exist. If it does exist, increment the number
	 * of Records for this classification by one. 
	 * @param taxonId New / existing TaxonID to add / increment count thereof.
	 * @return true if the process failed, false if nothing went wrong
	 */
	private static boolean processTaxonId(int taxonId) {
		TaxonNode[] newNodes = null;			//possible eventual new nodes
		TaxonNode tx = idNodes.get(taxonId);	//search tree to see if the node exists already
		System.out.println("tx" + tx);
		if (tx != null)	{					//if it does exist, increment its count
			tx.incCount();
		}
		else {								//otherwise, perform API call to get tree
			try {
				newNodes = WormsAPI.idToClassification(taxonId);
			} catch (IOException | ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if (newNodes == null) return true;
			newNodes[newNodes.length - 1].incCount();	//one of the new nodes exists
			
			for (int i = newNodes.length - 1; i >= 0; i--) {		//iterate over all node starting from lowest child
				tx = newNodes[i];
				TaxonNode current = idNodes.get(tx.getTaxonId());
				TaxonNode parent = null;
				if (i > 0) {										//if this is not the highest up find its parent
					parent = idNodes.get(newNodes[i - 1].getTaxonId());		//the parent is either already in existence
					if (parent == null) parent = newNodes[i - 1];		//or is is the old one that will be added later
				}
				if (current == null) { 							//if this node is not found, add it
					System.out.println("Put: " + tx.getTaxonId());
					idNodes.put(tx.getTaxonId(), tx);				//put it in the search structure
					strNodes.put(tx.getName(), tx);
					tx.setParent(parent);						//set its parent to the last
					if (parent != null) parent.addChild(tx);		//if a parent exists, add it as a child to its parent
				} else
					//stop loop if this node already exists in the tree (all its parents must exist too!)
					break;
			}
		}
		return false;
	}

	/**
	 * Get the species at a given index (taxonId). This assumes that the
	 * node already exists or else it will return null. As such, it is best
	 * to use this function once all the data has been parsed and the BioTree
	 * has been built. 
	 * 
	 * @param i
	 *            The speciesid (index) of the species.
	 * @return The Species object.
	 */
	public static TaxonNode getTaxonRecord(int taxonId) {
		return idNodes.get(taxonId);
	}
	
	public static void printTree() {
		printTree(idNodes.get(2), 0);
	}
	
	/**
	 * Print a taxonNode's tree starting at the supplied root.
	 * @param tx
	 * @param level
	 */
	private static void printTree(TaxonNode tx, int level) {
		String padd = new String(new char[level * 4]).replace('\0', ' ');
		System.out.format(padd + "%s %d\n", tx.getName(), tx.getCount());
		for (TaxonNode tx2: tx.getChildren())
			printTree(tx2, level + 1);
	}
}