
/**
 * @class
 *  
 * @classdesc 
 * This class produces a double linked list iterator for the 
 * Array oclotokens.  The initial starting point for the Linked List 
 * is set to be startID
 * <p>
 * Although you can directly instantiate the Iterator it is better 
 * to obtain it from {@link ocloInfo.getTokenLinkList}
 * 
 * @param {Array.Object} ocloTokens  ocloTokens is the Array which will 
 * be the foundation of the linked list produced. 
 * @param {Number} startID startID is the index of the linked list to use as
 * its initial position.  
 * 
 */
function ocloTokenLinkedListIterator(ocloTokens, startID) {
    var oTokens = ocloTokens;
    var currentID = -1;
    var lastID = ocloTokens.length - 1;

    if (startID != undefined) {
	if (startID <= lastID && startID >= 0) {
	    currentID = startID;

	}
    }

    this.hasNext = function() {
	return currentID < lastID;
    };

    this.hasPrevious = function() {
	return currentID > 0;
    };

    this.next = function() {

	if (this.hasNext()) {
	    currentID++;
	    return oTokens[currentID];
	}

	return null;
    };

    this.previous = function() {
	if (this.hasPrevious()) {
	    currentID--;
	    return oTokens[currentID];
	}
	return null;
    };

    this.get = function() {
	if (currentID > 0) {
	    return oTokens[currentID];
	}
	return ocloTokens[0];
    };

    this.getTokenAt = function(ID) {
	if (ID > 0 && ID <= lastID) {
	    return oTokens[ID];
	}
    };

    this.setCurrentID = function(ID) {
	if (ID <= lastID && ID >= 0) {
	    currentID = ID;
	}
    };

    this.getCurrentID = function() {
	return currentID;
    };

    this.getLastID = function(){
	return lastID;
    }; 
}

/**
 * @class  
 * 
 * @classdesc 
 * ocloInfo is a static class used to provide, print, and manipulate information 
 * related to a SMILE string.  It's primary object is an OcloObject, which is 
 * produced by ocloInfo.getOcloObject(smile,SmidgeTokens). 
 * <p>
 * getOcloObject transforms the SmidgeTokens into OcloTokens, which the other methods
 * in ocloInfo require.  It stores the smile, and additional information as part of the OcloObject
 * <p>
 * The OcloObject is used in spectroscopy to provide the functional groups in the submitted answer 
 * to the functionalgroup searcher.  It is not used by the other searchers, however, they could be changed 
 * to get thier information from the OcloObject instead of the chemicalInformation Class. 
 * <p>
 * It is recommended that any future searchers use the OcloObject to obtain information on submitted answers they may 
 * require. 
 * 
 */
function ocloInfo() {

}

/**
 *
 * @constant
 * @type {boolean} 
 * @description printCompleteBranch controls the printOcloObject method.  If set to true, in addition to the 
 * branch information, the entire OcloToken chain for the branch is printed. 
 * @default
 */
ocloInfo.printCompleteBranch = false;


/**
 * @constant
 * @type {Array.<string>}
 * @description smidgeTypes is an array of smidge token types, plus the ocloToken type, "oclo"; 
 * @default
 */
ocloInfo.smidgeTypes = [ "atom", "bond", "ring-closure", "open-branch", "close-branch", "oclo" ];


/**
 * @constant
 * @type {Object}
 * @description smidgeTokenMap maps Smidge and OcloToken names to their attributes. This information is used 
 * when printing the tokens.
 * 
 */
ocloInfo.smidgeTokenMap = {

    "atom" : [ "type", "symbol", "isotope", "aromatic", "chiralClass", "hydrogens", "charge", "klass" ],
    "bond" : [ "type", "order", "aromatic", "stereoDescriptor" ],
    "ring-closure" : [ "type", "index" ],
    "open-branch" : [ "type" ],
    "close-branch" : [ "type" ],
    "oclo" : [ "id", "type", "smidgeType", "symbol", "isotope", "aromatic", "chiralClass", "hydrogens", "charge", "klass", "order",
	    "stereoDescriptor", "index", "valance" ]
};

/**
 * @param token the OcloToken to be printed. 
 * @returns {String} HTML for the token. 
 */
ocloInfo.printToken = function(token) {

    var response = "";
    var type = token.type;
    var smidgeType = token.smidgeType;

    response += "&nbsp;&nbsp;&nbsp;&nbsp;{ <br>";

    if (ocloInfo.smidgeTokenMap[type] == null) {
	throw "ocloInfo.printToken: Token type '" + type + "' not recognized";
    }
    for (var i = 0; i < ocloInfo.smidgeTokenMap[type].length; i++) {
	var symbol = ocloInfo.smidgeTokenMap[type][i];
	if (ocloInfo.isSmidgeTypeMember(symbol, smidgeType)) {
	    response += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class ='smidgeColor'>" + symbol + ": " + token[symbol]
		    + "</span><br>";
	    continue;
	}
	// response += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + symbol + ": " + token[symbol] + "<br>";
    }

    response += "&nbsp;&nbsp;&nbsp;&nbsp;}<p>";
    return response;

};

/**
 * used to control what is printed in the token.  
 * @param type
 * @param smidgeType
 * @returns {Boolean}
 */
ocloInfo.isSmidgeTypeMember = function(type, smidgeType) {

    if (type == "smidgeType") {
	return true;
    }
    if (type == "type") {
	return false;
    }
    if (type == "id") {
	return true;
    }

    if (smidgeType == "atom") {
	if (type == "valance") {
	    return true;
	}
    }

    var result = false;
    var arr = ocloInfo.smidgeTokenMap[smidgeType];

    for (var i = 0; i < arr.length; i++) {
	var ele = arr[i];
	if (type == ele) {
	    return true;
	}
    }

    return result;
};

/**
 * @constant
 * @type {string}
 * @description JSON string for a smidgeToken.  
 * @example var token = JSON.parse(ocloInfo.smidgeToken);
 */
ocloInfo.smidgeToken = '{ "type" : "",\
    "symbol" : null,\
    "isotope" : 0, \
    "aromatic": false,\
    "chiralClass" : "", \
    "hydrogens" : 0,\
    "charge": 0,\
    "klass" : 0, \
    "index": 0,\
    "order": 0, \
    "stereoDescriptor": 0}';


/**
 * @constant
 * @type {string}
 * @description JSON string for an OCLO Token.  
 * @example var oToken = JSON.parse(ocloInfo.olcoToken);
 */
ocloInfo.ocloToken = '{ "id" : 0, "type" : "oclo",\
    "smidgeType": "", \
    "symbol" : "",\
    "isotope" : 0, \
    "aromatic": false,\
    "chiralClass" : "", \
    "hydrogens" : 0,\
    "charge": 0,\
    "klass" : 0, \
    "index": 0,\
    "order": 0, \
    "stereoDescriptor": 0, \
    "valance": 0 , \
    "isNitrogen":false,\
    "isOxygen":false,\
    "isCarbon":false,\
    "isSulphur":false,\
    "isPhosphorus":false,\
    "isHalogen": false}';


/**
 * Produces an ocloToken from a given SmidgeToken
 * @method
 * @param token a smidge token.
 * @returns ocloToken an instance of ocloInfo.ocloToken
 */
ocloInfo.getOcloToken = function(token) {

    var ocloToken = JSON.parse(ocloInfo.ocloToken);
    var type = token.type;
    ocloToken.smidgeType = type;

    for (var i = 1; i < ocloInfo.smidgeTokenMap[type].length; i++) {
	var symbol = ocloInfo.smidgeTokenMap[type][i];
	ocloToken[symbol] = token[symbol];
	if (ocloToken[symbol] == null) {
	    ocloToken[symbol] = 0;
	}
    }

    var vMap = chemicalInformation.getValenceMap();

    if (ocloToken.smidgeType == "atom") {
	ocloToken.valance = vMap[ocloToken.symbol];
	
	var symbol = ocloToken.symbol; 
	
	switch (symbol) {
	    case "C":
		ocloToken.isCarbon = true; 
		break;
	    case "N":
		ocloToken.isNitrogen = true; 
		break;
	    case "O":
		ocloToken.isOxygen = true; 
		break;
	    case "S":
		ocloToken.isSulphur = true; 
		break;
	    case "P":
		ocloToken.isPhosphorus = true; 
		break;
	    case "F":
	    case "Cl":
	    case "Br":
	    case "I":
		ocloToken.isHalogen = true; 
		break;	
	    default:
		break;
	}

    }
    // alert(ocloToken.type);
    return ocloToken;
};

/**
 * @constant
 * @type {string}
 * @description  The JSON string used to instantiate an ocloObject. 
 * The object can be used to answer questions about the SMILE that its
 * populated with.  See {@link ocloInfo.getOcloObject} for creation information. 
 * @example var ocloObject = JSON.parse(ocloInfo.oclo); 
 */
ocloInfo.oclo = '{"smile": "",\
    		    	"ocloTokens" : [], \
    			"dAtoms": [], \
    			"numDB": 0, \
    			"numTB": 0, \
    			"numBranches": {"num": 0, "indicies" : []}, \
    			"branches": [], \
    			"functionalGroups" : [],\
    			"molWeight": 0, \
    			"numHydrogens" : 0,\
    			"molFormula": "", \
    			"IHD" : 0,\
    			"numRings": 0,\
    			"containsNitrogen":false,\
    			"containsOxygen":false,\
    			"containsCarbon":false,\
    			"containsSulphur": false,\
    			"containsPhosphorus":false,\
    			"containsHalogen": false,\
    			"containsNonANitrogen":false,\
			"containsNonAOxygen":false,\
			"containsNonACarbon":false,\
			"containsNonASulphur": false,\
    			"containsNonAPhosphorus":false,\
    			"halogenIndicies":[],\
			"containsNonAHalogen": false }';

/**
 * @constant 
 * @type {string}
 * @description JSON for a distinct atom.  A distinct atom object encapsulates information about the 
 * occurances of the atom in the answer. 
 * @example var dAtom = JSON.parse(ocloInfo.dAtom); 
 */
ocloInfo.dAtom = '{"atom": "","numberOf" : 0, "indicies" : [], "nonAromaticIndices" :[]}';
/**
 * @constant 
 * @type {string}
 * @description JSON for a branch.  A branch object encapsulates information about the 
 * branch.  This includes its start and end Index in the OcloToken array formed from the answer and array 
 * containing only the branch ocloTokens.   
 * @example var branch = JSON.parse(ocloInfo.branch); 
 */
ocloInfo.branch = '{"startIndex": 0, "endIndex": 0, "smile": "", "ocloTokens": []}';

/**
 * getOcloObject is used to collect information about the submitted SMILE. The information is contained 
 * in an object defined by the JSON string at {@link ocloInfo.oclo}.  
 * <p>
 * The ocloObject can be used to provide information for all searchers. At the present time 
 * only the functionalGroups searcher uses an ocloObject.  In the future it is recommended that 
 * new searcher obtain information from the ocloObject. 
 * <p>
 * the ocloObject obtains its information from the chemicalInformation class. (as do the current searchers). 
 * 
 * @param smiles
 * @param tokens tokens is obtained from Smidge via Parser.parse(smiles). 
 * get OcloObject converts the smidge tokens into ocloInfo.ocloToken tokens.  
 * @returns oclo oclo is an instance of {@link ocloInfo.oclo}.
 */
ocloInfo.getOcloObject = function(smiles, tokens) {

    var oclo = JSON.parse(ocloInfo.oclo);
    var branch;

    try {

	// set the smile string
	oclo.smile = smiles;

	// create the oclo tokens.
	for (var i = 0; i < tokens.length; i++) {
	    var token = tokens[i];
	    var tmToken = ocloInfo.getOcloToken(token);
	    tmToken.id = i;
	    oclo.ocloTokens.push(tmToken);
	}

	//collect information about the answer
	oclo.numDB = chemicalInformation.countDoubleBonds(smiles);
	oclo.numTB = chemicalInformation.countTripleBonds(tokens);
	oclo.numRings = chemicalInformation.countRings(tokens);
	oclo.numBranches.num = chemicalInformation.countBranches(tokens);
	oclo.numBranches.indicies = ocloInfo.getBranchIndicies(oclo.ocloTokens);
	oclo.numHydrogens = chemicalInformation.countHydrogens(smiles, tokens);
	oclo.IHD = chemicalInformation.calculateIHD(smiles, tokens);
	oclo.molFormula = chemicalInformation.getMolFormula(smiles, tokens);
	oclo.molWeight = chemicalInformation.getMolecularWeight(smiles);

	for (var i = 0; i < oclo.numBranches.indicies.length; i++) {
	    branch = JSON.parse(ocloInfo.branch);
	    branch.startIndex = oclo.numBranches.indicies[i];
	    branch.endIndex = ocloInfo.getBranchCloseIndex(branch.startIndex, oclo.ocloTokens);
	    branch.ocloTokens = oclo.ocloTokens.slice(branch.startIndex, branch.endIndex + 1);

	    oclo.branches.push(branch);
	}

	var tmp = chemicalInformation.getDistinctAtoms(tokens);
	for (var i = 0; i < tmp.length; i++) {
	    var atom = tmp[i];
	    var dAtom = JSON.parse(ocloInfo.dAtom);
	    dAtom.atom = atom;
	    dAtom.numberOf = chemicalInformation.countAtomsOf(atom, tokens);
	    dAtom.indicies = chemicalInformation.getIndiciesOf(atom, oclo.ocloTokens);
	    dAtom.nonAromaticIndices = chemicalInformation.getNonAromaticIndiciesOf(atom, oclo.ocloTokens);
	    oclo.dAtoms.push(dAtom);

	    switch (atom) {
		case "C":
		    oclo.containsCarbon= true; 
		    if(dAtom.nonAromaticIndices.length >0){
			oclo.containsNonACarbon = true; 
		    }
		    break;
		case "N":
		    oclo.containsNitrogen = true; 
		    if(dAtom.nonAromaticIndices.length >0){
			oclo.containsNonANitrogen = true; 
		    }
		    break;
		case "O":
		    oclo.containsOxygen = true; 
		    if(dAtom.nonAromaticIndices.length >0){
			oclo.containsNonAOxygen = true; 
		    }
		    break;
		case "S":
		    oclo.containsSulphur = true; 
		    if(dAtom.nonAromaticIndices.length >0){
			oclo.containsNonASulphur = true; 
		    }
		    break;
		case "P":
		    oclo.containsPhosphorus = true; 
		    oclo.containsNonAPhosphorus = true;  
		    break;
		case "F":
		case "Cl":
		case "Br":
		case "I":
		    oclo.containsHalogen = true; 
		    oclo.containsNonAHalogen = true; 
		    oclo.halogenIndicies = oclo.halogenIndicies.concat(dAtom.nonAromaticIndices); 
		    break;	
		default:
		    break;
	    }


	}

	//functional groups
	var tmp =  ocloInfo.getFunctionalGroups(oclo); 
	
	//if the answer contains both ester and  ether. 
	//then we need to normalize them 
	if(tmp.contains("Ester")){
	    tmp = ocloInfo.normalizeEthers(tmp); 
	}
	
	//if the answer contains both ester and anhydride
	//then we need to normalize them.
	if(tmp.contains("Anhydride")){    
	    tmp = ocloInfo.normalizeEsters(tmp); 
	}
	
	//will normalize Carboxy and alcohol 
	if(tmp.contains("Carboxylic Acid")){
	    tmp = ocloInfo.nomralizeAlcohol(tmp);
	}
	
	//will remove duplicates as I store them in the 
	//ocloObject
	var temp = new Array(); 
	
	for (var i = 0; i < tmp.length; i++) {
	    var element = tmp[i];
	    if(temp[element.toString()] != "X"){
		oclo.functionalGroups.push(element.toLowerCase());
		temp[element.toString()] = "X";
	    }
	    
	}
		
    } catch (e) {
	alert(e);
    }

    return oclo;
};



/**
 * Removes 1 alcohol for each Carboxylic Acid found 
 * 
 * @param arr
 */
ocloInfo.nomralizeAlcohol = function(arr){
   
    var result = new Array(); 
    //count acids 
    var acidCount = 0; 
    for (var i = 0; i < arr.length; i++) {
	var ele = arr[i];
	if(ele == "Carboxylic Acid"){
	    acidCount++; 
	}
    }
    
    
    for (var i = 0; i < arr.length; i++) {
	var ele = arr[i];
	if(ele == "Alcohol"){
	    if(acidCount == 0){
		result.push(ele); 
	    }else{
		acidCount--; 
	    }
	    
	}else{
	    
	    result.push(ele);
	    
	}
	
    }
    
    return result; 
    
}; 

/**
 * Removes Esters from the array. 
 * is called getOcloObject(..)
 * @param arr
 */
ocloInfo.normalizeEsters = function(arr){
    
    var result = new Array(); 
    for (var i = 0; i < arr.length; i++) {
	var ele = arr[i];
	if(ele != "Ester"){
	    result.push(ele); 
	}
	
    }
    
    return result; 
    
}; 


/**
 * removes duplicate Ethers form the array 
 * called from getOcloObject
 */
ocloInfo.normalizeEthers = function(arr){
    
    //we are going to remove 1 ether for each ester found 
    //in the array. 
    var result = new Array(); 
    //count esters 
    var esterCount = 0; 
    for (var i = 0; i < arr.length; i++) {
	var ele = arr[i];
	if(ele == "Ester"){
	    esterCount++; 
	}
    }
    
    
    for (var i = 0; i < arr.length; i++) {
	var ele = arr[i];
	if(ele == "Ether"){
	    if(esterCount == 0){
		result.push(ele); 
	    }else{
		esterCount--; 
	    }
	    
	}else{
	    result.push(ele); 
	}
	
    }
    
    return result; 
    
};




/**
 * printOcloObject creates and returns an HTML string of the values in the oclo object.
 * 
 * @param {Object}
 *                oclo oclo is obtained by calling ocloInfo.getOcloObject(smile, tokens);
 * @returns {String} result result is the HTML string
 */
ocloInfo.printOcloObject = function(oclo) {

    var response = "<b>All Tokens from SMILE</b><br>";
    var prefix = "";

    for (var i = 0; i < oclo.ocloTokens.length; i++) {
	var ocloToken = oclo.ocloTokens[i];
	response += ocloInfo.printToken(ocloToken);
    }

    prefix += "<b>General Information: </b><br>";
    prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Smile: " + oclo.smile + "<br>";
    prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Molecular Formula: " + oclo.molFormula + "<br>";
    prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Molecular Weight: " + oclo.molWeight + "<br>";
    prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Number of Double Bonds: " + oclo.numDB + "<br>";
    prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Number of Triple Bonds: " + oclo.numTB + "<br>";
    prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Number of Rings: " + oclo.numRings + "<br>";
    prefix += "<b>Functional Groups:</b> " + oclo.functionalGroups + "<br>";
    
    prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Contains Carbon: " + oclo.containsCarbon + "&nbsp;&nbsp;&nbspNon Aromatic: " + oclo.containsNonACarbon + "<br>";
    prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Contains Nitrogen: " + oclo.containsNitrogen +  "&nbsp;&nbsp;&nbspNon Aromatic: " + oclo.containsNonANitrogen + "<br>";
    prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Contains Oxygen: " + oclo.containsOxygen +  "&nbsp;&nbsp;&nbspNon Aromatic: " + oclo.containsNonAOxygen + "<br>";
    prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Contains Halogen: " + oclo.containsHalogen +  "&nbsp;&nbsp;&nbspNon Aromatic: " + oclo.containsNonAHalogen + "<br>";
    prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Contains Sulphur: " + oclo.containsSulphur + "&nbsp;&nbsp;&nbspNon Aromatic: " + oclo.containsNonASulphur + "<br>";
    prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Contains Phosphorus : " + oclo.containsPhosphorus + "&nbsp;&nbsp;&nbspNon Aromatic: " + oclo.containsNonAPhosphorus + "<br>";

    prefix += "<b>Distinct Atom Information:</b> <br>";
    prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;H " + " occurs " + oclo.numHydrogens + " times, IHD = " + oclo.IHD + "<br>";

    for (var i = 0; i < oclo.dAtoms.length; i++) {
	var dAtom = oclo.dAtoms[i];
	prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + dAtom.atom + " occurs " + dAtom.numberOf + " times at indices "
		+ dAtom.indicies.toString() + "<br>";
	prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; non Aromatic Indicies at "
		+ dAtom.nonAromaticIndices.toString() + "<br>";
    }
    
    if(oclo.halogenIndicies.length > 0 ){
	prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Halogens occur " + oclo.halogenIndicies.length + " times at indices "
	+ oclo.halogenIndicies.toString() + "<br>";

    }
    prefix += "<b>Number of Branches: </b>" + oclo.numBranches.num + "<br>";

    for (var i = 0; i < oclo.branches.length; i++) {
	var branch = oclo.branches[i];
	var index = i+1; 
	prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Branch " + index + ": " +  "startIndex=" + branch.startIndex + ", endIndex= " + branch.endIndex
		+ "<br>";
    }

    if (ocloInfo.printCompleteBranch) {
	for (var i = 0; i < oclo.branches.length; i++) {
	    var branch = oclo.branches[i];
	    var index = i + 1; 
	    prefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class='smidgeBranch'>Branch " + index + ": startIndex=" + branch.startIndex
		    + ", endIndex= " + branch.endIndex + "</span><br>";

	    for (var j = 0; j < branch.ocloTokens.length; j++) {
		var token = branch.ocloTokens[j];
		prefix += ocloInfo.printToken(token);
	    }
	}
    }

    response = prefix + "<br>" + response;
    return response;

};

/**
 * Provides an iterator for the ocloTokens found in an oclo Object. The iterator 
 * is an instance of {@link ocloTokenLinkedListIterator}
 * 
 * @param {Object}
 *                oclo oclo is the object returned by ocloInfo.getOcloObject(smiles,tokens) the object can be printed via
 *                ocloInfo.printOcloObject(oclo)
 * 
 * @param startID
 *                startID is the starting position in the linked list if it is present and valid
 * 
 */
ocloInfo.getTokenLinkList = function(oclo, startID) {

    return new ocloTokenLinkedListIterator(oclo.ocloTokens, startID);

};

/**
 * This function and its supporting functions are based on the information from Dr. Hunt on how to find functional groups.
 * 
 * This method is called by {@link ocloInfo#getOcloObject}
 * 
 * @param {Object}
 *                oclo oclo is the object returned by ocloInfo.getOcloObject(smiles,tokens) the object can be printed via
 *                ocloInfo.printOcloObject(oclo)
 * 
 * @returns {Array.String} result result - an array of the functional groups found
 */
ocloInfo.getFunctionalGroups = function(oclo) {

    try {
	var result = new Array();

	if (oclo.containsCarbon == false) {
	    throw config.NO_CARBONS;
	}
	// the smidge tokens flag aromatics so we can
	// take care of that one off the bat.
	if (ocloInfo.checkforAromatic(oclo.ocloTokens)) {
	    result.push("Aromatic");
	    result.push("Arene");
	}
	//var cAtom = ocloInfo.getDatomOf("C", oclo.dAtoms);
	// No chars [atoms, branches, rings, etc] other than C
	// if (cAtom.numberOf == oclo.smile.length) {
	// so we're looking for an alkane, ignroing C's in aromatics (small c's )
	if (/CC/.test(oclo.smile)) {
	    result.push("Alkane");
	}
	// nothing else at all is in the answer so we
	//return result;
	//}

	// were looking for an alkene or alkyne, ignoring C's in aromatics (small c's)
	if (/C=C/.test(oclo.smile) || /C\d*=C/.test(oclo.smile)) {
	    result.push("Alkene");
	}
	if (/C#C/.test(oclo.smile) ||/C\d*#C/.test(oclo.smile) ) {
	    result.push("Alkyne");
	}

	// at this point we need to know if there are any 'non-aromatic' (uppercase) atoms other than C
	// we know that there are more atoms than C present.

	var nonAromatics = new Array();
	for (var i = 0; i < oclo.dAtoms.length; i++) {
	    var dAtom = oclo.dAtoms[i];
	    if (dAtom.nonAromaticIndices.length > 0) {
		nonAromatics[dAtom.atom] = dAtom.nonAromaticIndices;
	    }
	}

	var naKeys = Object.keys(nonAromatics);
	if (naKeys.length == 1 && naKeys.contains("C")) {
	    // There are no non-aromatics other than C so we,
	    return result;
	}

	// we know that there are non-aromatic atoms in the answer,
	// but we don't know if C is one of them.

	// It is at this point
	// we could return if C is'nt one of them, per Dr. Sutherlands (re phenol alcohol is an alcohol) answer...
	// as it stands we'll continue unless Dr. Hunt wants us to stop per his state diagram page 1.
	// Check for non-aromatic C's here if needed.

	if (Object.keys(nonAromatics).contains("O")) {
	    result = result.concat(ocloInfo.getOxygenFunctionalGroups(oclo, nonAromatics));
	} 

	if (Object.keys(nonAromatics).contains("N")) {
	    result = result.concat(ocloInfo.getNitrogenFunctionalGroups(oclo, nonAromatics));
	}

	if (ocloInfo.checkForHalogens(Object.keys(nonAromatics))) {
	    result = result.concat(ocloInfo.getHalogenFunctionalGroups(oclo, nonAromatics));
	}


	if (config.ALERT_FG_FINDS) {
	    alert("Functional Groups found so far: " + result);
	}

    } catch (e) {
	throw "GetFunctionalGroups: " + e; 
    }
    return result;
};

/**
 * Helper function for getFunctionalGroups
 * 
 * @param {oclo}
 *                oclo
 * @param {Array}
 *                nonAromatics
 * @returns {Array.String} result
 */
ocloInfo.getOxygenFunctionalGroups = function(oclo, nonAromatics) {

    var result = new Array();
    var numOxygens = nonAromatics["O"].length;

    // we're interested in 1, 2 or 3 O's any more and we're done
    // we're going to do it in a not so elegant way :(   This is a result of 
    //the method defined to find groups being exclusive (ie. 1 XOR 2 XOR 3 oxygens)
    //while the groups themselves are not really exclusive.  The results of 
    //all these calls are/can be normalized with the calls 'normalizeXXX' above.
    switch (numOxygens) {
	case 1:
	    result = ocloInfo.fg.dealWithOneOxy(oclo, nonAromatics["O"][0]);
	    break;
	case 2:
	    result = ocloInfo.fg.dealWithOneOxy(oclo, nonAromatics["O"][0]);
	    result = result.concat(ocloInfo.fg.dealWithOneOxy(oclo, nonAromatics["O"][1]));
	    result = result.concat(ocloInfo.fg.dealWithTwoOxy(oclo, nonAromatics["O"][0],nonAromatics["O"][1] ));
	    break;
	case 3:
	    result = ocloInfo.fg.dealWithOneOxy(oclo, nonAromatics["O"][0]);
	    result = result.concat(ocloInfo.fg.dealWithOneOxy(oclo, nonAromatics["O"][1]));
	    result = result.concat(ocloInfo.fg.dealWithOneOxy(oclo, nonAromatics["O"][2]));
	    result = result.concat(ocloInfo.fg.dealWithTwoOxy(oclo, nonAromatics["O"][0],nonAromatics["O"][1] ));
	    result = result.concat(ocloInfo.fg.dealWithTwoOxy(oclo, nonAromatics["O"][0],nonAromatics["O"][2] ));
	    result = result.concat(ocloInfo.fg.dealWithTwoOxy(oclo, nonAromatics["O"][1],nonAromatics["O"][2] ));
	    result = result.concat(ocloInfo.fg.dealWithThreeOxy(oclo, nonAromatics));
	    break;
	case 4:
	    // result = ocloInfo.fg.dealWithFourOxy(oclo, nonAromatics);
	    break;
	default:
	    break;
    }

    return result;

};

/**
 * @class
 * 
 * @classdesc  A static helper class specifically designed to deal with getting Oxygen based functional groups. 
 */
ocloInfo.fg = function() {

};


/**
 * this method deals with the case of 3 non-aromatic Oxygens  
 * present in the molecule.  This is a case from Dr. Hunts diagram. 
 * 
 * In this case we are looking for an Anhydride.  Finding one 
 * is similar to finding an Ester with a double bonded O added 
 * to the C.  So, we're going to follow the same path we 
 * did for ester and then check for the double bonded O. 
 * 
 * We'll also use the fact that they must be in order 
 * in the smile..  first is DB, second is SB, third is DB. 
 * 
 * 
 */
ocloInfo.fg.dealWithThreeOxy = function(oclo, nonAromatics) {
    
    var result = new Array();
    var tmpToken; 
    var llfirstO = ocloInfo.getTokenLinkList(oclo, nonAromatics["O"][0]);
    var llsecondO = ocloInfo.getTokenLinkList(oclo, nonAromatics["O"][1]);
    var llthirdO = ocloInfo.getTokenLinkList(oclo, nonAromatics["O"][2]);
    var firstO = llfirstO.get(); 
    var secondO = llsecondO.get(); 
    var thirdO = llthirdO.get(); 
    var doubleBondTypeFirstO = ocloInfo.getDoubleBondType(oclo, firstO.id); 
    var doubleBondTypeSecondO = ocloInfo.getDoubleBondType(oclo, secondO.id); 
    var doubleBondTypeThirdO = ocloInfo.getDoubleBondType(oclo, thirdO.id); 
    
    if(doubleBondTypeFirstO == "backward" || doubleBondTypeFirstO == "forward"){
	if(doubleBondTypeSecondO != "none"){
	    return result; 
	}
	 if(doubleBondTypeThirdO != "backward" && doubleBondTypeThirdO != "forward"){
	     return result; 
	 }
	
    }
    
    if(firstO.charge != 0 || secondO.charge != 0 || thirdO.charge != 0){
	//we're done so we, 
	return result; 
    }
    
    
    if(doubleBondTypeFirstO == "backward"){
	//first has double bond to something. 
	tmpToken = ocloInfo.getAtomBefore(oclo, firstO.id);
	if(tmpToken == null || !tmpToken.isCarbon  || tmpToken.charge != 0){
	    //for some reason there wasn't anything bonded to it
	    //or its not Anhydride
	    return result; 
	}

	//we must have pattern.. c(=0)OC for the first 2 O's 
	//we're looking for the O that follows placement 1
	
	tmpToken = ocloInfo.getAtomAfter(oclo, firstO.id + 1);
	if(tmpToken == null || !tmpToken.isOxygen){
	    //its not Anhydride so we 
	    return result; 
	}
	//what's after the O determines our answer 
	tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id); 
	if(tmpToken == null || !tmpToken.isCarbon){
	    //its not Anhydride so we 
	    return result; 
	}
	//at this point we have an Ester now we need
	//to see if we have Anhydride.
	//we're going to see if this carbon is 
	//the same carbon bonded to the thirdO. 
	var carbonID = tmpToken.id; 
	tmpToken = ocloInfo.getAtomBefore(oclo, thirdO.id); 
	if(tmpToken.id != carbonID){
	    //its not the same C so we
	    return result; 
	}
	result.push("Anhydride"); 
	
    }
    
    //there are 2 special cases paterns where we'll get a forward 
    //double bond on the first O.  both involve attaching a
    //ring on both ends of the Anhydride
    if (doubleBondTypeFirstO == "forward") {
	//cases are O=C(OC(=O)C1CCCC1)C2CCCC2
	//and O=C(CCCC1CCC1)OC(=O)C2CCCC2
	tmpToken = ocloInfo.getAtomAfter(oclo, firstO.id); 
	if(tmpToken == null || !tmpToken.isCarbon || tmpToken.charge != 0){
	    //its not anhydride so we, 
	    return result; 
	}
	//the next token needs to be a C or O 
	tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id); 
	if(!(tmpToken.isCarbon || tmpToken.isOxygen)){
	    //its neither so we, 
	    return result; 
	}
	//O is easier, we'll do it first. 
	if(tmpToken.id == secondO.id){//its the right oxygen
	    tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id); 
	    if(!tmpToken.isCarbon){
		return result; 
	    }
	    var carbonID = tmpToken.id; 
	    tmpToken = ocloInfo.getAtomBefore(oclo, thirdO.id); 
	    //if its the same C we're good 
	    if(tmpToken.id == carbonID){
		result.push("Anhydride"); 
	    }
	    
	}
	
	//the second case O=C(CCCC1CCC1)OC(=O)C2CCCC2
	//we're on the C at (C...
	if(tmpToken.isCarbon){
	    //we want the atom after the branch to be our second O 
	    tmpToken = ocloInfo.getAtomAfter(oclo, oclo.branches[0].endIndex);
	    if(tmpToken.id != secondO.id){
		return result; 
	    }
	   
	    //next up is the C 
	    tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id); 
	  
	    var carbonID = tmpToken.id;  
	    tmpToken = ocloInfo.getAtomBefore(oclo, thirdO.id); 
	    //if its the same C we're good 
	    if(tmpToken.id == carbonID){
		result.push("Anhydride"); 
	    }
	    
	}
	
    }
    
    return result; 
    
};

/**
 * this method deals with the case of only 2 non-aromatic Oxygens  
 * present in the molecule.  This is a case from Dr. Hunts diagram. 
 * 
 */
ocloInfo.fg.dealWithTwoOxy = function(oclo, index1, index2) {
    //we're looking for Nitro, Carboxylic Acid, or Ester
    //in all cases there must be 1 double bonded(smile contains =0 or =O), non 
    //charged Oxygen and only 1.  
    
    var result = new Array();
    var tmpToken; 
    var llfirstO = ocloInfo.getTokenLinkList(oclo, index1);
    var llsecondO = ocloInfo.getTokenLinkList(oclo, index2);
    var firstO = llfirstO.get(); 
    var secondO = llsecondO.get(); 
    var doubleBondTypeFirstO = ocloInfo.getDoubleBondType(oclo, firstO.id); 
    var doubleBondTypeSecondO = ocloInfo.getDoubleBondType(oclo, secondO.id); 
  
    if(firstO.charge != 0 || secondO.charge != 0){
	//we're done so we, 
	return result; 
    }
    
    //if we have only COC=O that's not enough. 
    if(oclo.ocloTokens.length <= 5){

	return result; 
	
    }
   
    
    if(doubleBondTypeFirstO == "backward" || doubleBondTypeFirstO == "forward"){
	if(doubleBondTypeSecondO != "none"){
	    return result; 
	}
	
    }
    if(doubleBondTypeSecondO == "backward" || doubleBondTypeSecondO == "forward"){
	if(doubleBondTypeFirstO != "none"){
	    return result; 
	}
	//for simplicity we're going to make the first 
	//one the double bond in all cases. 
	tmpToken = firstO; 
	firstO = secondO; 
	secondO = tmpToken;
	llfirstO.setCurrentID(firstO.id); 
	llsecondO.setCurrentID(secondO.id); 
	doubleBondTypeFirstO = doubleBondTypeSecondO; 
	
	doubleBondTypeSecondO = "none";
	
    }
    
   
    if (doubleBondTypeFirstO == "backward") {
	//first has double bond to something. 
	tmpToken = ocloInfo.getAtomBefore(oclo, firstO.id);
	if(tmpToken == null){
	    //for some reason there wasn't anything bonded to it
	    return result; 
	}

	if(!tmpToken.isCarbon && !tmpToken.isNitrogen){
	    //we're done
	    return result; 
	}

	if(tmpToken.isNitrogen){
	    //we're looking for Nitro
	    if(tmpToken.charge != 1){
		return result; 
	    }
	    //the first Atom after the Nitro will be the =O
	    //because of the nature of Nitro. we want the one after that.
	    var index = firstO.id +1; 
	    tmpToken = ocloInfo.getAtomAfter(oclo, index); 
	    if(tmpToken == null || !tmpToken.isOxygen){
		//its not nitro so we, 
		return result; 
	    }
	    //nothing can follow this O
	    tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id); 
	    if(tmpToken == null){
		//its nitro 
		result.push("Nitro");
		return result; 
	    }
	    return result; 
	}
	//at this point it's backward  c=O
	//and we're looking for Carboxylic Acid or Ester
	
	if(tmpToken.symbol != "C" || tmpToken.charge != 0){
	    //it's neither so we, 
	    return result; 
	}
	//we can have 2 cases patterns for Ester.. c(=0)OC or 
	//COC(=O)CC, based on the relation of the two O placements. 
	if(firstO.id < secondO.id){
	    //it's C(=O)OC, we're looking for the O that follows placement 
	    tmpToken = ocloInfo.getAtomAfter(oclo, firstO.id + 1);
	    if(tmpToken == null || !tmpToken.isOxygen ||tmpToken.id != secondO.id){
		//its neither so we, 
		return result; 
	    }
	    //what's after the O determines our answer 
	    tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id); 
	    if(tmpToken == null){
		result.push("Carboxylic Acid"); 
		return result; 
	    }
	    if(tmpToken.isCarbon){
		//alert("DB Oxy 1st: " + firstO.id + "  2nd; " + secondO.id);
		result.push("Ester"); 
	    }
	}else{
	    // we're looking for COC(=O)CXX  or COC(C)=O 
	    	tmpToken = ocloInfo.getAtomBefore(oclo, secondO.id);
		if(tmpToken == null || !tmpToken.isCarbon || tmpToken.charge !=0){
		    return result; 
		}
		tmpToken = ocloInfo.getAtomAfter(oclo, secondO.id);
		if(tmpToken == null || !tmpToken.isCarbon || tmpToken.charge !=0){
		    return result; 
		}
	    
	    if(llfirstO.hasNext()){
		//tmpToken now sits on the C before (=O)C.  
		//the next one should be the firstO
		tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id); 
		if(tmpToken == null || tmpToken.id != firstO.id ){
		    //we don't have ester here so we 
		    return result; 
		}
		//get the atom after the close of branch 
		tmpToken = ocloInfo.getAtomAfter(oclo, firstO.id +1); 
		if(tmpToken != null && tmpToken.isCarbon && tmpToken.charge == 0){
		    //we have ester 
		    result.push("Ester"); 
		}
		
	    }else{
		//tmpToken now sits on the C before (C)=O.  if it's the same C
		//as is what is before =O, then its an ester. 
		var tmpID = tmpToken.id; 
		tmpToken = ocloInfo.getAtomBefore(oclo, firstO.id); 
		if(tmpToken != null && tmpToken.id == tmpID){
		    //and the C just before =O is in a branch
//		    tmpToken = llsecondO.getTokenAt(secondO.id -2)
//		    if(tmpToken.smidgeType == "close-branch"){
			result.push("Ester");
		   // } 
		}
		return result; 
		
	    }
	    
	}
	
	
	return result; 
    }
    
    //at this point its a fowrad double bond O=[N+](O)XXXXXXX
    //or O=C(OXXX)CXXX or O=C(O)XXXXX
   
    if (doubleBondTypeFirstO == "forward") {
	 	
	tmpToken = ocloInfo.getAtomAfter(oclo, firstO.id);
	if(!tmpToken.isNitrogen && !tmpToken.isCarbon){
	    //its neither case so we, 
	    return result; 
	}
	//remember the index of the C or N for  later use. 
	var index = tmpToken.id; 

	tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id);
	
	if(tmpToken == null || !tmpToken.isOxygen){
	    //its not Nitro, ester or Carboxylic Acid so we
	    //there is a special case Ester here
	    // where the ester is attached to a ring, or ring plus akanes to 
	    // more rings.  
	    tmpToken = llfirstO.getTokenAt(index); 
	    if(tmpToken.isCarbon){
		tmpToken = llfirstO.getTokenAt(llfirstO.getLastID() -1); 
		if(tmpToken.isOxygen){
		    result.push("Ester"); 
		}
	    }
	    //alert("Here: " + tmpToken.smidgeType  + " Id: " + tmpToken.id);
	    return result; 
	}
	// get the C or N again. 
	tmpToken = ocloInfo.getTokenLinkList(oclo, index).get(); 
	
	if(tmpToken.isNitrogen){
	    tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id); 
	    if(tmpToken != null){
		tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id);
		//should be null because its the (O), end of branch 
		if(tmpToken == null){
		    result.push("Nitro"); 
		    return result; 
		}

	    }
	    //we're done so we, 
	    return result; 
	}
	//alert("Hi");
	//at this point it's a carbon.  
	 tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id); 
	 if(tmpToken != null){
	     tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id);
	     //if its null its the (O), end of branch 
	     //alert(tmpToken.id);
	     if(tmpToken == null){
		 result.push("Carboxylic Acid"); 

	     }else{
		 //if the next one is a O, then ether
		 //otherwise eSter. 
		 tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id);
		 if(tmpToken.isCarbon){
		     result.push("Ester"); 
		 }else{
		     if(tmpToken.id == secondO.id){
			 result.push("Ester");
		     }else{
			 result.push("Ether"); 
		     }
		 }
	     }

	 }
	
    }
    
    return result; 

};



/**
 * this method deals with the case of only 1 non-aromatic Oxygen is 
 * present in the molecule.  This is a case from Dr. Hunts diagram. 
 */
ocloInfo.fg.dealWithOneOxy = function(oclo, index) {

    var result = new Array();
    var ll = ocloInfo.getTokenLinkList(oclo, index);
    var oToken = ll.get();
    var doubleBondType = ocloInfo.getDoubleBondType(oclo, oToken.id);
    var lhs = null; 
    var rhs = null; 
    
   // alert(oToken.id); 
    if (oToken.charge != 0) {
	// if the O is charged we don't have C=O, CO, COC so we,
	return result;
    }

    // we're looking for an Ether or an Alcohol
    if (doubleBondType == "none") {

	// nothing before
	tmpToken = ll.previous();
	if (tmpToken == null) {
	    // we're looking for an alcohol
	    if (ocloInfo.isAtomAfter(oclo, oToken.id, "C")) {
		result.push("Alcohol");
	    }
	    // there's nothing before and we've dealt with what's after so we,
	    return result;
	}

	// put the ll back on the O
	ll.setCurrentID(oToken.id);

	// nothing after
	tmpToken = ll.next();
	if (tmpToken == null) {
	    // we're looking for an alcohol
	    if (ocloInfo.isAtomBefore(oclo, oToken.id, "C")) {
		result.push("Alcohol");
	    }
	    // there's nothing after and we've dealt with what's before so we,
	    return result;
	}

	// put the ll back on the O
	ll.setCurrentID(oToken.id);

	// at this point we know there is something on both sides of the O so
	// if they are both C then its Ether and if there is a C before and closed branch
	// after the O, its an alcohol.
	if (ocloInfo.isAtomBefore(oclo, oToken.id, "C")) {
	    if (ocloInfo.isAtomAfter(oclo, oToken.id, "C")) {
		//alert("Either Oxy: " + oToken.id);
		result.push("Ether");
	    }
	    if (ll.next().smidgeType == "close-branch") {
		result.push("Alcohol");
	    }
	}

    }

    // we're looking for an Aldehyde,Ketone,Amide or Acyl Halide
    if (doubleBondType == "backward") {
	
	// move ll to the double bond
	var tmpToken = ll.previous();
	var cIndex = 0; 

	if(oclo.ocloTokens.length <= 3){
	    //then at best we have C=O only so we, 
	    return result; 
	}
	
	tmpToken = ocloInfo.getAtomBefore(oclo, tmpToken.id);	
	if(tmpToken == null){
	    //for some reason there isn't an atom  before
	    //the double bond so we, 
	    return result; 
	}
	
	if(tmpToken.isCarbon == false || tmpToken.charge > 0){
	    // we don't have C=O so we, 
	    return result; 
	}
	
	//at this point we know we have C=O and we know that 
	//something is attached to one or both sides of the C.
	
	//set the Index of the C for later use
	cIndex = tmpToken.id; 
	
	//we want to know what's before the C, could be null
	lhs = ocloInfo.getAtomBefore(oclo, tmpToken.id);
	
	//move back to the C of C=O
	// we want to know what is after the C this time. 
	ll.setCurrentID(cIndex); 
	tmpToken = ll.get(); 
	
	rhs = ocloInfo.getAtomAfter(oclo, tmpToken.id);
	if(rhs != null){
	    
	    if(rhs.isOxygen){
		//we're back on the O of C(=O)X and want the atom after it instead
		ll.setCurrentID(rhs.id);
		if(ll.hasNext()){
		    rhs = ocloInfo.getAtomAfter(oclo, ll.next().id);
		    //alert(rhs.symbol);
		}else{
		    //it has no next token so it's 
		    rhs = null; 
		    // alert("Oxy1 RHS of C is NULL");
		}
	    }
	}
	
	//at this point we know what's on both sides of the C in C=O
	//we also know that if one of them is NULL, it'll be the RHS
	if(rhs == null){
	    if(lhs.isCarbon){
		//alert(lhs.id);
		tmpToken = ll.getTokenAt(cIndex + 1);
		//if it is a ring we have ketone, otherwise we have ald. 
		if(tmpToken.smidgeType == "ring-closure"){
		    result.push("Ketone");
		}else{
		    result.push("Aldehyde");
		}
		
		
	    } 
	    return result; 
	}
	
	//something is on both sides. 
	if(rhs.isCarbon){
	    if(lhs.isCarbon){
		//if we have (C=O) its aldehyde
		//otherwise its Ketone
		if(oToken.id -2 == cIndex){
		    result.push("Aldehyde"); 
		}else{
		    result.push("Ketone"); 
		}
	    }
	    if(lhs.isNitrogen){
		result.push("Amide");
	    }
	    if(lhs.isHalogen){
		result.push("Acyl Halide");
	    } 

	    return result;
	}
	if(lhs.isCarbon){
	    if(rhs.isNitrogen){
		result.push("Amide");
	    }
	    if(rhs.isHalogen){
		result.push("Acyl Halide");
	    } 

	    return result;
	}
		

    }

    if (doubleBondType == "forward") {
	// 1 special case occurs when Aldehyde is 
	// conneced to a ring structure.  ex:  question 8 spectroscopy. 

	if(/^O=CC/i.test(oclo.smile)){

	    result.push("Aldehyde");
	    return result;
	}

	//  special case for ketone when ketone is formed using 
	//carbons from nonaromatic ring. ex: question 34 spectroscopy

	if(/^O=C\d*C[^O]*\d+$/.test(oclo.smile)){
	    // alert("Ketone");
	    result.push("Ketone");
	}
	if(/^O=C\d+CC[^O]*\d+$/.test(oclo.smile)){
	    // alert("Ketone");
	    result.push("Ketone");
	}
	
	//same as first above but for 2 rings directly attached
	if(/^O=C\(C\d+CC[^O]*\d+$/.test(oclo.smile)){
	    // alert("Ketone");
	    result.push("Ketone");
	}
    }

    if (doubleBondType == "both") {/*we don't have C=O*/ }

    //alert("KetoneHere"); 
    return result;
};


/**
 * Determines if there is a triple bond on one or both sides of the element at index of the linked list gotten from
 * ocloInfo.getTokenLinkList(oclo, index)
 * 
 * @param {oclo}
 *                oclo oclo is an ocloObject
 * @param {Number}
 *                index
 * 
 * @returns {String} result result is one of 'forward', 'backward','both' or 'none'
 * 
 */
ocloInfo.getTripleBondType = function(oclo, index) {

    var ll = ocloInfo.getTokenLinkList(oclo, index);
    var result = "none";
    var tmpToken = null;

    try {
	if (ll.hasPrevious()) {
	    tmpToken = ll.previous();
	    if (tmpToken.order == 3) {
		// its a double bond token.
		result = "backward";
	    }
	}
	// put the linked list back to its starting point.
	ll.setCurrentID(index);

	if (ll.hasNext()) {
	    tmpToken = ll.next();
	    if (tmpToken.order == 3) {
		// its a double bond token.
		if (result == "backward") {
		    result = "both";
		} else {
		    result = "forward";
		}

	    }
	}
    } catch (e) {
	alert("ocloInfo.getDoubleTripleType Error: " + e);
    }

    // alert(result);
    return result;

};


/**
 * Determines if there is a double bond on one or both sides of the element at index of the linked list gotten from
 * ocloInfo.getTokenLinkList(oclo, index)
 * 
 * @param {oclo}
 *                oclo oclo is an ocloObject
 * @param {Number}
 *                index
 * 
 * @returns {String} result result is one of 'forward', 'backward','both' or 'none'
 * 
 */
ocloInfo.getDoubleBondType = function(oclo, index) {

    var ll = ocloInfo.getTokenLinkList(oclo, index);
    var result = "none";
    var tmpToken = null;

    try {
	if (ll.hasPrevious()) {
	    tmpToken = ll.previous();
	    if (tmpToken.order == 2) {
		// its a double bond token.
		result = "backward";
	    }
	}
	// put the linked list back to its starting point.
	ll.setCurrentID(index);

	if (ll.hasNext()) {
	    tmpToken = ll.next();
	    if (tmpToken.order == 2) {
		// its a double bond token.
		if (result == "backward") {
		    result = "both";
		} else {
		    result = "forward";
		}

	    }
	}
    } catch (e) {
	alert("ocloInfo.getDoubleBondType Error: " + e);
    }

    // alert(result);
    return result;

};

/**
 * primarily a helper function for getOxygenFunctionalGroups
 * @deprecated
 * @see {@link ocloInfo.getAtomAfter}
 */
ocloInfo.isAtomAfter = function(oclo, index, kind) {

    var result = false;
    var ll = ocloInfo.getTokenLinkList(oclo, index);
    var tmpToken = null;
    var afterWhat = "C";

    if (kind != undefined) {
	afterWhat = kind;
    }

    tmpToken = ll.next();

    if (tmpToken.symbol == afterWhat) {
	result = true;
    }
    if (tmpToken.smidgeType == "open-branch") {
	tmpToken = ll.next();
	if (tmpToken.symbol == afterWhat) {
	    result = true;
	}
    }

    // alert("C is after: " + result);
    return result;
};


/**
 * Generalization to get the ocloToken for first atom after something.
 * 
 * 
 * @param {Object} oclo the ocloObject. 
 * @param {Number} index The index of the thing you wnat to search after. 
 * @returns {ocloToken} token token is the ocloToken for the atom after or null when there is 
 * no atom after, including when the token at index is at the end of a branch. 
 */
ocloInfo.getAtomAfter = function(oclo, index) {

    var result = null;
    var ll = ocloInfo.getTokenLinkList(oclo, index);
    var tmpToken = null;
  

    tmpToken = ll.next();
    while (null != tmpToken && tmpToken.smidgeType != "atom" && tmpToken.smidgeType != "open-branch"
	    && tmpToken.smidgeType != "close-branch") {
	tmpToken = ll.next();
    }

    if (tmpToken == null) {
	//we hit the end of the SMILE
	return result;
    }

    switch (tmpToken.smidgeType) {
	case "atom":
	    result = tmpToken;
	    break;
	case "open-branch":
	    //case is X(^^^), and there must be an Atom in the branch. 
	    tmpToken = ll.next(); 
	    while(tmpToken.smidgeType != "atom") {
		tmpToken = ll.next(); 
	    }
	    result = tmpToken;
	    break;
	case "close-branch":
	    // case is (..X), it's the last atom in the branch.
	    break;
	default:
	    break;
    }

    return result;
};

/**
 * Generalization to get the first atom ocloToken before something
 * 
 * 
 * @param {Object} oclo the ocloObject. 
 * @param {Number} index The index of the thing you wnat to search before. 
 * @returns {ocloToken}  the atom before (directly bonded, taking into account branches) or null when there is 
 * no atom before. 
 */
ocloInfo.getAtomBefore = function(oclo, index) {
    var result = null;
    var ll = ocloInfo.getTokenLinkList(oclo, index);
    var tmpToken = null;
    var branches = oclo.branches;
    var branch = null; 
    var viewID = 0; 
    var tt = null; 

    tmpToken = ll.previous();
    while (null != tmpToken && tmpToken.smidgeType != "atom" && tmpToken.smidgeType != "open-branch"
	    && tmpToken.smidgeType != "close-branch") {
	tmpToken = ll.previous();
    }

    if (tmpToken == null) {
	return result;
    }

    switch (tmpToken.smidgeType) {
	case "atom":
	    result = tmpToken;
	    break;
	case "open-branch":
	    //case is ()^()^(..X)
	    for (var i = branches.length - 1; i >= 0; i--) {
		branch = branches[i];
		if(branch.startIndex > tmpToken.id){
		    continue; 
		}
		viewID = branch.startIndex - 1;
		tt = ll.getTokenAt(viewID);
		// alert(tt.symbol);
		if (tt.smidgeType == "atom") {
		    result = tt;
		    break;
		}
	    }
	    break;
	case "close-branch":
	    // case is ()^()^()X
	    // we're looking before X at the ^s
	    for (var i = branches.length - 1; i >= 0; i--) {
		branch = branches[i];
		if(branch.endIndex > tmpToken.id){
		    continue; 
		}
		viewID = branch.startIndex - 1;
		tt = ll.getTokenAt(viewID);
		if (tt.smidgeType == "atom") {
		    result = tt; ;
		    break;
		}
	    }
	    break;
	default:
	    break;
    }

    return result;

};

/**
 * primarily a helper function for getOxygenFunctionalGroups
 * @deprecated
 * @see {@link ocloInfo.getAtomBefore}
 * 
 */
ocloInfo.isAtomBefore = function(oclo, index, kind) {
    var result = false;
    var ll = ocloInfo.getTokenLinkList(oclo, index);
    var tmpToken = null;
    var branchcount = 0;
    var afterWhat = "C";

    if (kind != undefined) {
	afterWhat = kind;
    }

    tmpToken = ll.previous();
    if (tmpToken == null) {
	return false; // catch all for general use of the funtion.
    }

    if (tmpToken.symbol == afterWhat) {
	result = true;
    }

    if (tmpToken.smidgeType == "close-branch") {
	// more complicated because we can have C(..)(..)(..)O(..)..
	// we need to look at ^ for C ^(..)^(..)^(..)O(..)..
	// we don't look further back because C's valance is 4
	var branches = oclo.branches;
	for (var i = branches.length - 1; i >= 0; i--) {
	    var branch = branches[i];
	    if (branch.endIndex <= tmpToken.id) {
		branchcount++;
		if (branchcount > 3) {
		    break;
		}
		var lookat = branch.startIndex - 1;

		tt = ll.getTokenAt(lookat);
		// alert(tt.symbol);
		if (tt.symbol == afterWhat) {
		    result = true;
		    break;
		}

	    }
	}

    }

    if (tmpToken.smidgeType == "open-branch") {
	// more complicated because we can have ^(..)^(..)^(O)[^C](..)..
	var branches = oclo.branches;
	branchcount = 0;
	for (var i = branches.length - 1; i >= 0; i--) {
	    var branch = branches[i];
	    if (branch.startIndex <= tmpToken.id) {
		branchcount++;
		if (branchcount > 2) {
		    break;
		}
		var lookat = branch.startIndex - 1;

		tt = ll.getTokenAt(lookat);
		// alert(tt.symbol);
		if (tt.symbol == afterWhat) {
		    result = true;
		    break;
		}

	    }
	}

    }

    // alert("C is before: " + result);

    return result;
};

/**
 * Helper function for getFunctionalGroups. 
 * 
 * This is called when Nitrogen is present and Oxygen is not.  
 * we are looking for Nitrile, Imine or first, second or third degree 
 * Amine
 * 
 * @param {oclo}
 *                oclo
 * @param {Array}
 *                nonAromatics
 * @returns {Array.String} result
 */
ocloInfo.getNitrogenFunctionalGroups = function(oclo, nonAromatics) {

    var result = new Array();
    var nIndicies = nonAromatics["N"]; 
    var ll = ocloInfo.getTokenLinkList(oclo, 0); 
    var tmpToken = null; 
    
    //we're going to test each non-aromatic N. 
    for (var i = 0; i < nIndicies.length; i++) {
	var index = nIndicies[i];
	ll.setCurrentID(index);
	tmpToken = ll.get(); 
	var BondType = ocloInfo.getTripleBondType(oclo, tmpToken.id); 
	
	//the smile s/b such that the answer will be either backward or none 
	//we're looking for Nitrile  CC#N or something like C(C)(C)C#N
	if(BondType == "backward"){
	    tmpToken = ocloInfo.getAtomBefore(oclo, tmpToken.id); 
	    if(tmpToken != null && tmpToken.isCarbon){
		tmpToken = ocloInfo.getAtomBefore(oclo, tmpToken.id); 
		if(tmpToken != null && tmpToken.isCarbon){
		    result.push("Nitrile"); 
		    continue;  //with next Nitrogen in for loop. 
		}
	    }
	   
	   continue;  //with next nitr 
	}
	
	ll.setCurrentID(index);
	tmpToken = ll.get(); 
	
	BondType = ocloInfo.getDoubleBondType(oclo, tmpToken.id); 
	//the smile s/b such that the answer will be either backward, forward or none 
	//we're looking for Imine
	//patterns:  CC(C)=N
	//others:  CC(=N)C,  CC(=NCXXXX)C, CC(CXXX)=NC,  
	///
	if(BondType == "backward"){
	    
	    //there is a special case pattern that 
	    //exhibts by a SMILE with no branches.  all positive cases 
	    //have at least 1 branch. so, 
	    if(oclo.branches.length == 0){
		//its not a imine 
		continue; 
	    }
	    
	    
	    //move to the double bonded C 
	    tmpToken = ocloInfo.getAtomBefore(oclo, tmpToken.id); 
	    if(tmpToken == null || !tmpToken.isCarbon || tmpToken.charge != 0){
		//its not Imine 
		continue;  // with next Nitrogen 
	    }
	    
	    //remember for later. 
	    var carbonID = tmpToken.id; 
	    
	    //there must be a C before this one. 
	    tmpToken = ocloInfo.getAtomBefore(oclo, tmpToken.id); 
	    if(tmpToken == null || !tmpToken.isCarbon || tmpToken.charge != 0){
		//its not Imine 
		continue;  // with next Nitrogen 
	    }
	    
	    //there must be nothing or a carbon after the N 
	    tmpToken = ocloInfo.getAtomAfter(oclo, index); 
	    if(tmpToken !=null && (!tmpToken.isCarbon || tmpToken.charge != 0)){
		//its not Imine
		continue;  // with the next Nitrogen. 
	    }
	    // now move back to the double bonded C that branches 
	    ll.setCurrentID(carbonID);
	    tmpToken = ll.get(); 
	    
	    //the next Atom will be N or C if its still possible its Imine 
	    tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id); 
	    if(tmpToken.isCarbon){
		result.push("Imine"); 
		continue;  //with the Next nitrogen. 
	    }
	    
	    if(tmpToken.isNitrogen){
		//it should be the Nitrogen we've been looking at. 

		if(tmpToken.id  == index){
		    ll.setCurrentID(index); 
		    if(ll.hasNext()){ //it needs to be CC(=N)C or CC(=Nccc)C
			tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id);
			if(tmpToken != null && tmpToken.isCarbon && tmpToken.charge == 0){
			    result.push("Imine"); 
			}
			//it is null so should be CC(=N)C 
			// lets see if the next one after the branch is C. 
			if(tmpToken == null){
			    tmpToken = ocloInfo.getAtomAfter(oclo, index + 1);
			    if(tmpToken != null && tmpToken.isCarbon && tmpToken.charge == 0){
				    result.push("Imine"); 
				}    
			}
			
		    }else{
			result.push("Imine");  // its CC(C)=N
		    }
		}

		continue; //with the nest Nitrogen
	    }



	}
	
	//CN=C(CXXXX)C, or CN=C(C)C
	if(BondType == "forward"){
	    tmpToken = ocloInfo.getAtomBefore(oclo, tmpToken.id); 
	    if(tmpToken != null && (!tmpToken.isCarbon || tmpToken.charge != 0)){
		//its not Imine
		continue;  //with next Nitrogen.
	    }
	    // after the Nitrogen should be C 
	    tmpToken = ocloInfo.getAtomAfter(oclo, index); 
	    if(tmpToken != null && (!tmpToken.isCarbon || tmpToken.charge != 0)){
		//its not Imine
		continue;  //with next Nitrogen.
	    }
	    // after the Carbon should be (C)C or (CXXX)C 
	    tmpToken = ocloInfo.getAtomAfter(oclo, index); 
	    if(tmpToken != null && (!tmpToken.isCarbon || tmpToken.charge != 0)){
		//its not Imine
		continue;  //with next Nitrogen.
	    }
	    
	    //next C should be after the first branch 
	    tmpToken = ocloInfo.getAtomAfter(oclo, oclo.branches[0].endIndex); 
	    if(tmpToken != null && tmpToken.isCarbon && tmpToken.charge == 0){
		result.push("Imine"); 
		continue; //with next Nitrogen
	    }
	   
	}
	
	//we're looking fpr 1st, 2nd or 3rd degree Amine 
	// 3rd looks like CN(C)C or CN(CXXX)(C) or CC(C)N(C)C
	// 2nd looks like CNC or C(NCXXX) or CNC(XXXX)XXXX or CNC(C)C
	// 1st looks like CN or NC 
	
	if(BondType == "none"){
	    tmpToken = ocloInfo.getAtomBefore(oclo, tmpToken.id);
	    if(tmpToken == null){
		tmpToken = ocloInfo.getAtomAfter(oclo, index); 
		if(tmpToken != null && tmpToken.isCarbon && tmpToken.charge == 0){
		    result.push("1st Degree Amine"); 
		   
		}
		 continue; //with next Nitrogen
	    }
	    // its not null and should be C before N
	    if(!tmpToken.isCarbon || tmpToken.charge != 0){
		//its not an Amine 
		continue;  //with next Nitrogen 
	    }
	    
	    tmpToken = ocloInfo.getAtomAfter(oclo, index); 
	    //looking for C after N 
	    if(tmpToken != null  && tmpToken.isCarbon && tmpToken.charge  == 0 ){
		//its at least 2nd degree Amine 
		//if there isn't anything after this C its only 2nd 
		
		//remember for later 
		var carbonID = tmpToken.id; 
		
		// its not null and should be C after NC
		if(!tmpToken.isCarbon || tmpToken.charge != 0){
		    //its not an Amine 
		    continue;  //with next Nitrogen 
		}

		//we know we have CNCXC  we want to know 
		//if the NC is actually N(C or not. 
		if(carbonID - index == 2){
		    //we have N(C 
		    result.push("3rd Degree Amine"); 
		}else{
		    result.push("2nd Degree Amine");
		}
	    }else{
		if(tmpToken == null){
		    result.push("1st Degree Amine"); 
		    continue; //with next Nitrogen
		}
	    }
	    
	    
	}
		
	
    }

    return result;

};

/**
 * Helper function for getFunctionalGroups.  This function is looking 
 * for Alkyl Halides 
 * 
 * @param {oclo}
 *                oclo
 * @param {Array}
 *                nonAromatics
 * @returns {Array.String} result
 */
ocloInfo.getHalogenFunctionalGroups = function(oclo, nonAromatics) {
  
    var ll = ocloInfo.getTokenLinkList(oclo, 0); 
    var tmpToken = null; 
    
    var result = new Array();    
    if(oclo.containsHalogen){
	for (var i = 0; i < oclo.halogenIndicies.length; i++) {
	    var index = oclo.halogenIndicies[i];
	    ll.setCurrentID(index);
	    tmpToken = ll.get();
	    tmpToken = ocloInfo.getAtomAfter(oclo, index); 
	    if(tmpToken != null){
		//its not Alkyl Halide 
		//alert("hello");
		ll.setCurrentID(index);
		tmpToken = ll.get();
		if(! ll.hasPrevious()){
		    //then it is the first thing in the smile 
		    result.push("Halide"); 
		    break;
		}
		
		continue; //with next Halogen
	    }
	    
	    tmpToken = ocloInfo.getAtomBefore(oclo, index); 
	    //this should be a C for us to go on looking 
	    if(tmpToken == null || !tmpToken.isCarbon || tmpToken.charge != 0){
		//its not Alkyl Halide 
		continue; //with next halogen. 
	    }
	    
	    /*
	     * At this point it is  C-X
	     * as long as there is nothing after the X we have Halide 
	     * 
	     */
	    tmpToken = ocloInfo.getAtomAfter(oclo, index);
	    if(tmpToken == null){
		 result.push("Halide"); 
		    break;
	    }
	    
	   
	    
	    // the commented out code below is the starting point for 
	    // finding the degree of Halide being searched for. 
	    //
	    // It is unneeded in the current spectroscopy application but is 
	    // left here in a comment in case it is needed in the future. 
	    
	   /* var carbonID = tmpToken.id; 
	    
	    //the branched C should have a C before it. 
	    tmpToken = ocloInfo.getAtomBefore(oclo, tmpToken.id); 
	    if(tmpToken == null || !tmpToken.isCarbon || tmpToken.charge != 0){
		//its not Alkyl Halide 
		continue; //with next halogen. 
	    }

	    //if the halogen is at the end of the SMILE Br ex.
	    if(!ll.hasNext()){	
		
		//move back to the branched C 
		ll.setCurrentID(carbonID); 
		tmpToken = ll.get(); 
		//we are at the C that should branch to 2 more C's and then the Halogen 
		//we're looking at.  If that's the case its what we're looking for . 
		
		//the next C should be X(C
		tmpToken = ll.getTokenAt(tmpToken.id + 2); 
		if(tmpToken == null || !tmpToken.isCarbon || tmpToken.charge != 0){
		    //its not Alkyl Halide 
		    continue; //with next halogen. 
		}
		tmpToken = ll.get(); //go back to the carbonID C 
		
		var branchClose = ocloInfo.getBranchCloseIndex(tmpToken.id + 1, oclo.ocloTokens);
		tmpToken = ocloInfo.getAtomAfter(oclo, branchClose);
		
		if(tmpToken != null && tmpToken.isCarbon && tmpToken.id + 2 == index){
		    result.push("Alkyl Halide"); 
		    //one is all we need to find so we, 
		    break;  //and don't look at anymore halogens. 
		}
		
		
	    }else{  //the Halogen is in the middle of the SMILE somewhere (Br) ex.
		//move back to the branched C
		ll.setCurrentID(carbonID); 
		tmpToken = ll.get(); 
		//we are at the C that should branch to (C)(Hal)C  or (Hal)(C)C or (C)C(hal) or (Hal)C(C)
		//and then the Halogen.we're looking at.  If that's the case its what we're looking for . 
		
		//in all cases there is (Value
		
		tmpToken = ocloInfo.getAtomAfter(oclo, tmpToken.id); 
		if(tmpToken != null && tmpToken.charge == 0 && tmpToken.id -2 == carbonID && (tmpToken.isCarbon || tmpToken.isHalogen)){
		    //we can continue. 
		    if(tmpToken.isHalogen){
			//we know its at the end of a branch  so 
			//the next C is (hal)C(C) or (hal)(C)C
			tmpToken = ll.getTokenAt(tmpToken.id + 2); 
			//alert(tmpToken.symbol);
			if(tmpToken != null && tmpToken.isCarbon){
			    tmpToken = ll.getTokenAt(tmpToken.id + 2); 
			    if(tmpToken != null && tmpToken.isCarbon){
				result.push("Alkyl Halide"); 
				break; 
			    }
			}
			if(tmpToken.smidgeType == "open-branch"){
			    //alert("open Branch"); 
			    var branchClose = ocloInfo.getBranchCloseIndex(tmpToken.id, oclo.ocloTokens);
			    tmpToken = ll.getTokenAt( branchClose + 1);
			    if(tmpToken != null && tmpToken.isCarbon){
				result.push("Alkyl Halide"); 
				break; 
			    }
			    
			}
			
		    }else if(tmpToken.isCarbon){
			//we're looking at (C)(hal)C or (C)C(Hal)
			// we need to look past the (C) branch next 
			var branchClose = ocloInfo.getBranchCloseIndex(tmpToken.id -1, oclo.ocloTokens);
			tmpToken = ocloInfo.getAtomAfter(oclo, branchClose);
			if(tmpToken != null && tmpToken.isCargon && tmpToken.charge == 0  && tmpToken.id +2 == index){
			    	result.push("Alkyl Halide"); 
			    	break;
			    
			}
			
			tmpToken = ll.getTokenAt(index + 2); 
			if(tmpToken != null && tmpToken.isCarbon && tmpToken.charge == 0){
			    result.push("Alkyl Halide"); 
			    	break;
			}
			
					
		    }
			       
		}
		
		
	    }*/
	    
	}
	
	
	
    }
    
    return result;

};


/**
 * @method 
 * @param {Array.String} arr 
 * @returns {Boolean} value.  returns True if arr 
 * contains the symbol for any of the halogens. 
 */
ocloInfo.checkForHalogens = function(arr) {
    result = false;
    for (var i = 0; i < chemicalInformation.halogens.length; i++) {
	var halogen = chemicalInformation.halogens[i];
	if (arr.contains(halogen)) {
	    return true;
	}
    }
    return result;
};


/**
 * 
 * @param {Array.ocloToken} ocloTokens 
 * @returns {Boolean} value, returns true if any of the 
 * tokens in ocloTokens is an aromatic. 
 */
ocloInfo.checkforAromatic = function(ocloTokens) {

    for (var i = 0; i < ocloTokens.length; i++) {
	var token = ocloTokens[i];
	if (token.aromatic == true) {
	    return true;
	}
    }

    return false;
};

/**
 * Used by getFunctionalGroups
 * 
 * @param {String}
 *                symbol
 * @param {Array.dAtoms}
 *                dAtoms
 * @returns the dAtom (distinct) data structure found for symbol, or null.
 */
ocloInfo.getDatomOf = function(symbol, dAtoms) {

    for (var i = 0; i < dAtoms.length; i++) {
	var dAtom = dAtoms[i];
	if (dAtom.atom == symbol) {
	    return dAtom;
	}
    }
    return null;

};


/**
 * 
 * @param {Array.ocloToken} ocloTokens 
 * @returns {Array} value, an array with the index 
 * of any open-branch tokens found in ocloTokens
 */
ocloInfo.getBranchIndicies = function(ocloTokens) {

    var result = [];

    for (var i = 0; i < ocloTokens.length; i++) {
	var token = ocloTokens[i];
	if (token.smidgeType == "open-branch") {
	    result.push(i);
	}
    }

    return result;
};

/**
 * 
 * uses a psuedo stack (empty stack with an int stack pointer) to figure out the closing index for a branch in ocloTokens starting
 * at startIndex.
 * 
 * ocloTokens must be well formed. that is each open branch must be matched by a closed branch somewhere.
 * 
 * @param startIndex
 * @param {Array.ocloToken} ocloTokens 
 * @returns {Number} result
 */
ocloInfo.getBranchCloseIndex = function(startIndex, ocloTokens) {
    var result = startIndex;
    var pStack = 0;

    for (var i = startIndex + 1; i < ocloTokens.length; i++) {
	var token = ocloTokens[i];
	if (token.smidgeType == "close-branch") {
	    if (pStack == 0) {
		return i;
	    } else {
		pStack--;
		continue;
	    }
	}

	if (token.smidgeType == "open-branch") {
	    pStack++;
	    continue;
	}
	// ignore everything else;

    }
    // default return if we didn't find the match.
    return result;
};

/**
 * Was the intial method to obtain an ocloToken.  Was replaced 
 * with ocloInfo.getOcloToken(smidgeToken)
 * 
 * @deprecated
 * @param tokens
 * @param smile
 * @returns {___anonymous58129_58168}
 */
ocloInfo.parse = function(tokens, smile) {

    var ocloToken = {
	"smile" : "",
	"tokens" : []
    };
    ocloToken.smile = smile;
    for (var i = 0; i < tokens.length; i++) {
	var token = tokens[i];
	var tmToken = ocloInfo.getOcloToken(token);
	tmToken.id = i;
	ocloToken.tokens.push(tmToken);
    }

    return ocloToken;
};

/**
 * Config is a Static class so does not need to be instantiated. If you wich to however a default constructor is provided.
 * 
 * @class
 * 
 * @classdesc config is a static class that is used to obtain literals and other configuration information for various Javascript
 *            Organic Chemistry Learning Objects applications.
 *            <p>
 *            You should view the javascript file to see the members as they are ommitted here so as to keep the script itself
 *            readable.
 * 
 * If a literal is being used or should be configurable then it must be created here, and only here.
 * 
 * @tutorial ArchitectureOverview
 * 
 */
function config() {

}

/*
 * testing related section: Contains URLs and booleans to use while developing and/or testing the application. also contains the
 * LIVE site URL.
 * 
 */
   
config.WHICH_URL = "LIVE";
config.TEST_BASEURL = "http://localhost:8080/JSOCLOWebsite/";
config.CPANEL_URL = "http://chem.cpsc.ucalgary.ca/WebContent/";
config.LIVE_BASEURL = "/courses/351/WebContent/";
config.TEST_BASEURL_VM = "http://192.168.155.129:8080/JSOCLOWebsite/";
config.ALERT_FG_FINDS = false;

/*
 * application behaviour related section: Contains booleans to control the application behaviour. Things like cycling the next and
 * previous buttons, alerting hints and persist messges or showing them in the response area, locking questions. The number of max
 * attempts.. ect.
 */
config.ORGNOM_UNLOCK_QUESTION = false;
config.ORGNOM_ALERT_PERSIST_MSGS = true;
config.CYCLE_NEXT_PREV = true;
config.ALERT_HINTS_MSGS = false;
config.ALERT_PERSIST_MSGS = false;
config.DONT_SHOW_FEEDBACK_ON_CORRECT = false;
config.SPEC_UNLOCK_ALL_QUESTIONS = false;
config.APPEND_CORRECT_VALUES = true;
config.PREPEND_INCORRECT_VALUES = true;
config.MAXATTEMPTS = 8;
config.ALLOW_HINTS_AT = 3;

/*
 * User interaction section: Strings that are displayed to the user during there interactions with the applicaiton(s).
 */

config.NO_ANSWER = "Please enter an answer before submitting.";
config.DUPLICATE_ANSWER = "Sorry, but you tried that answer already.";
config.ALREADY_CORRECT = "  \n(you've finished the question previously)";
config.FORFEIT = "  \n(you viewed the solution before getting it correct)";
config.FORFEIT_CONFIRMATION = "If you view the solution you'll receive a zero on the question.\nSelect ok to view the solution.";
config.NO_CATEGORY_CHANGE = "You need to finish the current question first.\n";
config.NO_NEXT_QUESTION = "You're on the last question of the category, \nchoose a previous question or new category to move on";
config.NO_PREV_QUESTION = "You're on the first question of the category, \nchoose another question or new category to move on";
config.UNRECOGNIZED_ANWER = "That's incorrect. You'll have to try another answer";
config.PERSIST_MESSAGES = [ "It'll be worth it to persist a little longer.", "I think you're close, keep at it.",
	"Patience grasshopper.  Keep at it!", "I bet you'll get it next try. Keep going",
	"Just give it one more try.  You'll get it soon." ];
config.HINTS_ALERT_MSG = "Hints are available now";
config.TRY_FIRST = "Try another answer first";

/*
 * Error and support section: Contains error messages and support information to display when required.
 */
config.SUPPORT_EMAIL_STRING = " Dr. Ian Hunt (irhunt@ucalgary.ca) ";
config.STN = "/questions/";
config.QDIR_PREFIX = "/question";
config.ANSWER_FILE_NAME = "question.txt";
config.AF_LOAD_ERROR = "We were unable to load that questions information properly. \n\nPlease try again later.\n\n"
	+ "If the problem persists please report it to " + config.SUPPORT_EMAIL_STRING
	+ "giving him the information below.  \n\nThank you and sorry for the trouble.";
config.NO_HELP_LINK = "A help page for the question isn't available";
config.FG_NOT_FOUND = "The chemicalInformation class does not have information for the following functional group(s): ";
config.NO_CARBONS = "The Answer must contain at least one Carbon atom";

/*
 * Nomenclature specific section: Strings specific to nomenclature
 * 
 */
config.ORGNOM_FCLASS = "orgnom";
config.DEFAULT_NOM_TYPE_MSG = "Enter the IUPAC name ";
config.ORGOM_MAX_QUESTION_SCORE = 10;
config.ORGNOM_QNAME = "Question - ";
config.ORGNOM_TITLE = "Basic Organic Nomenclature";
config.ORGNOM_CONF = "json/orgnom.json";

/*
 * Spectroscopy specific section: Strings specific to spectroscopy
 */

config.SPEC_FCLASS = "spectroscopy";
config.SPEC_TITLE = "Interactive Spectroscopy";
config.SPEC_QNAME = "Question - ";
config.SPEC_MASS_IMG = "MS.gif";
config.SPEC_IR_IMG = "IR.gif";
config.SPEC_CARBON_IMG = "C.gif";
config.SPEC_HYDROGEN_IMG = "H.gif";
config.SPEC_ALLSPEC_PAGE = "allSpectra.htm";
config.SPEC_ANSWER_IMG = "ans.gif";
config.SPEC_ANSWER_PAGE = "Ans.html";
config.SPEC_MAX_QUESTION_SCORE = 10;
config.SPEC_CONF = "json/spectroscopy.json";
config.SPEC_MAX_NUMBER_OF_QUESIONS = 40;

config.JSME_ANALYZER = "O.C.L.O. Chemical Informtion";

/*
 * The number of questions in the largest category. this value is used in rdUtilis as a starting point in determining the number of
 * questions for each category in Nomenclature
 * 
 * @type {Number}
 */
config.MAX_NUM_QUESTIONS = 40;

/**
 * @returns One of the config.PERSIST_MESSAGES, randomly chosen.
 */
config.getPersistMessage = function() {

    var min = 0;
    var max = config.PERSIST_MESSAGES.length - 1;
    var index = Math.floor(Math.random() * (max - min + 1) + min);

    return config.PERSIST_MESSAGES[index];

};

/**
 * Returns one of the Base URLs.
 */
config.getBaseUrl = function() {

    switch (config.WHICH_URL) {
	case "VM":
	    return config.TEST_BASEURL_VM;
	    break;
	case "CPANEL":
	    return config.CPANEL_URL;
	    break;
	case "LOCAL":
	    return config.TEST_BASEURL;
	    break;
	case "LIVE":
	    return config.LIVE_BASEURL;
	    break;
	default:
	    throw "Which URL is not set properly in config";
    }

};

/**
 * Relies on Jquery
 * 
 * Prevents anything other than the input box submitting the 'Enter' key.
 * 
 * @param e
 */
function preventReturn(e) {

    if (e.which == 13) {
	e.preventDefault();
    }

}

/**
 * 
 * {@link RegExp} RegExp.escape is used to escape any special chars in a string, so that RegExp will be ok with it. We use it in the
 * Searcher class when creating regular expressions out of the strings in the question file.
 * 
 * It does not escape $, | or - as they appear in the question answers.  and does not escape ^ as it might appear
 * in the questions answers. 
 * 
 * @param string
 * @returns string with regular expression special chars escaped.
 */
RegExp.escape = function(string) {
    return string.replace(/[\/\\*+?.()[\]{}]/g, '\\$&');
};

Array.prototype.contains = function(obj) {
    var i = this.length;
    while (i--) {
	if (this[i] === obj) {
	    return true;
	}
    }
    return false;
};

/**
 * Sort comparator to sort strings in a case insensitive manner.
 * 
 * @param a
 * @param b
 * @returns {Number}
 */
function compareCaseInsenstive(a, b) {

    var st1 = a[0].toLowerCase();
    var st2 = b[0].toLowerCase();

    if (st1 < st2) {
	return -1;
    }
    if (st1 > st2) {
	return 1;
    }
    return 0;
}

/**
 * Molecular formula comparator; used to sort the molecular formula array in molecularFormulaSearcher
 * 
 * @param a
 *                a - A string of at least 1 in length
 * @param b
 *                b - a string of at least 1 in length
 * 
 * The ordering is alphabetic except for halogens which go at the end of the formula but are otherwise in alphabetic order
 * themselves.
 */
function compareMolFormula(a, b) {

    // if they are both NOT halogens, it's alphabetic.
    if (!chemicalInformation.isHalogen(a) && !chemicalInformation.isHalogen(b)) {
	return compareCaseInsenstive(a, b);
    }

    // if the are both halogens, it's alphabetic
    if (chemicalInformation.isHalogen(a) && chemicalInformation.isHalogen(b)) {
	return compareCaseInsenstive(a, b);
    }

    // now one or the other is halogen, but not both.
    // if it is a, then a is greater than b.
    if (chemicalInformation.isHalogen(a)) {
	return 1;
    }

    return -1;

}

/**
 * Used to sort numerically
 * 
 * @param a
 * @param b
 * @returns {Number}
 */
function compareNumber(a, b) {
    return a - b;
}

// =========================Supporting Classes =====================

/**
 * Constructor for a new Searcher
 * 
 * @class
 * 
 * @param {RegExp}
 *                regEx the regular expression used to test the answers with.
 * @param {String}
 *                foundMsg the message to return when the test is true.
 * @param {String}
 *                notFoundMsg the message to return when the test is false.
 * 
 * @classdesc A supporting class used to check answers against the values that were parsed from the question file. It can test the
 *            answer, check for an exact match and return positive and negative messages based on the testing.
 *            <p>
 *            It serves as a base class for other inheriting searchers. (except lociSearcher).
 * 
 * @see {@link chargeSearcher}
 * @see {@link functionalGroupsSearcher}
 * @see {@link molecularFormulaSearcher}
 * @see {@link molecularWeightSearcher}
 * @see {@link numHydrogenSearcher}
 * @see {@link numCarbonSearcher}
 * @see {@link numTypesHydrogenSearcher}
 * @see {@link numTypesCarbonSearcher}
 * @see {@link ringsSearcher}
 * 
 * @tutorial ArchitectureOverview
 */
function searcher(regEx, foundMsg, notFoundMsg) {

    // private undocumented members
    // RegExp.escape is something I added to RegExp. Its
    // definition is in jsocloUtils.js Go look at it now

    var regString = regEx;

    var regExp = null;

    if (regEx != null) {
	regExp = new RegExp(RegExp.escape(regEx));

    } else {
	regExp = new RegExp("");
    }

    var positiveMsg = "";
    var negativeMsg = "";

    // we don't want 'null' to be displayed
    if (foundMsg == null) {
	foundMsg = "";
    }
    if (notFoundMsg == null) {
	notFoundMsg = "";
    }

    // clean up the input. trim spaces
    // and replace end of lines chars.
    positiveMsg = foundMsg.replace(/^\s+|\s+$/g, '');
    negativeMsg = notFoundMsg.replace(/^\s+|\s+$/g, '');
    positiveMsg = positiveMsg.replace(/\n|\r|\f/g, '');
    negativeMsg = negativeMsg.replace(/\n|\r|\f/g, '');

    // =========================privileged methods =====================

    /**
     * Uses its regular expression to test an answer and will return the approriate message from the question file.
     * 
     * @param {String}
     *                answer the input to test
     * @returns {String}
     * @method
     * @access protected
     */
    this.search = function(answer) {
	//alert(regExp);
	if (regExp.test(answer)) {
	    return positiveMsg;
	}

	return negativeMsg;

    };

    /**
     * Tests an answer to determine its correctness.
     * 
     * @param {String}
     *                answer, the input to test
     * @returns {Booean} True if the answer is correct. False otherwise.
     * @method
     * @access protected
     */
    this.test = function(answer) {
	return regExp.test(answer);
    };

    /**
     * @returns {RegExp}
     */
    this.getRegExp = function() {
	return regExp;
    };

    /**
     * @returns {String}
     */
    this.getfoundMsg = function() {
	return positiveMsg;
    };

    /**
     * @returns {String}
     */

    this.getNotFoundMsg = function() {
	return notFoundMsg;
    };

    /**
     * @returns {String}
     */
    this.getRegExpString = function() {
	return regString;
    };

}

// =======================================================================================

/**
 * chemicalInformation does not need to be instantiated as all of its members and methods are static, however, if you really want
 * to, a default contructor.
 * 
 * @class
 * 
 * @classdesc Static class that contains chemical information and functions that are needed in the spectroscopy application.
 * 
 * @tutorial ArchitectureOverview
 * 
 * 
 */
function chemicalInformation() {

}



// "alkane" : [ /CC /, /C\d*C/ ],
/**
 * @returns {Array} an array with molecular symbols used as keys. Keys map to the molecular weight of the symbol.
 */
chemicalInformation.getMolWeightMap = function() {

    var weightMap = new Array();

    weightMap["C"] = 12;
    weightMap["F"] = 19;
    weightMap["H"] = 1;
    weightMap["I"] = 127;
    weightMap["N"] = 14;
    weightMap["O"] = 16;
    weightMap["P"] = 31;
    weightMap["S"] = 32;
    weightMap["Cl"] = 35;
    weightMap["Br"] = 80;

    return weightMap;
};

/**
 * @returns {Array} an array with molecular symbols used as keys. Keys map to the valence of the symbol.
 */
chemicalInformation.getValenceMap = function() {

    var valences = new Array();

    valences["C"] = 4;
    valences["F"] = 1;
    valences["H"] = 1;
    valences["I"] = 1;
    valences["N"] = 3;
    valences["O"] = 2;
    valences["P"] = 3;
    valences["S"] = 2;
    valences["Cl"] = 1;
    valences["Br"] = 1;

    return valences;

};

chemicalInformation.getMolecularWeight = function(smileAnswer) {

    var tokens = Parser.parse(smileAnswer);
    var totalWeight = 0;
    var weightMap = chemicalInformation.getMolWeightMap();

    for (var i = 0; i < tokens.length; i++) {
	var token = tokens[i];
	if (token.type == "atom") {
	    totalWeight += weightMap[token.symbol.toString()];
	}
    }
    // now count the hydrogens
    var hydrogens = chemicalInformation.countHydrogens(smileAnswer, tokens);
    // alert("Hydrogens: " + hydrogens);

    totalWeight += hydrogens;
    return totalWeight;

};

chemicalInformation.getMolFormula = function(smileAnswer, tokens) {

    var molForm = new Array();
    var formula = "";
    var atoms = chemicalInformation.getDistinctAtoms(tokens);
    var numH = chemicalInformation.countHydrogens(smileAnswer, tokens);

    // add the elements with their counts to an array
    for (var i = 0; i < atoms.length; i++) {
	var atom = atoms[i];
	var numAtom = chemicalInformation.countAtomsOf(atom, tokens);
	molForm.push(atom + (numAtom > 1 ? numAtom : ""));
    }

    // now we deal with Hydrogen
    if (numH > 0) {
	molForm.push("H" + (numH > 1 ? numH : ""));
    }

    // sort the array
    molForm.sort(compareMolFormula);

    // build the formula
    for (var i = 0; i < molForm.length; i++) {
	var mol = molForm[i];
	formula += mol;
    }
    return formula;
};

/**
 * The Halogens in an array (F,Cl,Br,I)
 */
chemicalInformation.halogens = [ "F", "Cl", "Br", "I" ];

/**
 * 
 * @param {String}
 *                halc Presumably an Atomic symbol
 * @returns {Boolean} true if the candidate is a Halogen.
 */
chemicalInformation.isHalogen = function(halc) {

    for (var i = 0; i < chemicalInformation.halogens.length; i++) {
	var hal = chemicalInformation.halogens[i];
	if (halc == hal) {
	    return true;
	}
    }
    return false;

};

/**
 * 
 * @param {tokens}
 *                tokens can be obtained from smidge by Parse.parse(smile)
 * @returns {Number} the number of halogen atoms found
 */
chemicalInformation.countHalogens = function(tokens) {
    var count = 0;

    for (var i = 0; i < chemicalInformation.halogens.length; i++) {
	var halogen = chemicalInformation.halogens[i];
	count += chemicalInformation.countAtomsOf(halogen, tokens);
    }

    return count;
};

/**
 * This will count all atoms, except Hydrogen atoms.
 * 
 * @param {String}
 *                symbol symbol of the element you want to count
 * @param {tokens}
 *                tokens can be obtained from smidge by Parse.parse(smile)
 * @returns {Number} the number of atoms of type 'symbol' found
 */
chemicalInformation.countAtomsOf = function(symbol, tokens) {
    var token;
    var count = 0;
    for (var i = 0; i < tokens.length; i++) {
	token = tokens[i];
	if (token.type == "atom") {
	    if (token.symbol.toString() == symbol) {
		count++;
	    }
	}
    }
    return count;
};

chemicalInformation.countBranches = function(tokens) {

    var me = 0;

    for (var i = 0; i < tokens.length; i++) {
	var token = tokens[i];
	if (token.type == "open-branch") {
	    me++;
	}
    }

    return me;
};

chemicalInformation.getIndiciesOf = function(atom, ocloTokens) {

    var me = [];

    for (var i = 0; i < ocloTokens.length; i++) {
	var token = ocloTokens[i];
	if (atom == token.symbol) {
	    me.push(i);
	}
    }

    return me;
};

chemicalInformation.getNonAromaticIndiciesOf = function(atom, ocloTokens) {

    var me = [];

    for (var i = 0; i < ocloTokens.length; i++) {
	var token = ocloTokens[i];
	if (atom == token.symbol && token.aromatic == false) {
	    me.push(i);
	}
    }

    return me;
};

/**
 * Counts the number of rings in the answer.
 * 
 * @param {tokens}
 *                tokens can be obtained from smidge by Parse.parse(smile)
 * @returns {Number} the number of ring closures
 */
chemicalInformation.countRings = function(tokens) {
    var count = 0;
    var closures = new Array();

    for (var i = 0; i < tokens.length; i++) {
	var token = tokens[i];
	if (token.type == "ring-closure") {
	    if (closures[token.index] != "x") {
		closures[token.index] = "x";
		count++;
		continue;
	    }
	}

    }

    return count;
};

/**
 * We use the SMILE here because of c1ccccc1 : ie small letter c. We use the SMILE instead of the aromatic property in the tokens
 * because JSME will only produce small c's when the the double bonded hexagon tool is used. In all other cases it produce C=C.
 * Those we count using the tokens.
 * 
 * <p>
 * Relying on this property of JSME allows us to count the number of small c's and then divide the answer by two to get the number
 * of double bonds.
 * 
 * <p>
 * The aromatic property in the tokens could be used for the same purpose but has 2 problems for me: 1. I don't know if other atoms
 * can be aromatic or not and 2. If they can the code needed would be significantly greater to do this.
 * 
 * <p>
 * We can change to the tokens later if it is needed.
 * 
 * 
 * @param {SMILE}
 *                smile the SMILE string
 * @returns {Number} The number of double bonds
 */
chemicalInformation.countDoubleBonds = function(smile) {
    var count = 0;
    var smallC = 0;
    // first count the small c's in the SMILE
    for (var i = 0; i < smile.length; i++) {
	var charr = smile[i];
	if (charr == 'c') {
	    smallC++;
	}
    }
    // small C should be evenly divisible by 6
    // and the number of double bonds is smallC/2
    if (smallC % 6 != 0) {
	// alert("chemicallInformation.countDoublebonds: Calculation for SMILE " + smile + " produced an unexpected result");
    }

    count = ~~(smallC / 2); // shift it to an INT.

    var tokens = Parser.parse(smile);

    // now we'll count the number of double bonds in the
    // tokens.
    for (var i = 0; i < tokens.length; i++) {
	var token = tokens[i];
	if (token.type == "bond") {
	    if (token.order == 2) {
		count++;
	    }

	}
    }

    return count;
};

/**
 * Counts the number of triple bonds in the answer.
 * 
 * @param {tokens}
 *                tokens can be obtained from smidge by Parse.parse(smile)
 * @returns {Number} the number of triple bonds found
 */
chemicalInformation.countTripleBonds = function(tokens) {
    var count = 0;
    for (var i = 0; i < tokens.length; i++) {
	var token = tokens[i];
	if (token.type == "bond") {
	    if (token.order == 3) {
		count++;
	    }
	}
    }
    return count;
};

/**
 * Since H will not be in the smile (we use JSME without explicit Hydrogens). counting them is not as easy as the others. We are
 * going to use the Index of Hydrogen Deficiency to do this. The formula... <br>
 * Y = -1(2IHD -2X -2) where <br>
 * Y = #H + #Halogens + #N and <br>
 * X = #C + #N and <br>
 * IHD = #rings + #double bonds + 2(#triple bonds)<br>
 * <br>
 * 
 * so, #H = Y -#halogens - #N<br>
 * 
 * 
 * @param {tokens}
 *                tokens can be obtained from smidge by Parse.parse(smile)
 * @returns {Number} of Hydrogens in the compound.
 */
chemicalInformation.countHydrogens = function(smile, tokens) {
    var hydrogens = 0;
    var N = chemicalInformation.countAtomsOf("N", tokens);
    var x = N + chemicalInformation.countAtomsOf("C", tokens);
    var hal = chemicalInformation.countHalogens(tokens);
    var IHD = chemicalInformation.calculateIHD(smile, tokens);
    var y = -1 * (2 * IHD - 2 * x - 2);

    hydrogens = y - hal - N;
    return hydrogens;
};

/**
 * Calculates the Index of Hydrogen Deficiency (IHD)
 * 
 * @param smile
 * @param {tokens}
 *                tokens can be obtained from smidge by Parse.parse(smile)
 * @returns {Number}
 */
chemicalInformation.calculateIHD = function(smile, tokens) {
    var count = 0;
    count += chemicalInformation.countRings(tokens);
    count += chemicalInformation.countDoubleBonds(smile);
    count += 2 * chemicalInformation.countTripleBonds(tokens);
    return count;
};

/**
 * Counts the charge of the answer
 * 
 * @param {tokens}
 *                tokens can be obtained from smidge by Parse.parse(smile)
 * @returns {Number}
 */
chemicalInformation.countCharge = function(tokens) {
    var count = 0;
    for (var i = 0; i < tokens.length; i++) {
	var token = tokens[i];
	if (token.type == "atom") {
	    count += token.charge;
	}

    }

    return count;

};

/**
 * Counts the number of disctinct atoms in the answer.
 * 
 * @param {tokens}
 *                tokens
 * @returns {Array} of strings where each element is in the answer at least one time. No duplicate strings exist in the Array.
 */
chemicalInformation.getDistinctAtoms = function(tokens) {

    var atoms = new Array();
    var alreadyThere = new Array();

    for (var i = 0; i < tokens.length; i++) {
	var token = tokens[i];
	if (token.type == "atom") {
	    if (alreadyThere[token.symbol.toString()] != "x") {
		alreadyThere[token.symbol.toString()] = "x";
		atoms.push(token.symbol.toString());
	    }

	}
    }
    return atoms;
};

/**
 * Searcher for Molecular Formula
 * 
 * @class
 * 
 * @extends searcher
 * @param {String}
 *                mf the molecular formula from the question file.
 * 
 * @tutorial ArchitectureOverview
 * 
 */
function molecularFormulaSearcher(mf, correctMsg, wrongMsg) {

    searcher.call(this, null, correctMsg, wrongMsg);

    // the question files use Z (Br) and Y(Cl)
    // we don't, so we'll replace any Z and Y in
    // the formula.

    var newMF = mf.replace(/Y/ig, "Cl").replace(/Z/ig, "Br");
    var molecularFormula = newMF;
    var tmp = "";

    /**
     * override {@link searcher#test}. will return true if the molecular formula of the answer is equal to the molecular formula of
     * the question.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.test = function(smileAnswer) {
	var tokens = Parser.parse(smileAnswer);
	var molForm = new Array();
	var formula = "";
	var atoms = chemicalInformation.getDistinctAtoms(tokens);
	var numH = chemicalInformation.countHydrogens(smileAnswer, tokens);

	// add the elements with their counts to an array
	for (var i = 0; i < atoms.length; i++) {
	    var atom = atoms[i];
	    var numAtom = chemicalInformation.countAtomsOf(atom, tokens);
	    molForm.push(atom + (numAtom > 1 ? numAtom : ""));
	}

	// now we deal with Hydrogen
	if (numH > 0) {
	    molForm.push("H" + (numH > 1 ? numH : ""));
	}

	// sort the array
	molForm.sort(compareMolFormula);

	// build the formula
	for (var i = 0; i < molForm.length; i++) {
	    var mol = molForm[i];
	    formula += mol;
	}

	if (formula == molecularFormula) {
	    return true;
	}
	tmp = formula;
	return false;
    };

    /**
     * override {@link searcher#search}. will return the approriate message from the question file.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */

    this.search = function(smileAnswer) {
	if (this.test(smileAnswer)) {
	    if (config.APPEND_CORRECT_VALUES) {
		return this.getfoundMsg() + "<span class='correctColor'>(" + molecularFormula + ")</span>";
	    }
	    return this.getfoundMsg();
	}

	if (config.PREPEND_INCORRECT_VALUES) {
	    var me = tmp;
	    tmp = "";
	    return "<span class='notCorrectColor'>(" + me + ")</span> " + this.getNotFoundMsg();
	}
	return this.getNotFoundMsg();
    };

    this.getMolecularFormula = function() {
	return molecularFormula;
    };

}
// inherit
molecularFormulaSearcher.prototype = new searcher();
molecularFormulaSearcher.prototype.constructor = molecularFormulaSearcher;

/**
 * Searcher for Molecular weight.
 * 
 * @class
 * 
 * @extends searcher
 * @param {Number}
 *                mw molecular weight
 * 
 * @tutorial ArchitectureOverview
 * 
 */
function molecularWeightSearcher(mw, correctMsg, wrongMsg) {

    searcher.call(this, null, correctMsg, wrongMsg);

    var molecularWeight = new Number(mw).valueOf();
    var tmp = 0;

    /**
     * override {@link searcher#test}. will return true if the molecular weight of the answer is equal to the molecular weight of
     * the question.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.test = function(smileAnswer) {
	var tokens = Parser.parse(smileAnswer);
	var totalWeight = 0;
	var weightMap = chemicalInformation.getMolWeightMap();

	for (var i = 0; i < tokens.length; i++) {
	    var token = tokens[i];
	    if (token.type == "atom") {
		totalWeight += weightMap[token.symbol.toString()];
	    }
	}
	// now count the hydrogens
	var hydrogens = chemicalInformation.countHydrogens(smileAnswer, tokens);
	// alert("Hydrogens: " + hydrogens);

	totalWeight += hydrogens;

	// alert("Total weight: " + totalWeight);

	if (totalWeight == molecularWeight) {
	    return true;
	}
	tmp = totalWeight;

	return false;
    };

    /**
     * override {@link searcher#search}. will return the approriate message from the question file.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.search = function(smileAnswer) {
	if (this.test(smileAnswer)) {
	    if (config.APPEND_CORRECT_VALUES) {
		return this.getfoundMsg() + "<span class='correctColor'>(" + molecularWeight + ")</span>";
	    }
	    return this.getfoundMsg();
	}

	if (config.PREPEND_INCORRECT_VALUES) {
	    var me = tmp;
	    tmp = 0;
	    return "<span class='notCorrectColor'>(" + me + ")</span> " + this.getNotFoundMsg();
	}
	return this.getNotFoundMsg();
    };

    this.getMolecularWeight = function() {
	return molecularWeight;
    };

}
// inherit
molecularWeightSearcher.prototype = new searcher();
molecularWeightSearcher.prototype.constructor = molecularWeightSearcher;

/**
 * Searcher for Number of Carbons.
 * 
 * @class
 * 
 * @extends searcher
 * @param {Number}
 *                nm number of carbons
 * 
 * @tutorial ArchitectureOverview
 * 
 */
function numCarbonSearcher(nm, correctMsg, wrongMsg) {

    searcher.call(this, null, correctMsg, wrongMsg);

    var numCarbon = new Number(nm).valueOf();
    var tmp = 0;

    /**
     * override {@link searcher#test}. will return true if the number of Carbons in the answer is equal to the number of Carbons in
     * the question.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.test = function(smileAnswer) {
	var tokens = Parser.parse(smileAnswer);
	var numC = chemicalInformation.countAtomsOf("C", tokens);
	if (numC == numCarbon) {
	    return true;
	}
	tmp = numC;
	return false;
    };

    /**
     * override {@link searcher#search}. will return the approriate message from the question file.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.search = function(smileAnswer) {

	if (this.test(smileAnswer)) {
	    if (config.APPEND_CORRECT_VALUES) {
		return this.getfoundMsg() + "<span class='correctColor'>(" + numCarbon + ")</span>";
	    }
	    return this.getfoundMsg();
	}

	if (config.PREPEND_INCORRECT_VALUES) {
	    var me = tmp;
	    tmp = 0;
	    return "<span class='notCorrectColor'>(" + me + ")</span> " + this.getNotFoundMsg();
	}

    };

    this.getNumCarbons = function() {
	return numCarbon;
    };

}
// inherit
numCarbonSearcher.prototype = new searcher();
numCarbonSearcher.prototype.constructor = numCarbonSearcher;

/**
 * Searcher for Number of types of Carbon.
 * 
 * @class
 * 
 * @extends searcher
 * @param {Number}
 *                nm number of types of Carbon
 * 
 * @tutorial ArchitectureOverview
 */
function numTypesCarbonSearcher(nm, correctMsg, wrongMsg) {

    searcher.call(this, null, correctMsg, wrongMsg);

    var numCarbon = nm;
    /**
     * override {@link searcher#test}. will return the approriate message from the question file.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.test = function(smileAnswer) {
	return false;
    };

    /**
     * override {@link searcher#search}. will return the approriate message from the question file.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.search = function(smileAnswer) {

	return "Needs to be implemented)";

    };

    this.getNumTypesCarbons = function() {
	return numCarbon;
    };

}
// inherit
numTypesCarbonSearcher.prototype = new searcher();
numTypesCarbonSearcher.prototype.constructor = numTypesCarbonSearcher;

/**
 * Searcher for Number of hydrogens.
 * 
 * @class
 * 
 * @extends searcher
 * @param {Number}
 *                nm number of hydrogens
 * 
 * @tutorial ArchitectureOverview
 */
function numHydrogenSearcher(nm, correctMsg, wrongMsg) {

    searcher.call(this, null, correctMsg, wrongMsg);

    var numHydrogen = new Number(nm).valueOf();
    var tmp = 0;

    /**
     * override {@link searcher#test}. will return true if the number of hydrogens in the answer is equal to the number of Carbons
     * in the question.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.test = function(smileAnswer) {
	var tokens = Parser.parse(smileAnswer);
	numH = chemicalInformation.countHydrogens(smileAnswer, tokens);
	if (numH == numHydrogen) {
	    return true;
	}
	tmp = numH;
	return false;
    };

    /**
     * override {@link searcher#search}. will return the approriate message from the question file.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.search = function(smileAnswer) {

	if (this.test(smileAnswer)) {
	    if (config.APPEND_CORRECT_VALUES) {
		return this.getfoundMsg() + "<span class='correctColor'>(" + numHydrogen + ")</span>";
	    }
	    return this.getfoundMsg();
	}

	if (config.PREPEND_INCORRECT_VALUES) {
	    var me = tmp;
	    tmp = 0;
	    return "<span class='notCorrectColor'>(" + me + ")</span> " + this.getNotFoundMsg();
	}
    };

    this.getNumHydrogens = function() {
	return numHydrogen;
    };

}
// inherit
numHydrogenSearcher.prototype = new searcher();
numHydrogenSearcher.prototype.constructor = numHydrogenSearcher;

/**
 * Searcher for Number of types of hydrogens.
 * 
 * @class
 * 
 * @extends searcher
 * @param {Number}
 *                nm number of types of hydrogens
 * 
 * @tutorial ArchitectureOverview
 */
function numTypesHydrogenSearcher(nm, correctMsg, wrongMsg) {

    searcher.call(this, null, correctMsg, wrongMsg);

    var numHydrogen = nm;
    /**
     * override {@link searcher#test}. will return the approriate message from the question file.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.test = function(smileAnswer) {
	return false;
    };
    /**
     * override {@link searcher#search}. will return the approriate message from the question file.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.search = function(smileAnswer) {

	return "Needs to be implemented)";

    };

    this.getNumTypesHydrogens = function() {
	return numHydrogen;
    };

}
// inherit
numTypesHydrogenSearcher.prototype = new searcher();
numTypesHydrogenSearcher.prototype.constructor = numTypesHydrogenSearcher;

/**
 * Searcher for charge.
 * 
 * @class
 * 
 * @extends searcher
 * @param {Number}
 *                nm the charge from the question file
 * 
 * @tutorial ArchitectureOverview
 * 
 */
function chargeSearcher(nm, wrongMsg) {

    searcher.call(this, null, "", wrongMsg);

    var charge = nm;
    var tmp = 0;

    /**
     * override {@link searcher#test}. will return true if the charge of the answer is equal to the charge of the question.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.test = function(smileAnswer) {
	var tokens = Parser.parse(smileAnswer);
	var fCharge = chemicalInformation.countCharge(tokens);
	if (fCharge == charge) {
	    return true;
	}
	tmp = fCharge;
	return false;
    };

    /**
     * override {@link searcher#search}. will return the approriate message from the question file.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.search = function(smileAnswer) {

	if (this.test(smileAnswer)) {
	    return this.getfoundMsg();
	}
	if (config.PREPEND_INCORRECT_VALUES) {
	    var me = tmp;
	    tmp = 0;
	    return "<span class='notCorrectColor'>(" + me + ")</span> " + this.getNotFoundMsg();
	}
	return this.getNotFoundMsg();
    };

    this.getCharge = function() {
	return charge;
    };

}
// inherit
chargeSearcher.prototype = new searcher();
chargeSearcher.prototype.constructor = chargeSearcher;

/**
 * Searcher for rings.
 * 
 * @class
 * 
 * @extends searcher
 * @param {Number}
 *                nm the number of rings from the question file.
 * 
 * @tutorial ArchitectureOverview
 */
function ringsSearcher(nm, wrongMsg) {

    searcher.call(this, null, "", wrongMsg);

    var rings = nm;
    var tmp = 0;

    /**
     * override {@link searcher#test}. will return true if the number of rings in the answer is equal to the number of rings in the
     * question.
     * 
     * @method
     * @access privileged
     * @param smileAnswer
     * 
     */
    this.test = function(smileAnswer) {
	var tokens = Parser.parse(smileAnswer);
	var numR = chemicalInformation.countRings(tokens);
	if (numR == rings) {
	    return true;
	}
	tmp = numR;
	return false;
    };

    /**
     * override {@link searcher#search}. will return the approriate message from the question file.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.search = function(smileAnswer) {

	if (this.test(smileAnswer)) {
	    return this.getfoundMsg(); // empty message
	}

	if (config.PREPEND_INCORRECT_VALUES) {
	    var me = tmp;
	    tmp = 0;
	    return "<span class='notCorrectColor'>(" + me + ")</span> " + this.getNotFoundMsg();
	}
	return this.getNotFoundMsg();

    };

    this.getNumberOfRings = function() {
	return rings;
    };

}
// inherit
ringsSearcher.prototype = new searcher();
ringsSearcher.prototype.constructor = ringsSearcher;

/**
 * Searcher for Functional Groups.
 * 
 * @class
 * 
 * @extends searcher
 * @param {String}
 *                fgString fgString is a string of the following form.
 *                <p>
 *                FG1,FG2,..FGN,
 * @param {String}
 *                AllFoundMsg
 * @param {String}
 *                AllNotFoundMsg
 * 
 * @tutorial ArchitectureOverview
 * 
 * 
 */
function functionalGroupsSearcher(fgString, AllFoundMsg, AllNotFoundMsg) {

    searcher.call(this, null, AllFoundMsg, AllNotFoundMsg);
    var fGroups = null;
    var noGroups = false;

    // deal with when we instatiate one
    // in the analyzer constructor and reset function.
    if (fgString == null) {
	noGroups = true;
	fgString = "";
    }

    fgString = fgString.replace(/,\s*$/, '');
    fGroups = fgString.split(",");

    if (noGroups) {
	fGroups = new Array(); // empty array
    }

    /**
     * override {@link searcher#test}. will return true if all of the functional groups in the quesion file are found in the
     * answer.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.test = function(smileAnswer) {
	
	var fGroupsCount = 0;
	var tokens = Parser.parse(smileAnswer);
	var ocloObj = ocloInfo.getOcloObject(smileAnswer, tokens);
	var answerFgroups = ocloObj.functionalGroups; 
	
	for (var i = 0; i < fGroups.length; i++) {
	    
	    if(answerFgroups.contains(fGroups[i])){
		fGroupsCount++;
		continue;
	    }else{
		if(fGroups[i] == "amine"){
		    for (var j = 0; j < answerFgroups.length; j++) {
			var astring = answerFgroups[j];
			if(astring.indexOf("amine") > -1){
			    fGroupsCount++; 
			    break;
			}
		    }
		}}
		
	    }
	    
	if (fGroupsCount == fGroups.length) {
	    return true;
	}
	return false;
    };

    /**
     * override {@link searcher#search}. will return the approriate message from the question file.
     * 
     * @method
     * @access protected
     * @param {String}
     *                smileAnswer The submitted SMILE string.
     * 
     */
    this.search = function(smileAnswer) {
	if (noGroups) {
	    return "";
	}

	if (this.test(smileAnswer)) {
	    if (config.APPEND_CORRECT_VALUES) {
		var groups = "";
		for (var i = 0; i < fGroups.length; i++) {
		    var group = fGroups[i];
		    groups += group;
		    if (i != fGroups.length - 1) {
			groups += ",";
		    }
		}

		return this.getfoundMsg() + "<span class='correctColor'>(" + groups + ")</span>";
	    }
	    return this.getfoundMsg();
	}

	return this.getNotFoundMsg();
    };

    this.getFunctionalGroups = function() {
	return fGroups;
    };
    this.getNoGroups = function() {
	return noGroups;
    };

}
// inherit
functionalGroupsSearcher.prototype = new searcher();
functionalGroupsSearcher.prototype.constructor = functionalGroupsSearcher;

// =======================================================================================

/**
 * Constructor for a new Loci Searcher
 * 
 * @class
 * @param {Number}
 *                numOfLoci
 * @param {String}
 *                notfoundMsg The string when the number of Loci is incorret in an answer.
 * 
 * 
 * @classdesc lociSearcher is used to search an answer for the correct number of loci and return a message based on the search. What
 *            counts as a loci was determined by what was counted in the original Java version of the application.
 *            <p>
 *            The search is based on the tutor.java version. It will count numbers and capital letters N, found outside of [], per
 *            tutor.java's code.
 * 
 * 
 */
function lociSearcher(numOfLoci, notfoundMsg) {

    var nLoci = new Number(numOfLoci);
    var negativeMsg = notfoundMsg;

    if (nLoci == null) {
	nLoci = new Number(-1);
    }

    if (negativeMsg == null) {
	negativeMsg = "";
    }

    // clean up the input. trim spaces
    // and replace end of lines chars.
    negativeMsg = negativeMsg.replace(/^\s+|\s+$/g, '');
    negativeMsg = negativeMsg.replace(/\n|\r|\f/g, '');

    /**
     * Searches an answer, counting the number of Loci present and returns either the notFoundMsg, if the number found in the answer
     * is not the same as the Loci in the answer or the empty string if it is.
     * 
     * @param {String}
     *                answer, the input to test
     * @returns {String} the empty or not found string.
     * @method
     * @access protected
     */
    this.search = function(answer) {

	var nLociFound = new Number(0);
	var closedBrackets = new Array();
	var openBrackets = new Array();

	// find the index(es) of open square brackets
	regex = /\[/g;
	match = regex.exec(answer);
	while (match != null) {
	    openBrackets.push(match.index);
	    match = regex.exec(answer);
	}

	// find the index(es) of closed square brackets
	regex = /\]/g;
	match = regex.exec(answer);
	while (match != null) {
	    closedBrackets.push(match.index);
	    match = regex.exec(answer);
	}

	// before the first [ value
	var eob = 0;
	if (openBrackets.length > 0) {
	    eob = openBrackets[0];
	}
	// after the last ] value
	var boe = 0;
	if (closedBrackets.length > 0) {
	    boe = closedBrackets[closedBrackets.length - 1];
	}

	// mmb will be true if the number of open and closed
	// brackets is mismatched.
	var mmb = openBrackets.length != closedBrackets.length;

	// find all numbers or capitial N's
	regex = /(\d\d*|NN*)/g;
	match = regex.exec(answer);

	while (match != null) {
	    // before first [, after last ], mismatched and no square brackets
	    // in answer. counts.
	    if (match.index < eob || match.index > boe || mmb) {
		nLociFound++;
		match = regex.exec(answer);
		continue; // the while
	    }
	    // check in between ] and [
	    var inSquare = false;
	    // iterate over the equally sized arrays
	    for (var i = 0; i < openBrackets.length; i++) {
		if (match.index > openBrackets[i] && match.index < closedBrackets[i]) {
		    inSquare = true;
		    break; // the for
		}

	    }
	    if (!inSquare) { // then count it.
		nLociFound++;

	    }
	    // next match if there is one.
	    match = regex.exec(answer);
	}

	if (nLociFound.valueOf() != nLoci.valueOf()) {
	    return negativeMsg;
	}

	return "";

    };

}
