/* 
 * Generally useful tools for modules in Tree Illustrator. They might be
 * concerned with display logic, geometry and transformation, text formatting;
 * anything that spans our normal lines of responsibility.
 */

function jiggle( range ) {
    // Return a number +- zero, within this range
    return Math.round(Math.random() * range * 2) - range; 
}


/* "Sniffers" to guess the format of user-entered tree data. 
 * N.B. these don't need to be fool-proof; they're just used to pre-select
 * the most likely format. 
 */
function isProbablyNewick(data) {
    if (typeof(data) !== 'string') {
        return false;
    }
    data = $.trim(data);
    // Look for expected start and end marks
    // N.B. this will reject a valid (but trivial) string like 'A;'
    if (data.startsWith('(') && data.endsWith(');')) {
        return true;
    }
    return false;
}

var matchesNEXUSBlockStarter = new RegExp('begin \\w+;', 'i');
function isProbablyNEXUS(data) {
    if (typeof(data) !== 'string') {
        return false;
    }
    // Look for required(?) first line
    if ($.trim(data).startsWith("#nexus") || $.trim(data).startsWith("#NEXUS")) {
        return true;
    }
    // ... or accept typical NEXUS block starter
    if (matchesNEXUSBlockStarter.test(data)) {
        return true;
    }
    return false;
}

function isProbablyNeXML(data) {
    /* NOTE that this is the most "expensive" sniffer, so it's probably
     * best to check the others first.
     */
    var testXML;
    if (data instanceof XMLDocument) {
        testXML = data;
    } else {
        try {
            testXML = $.parseXML(data);
            if (!testXML) {
                // if data is not a string, result is null 
                return false;
            }
        } catch (err) {
            // var failed to parse as XML
            ///console.error(err);
            return false;
        }
    }
    if ($(testXML).children().length === 0) {
        // XML is strangely empty
        return false;
    }
    var rootNodeName = $(testXML).children()[0].nodeName;
    switch( rootNodeName ) {
        case 'nex:nexml':
            break;
        case 'nexml':
            break;
        default:
            return false;
    }
    return true;
}

function isProbablyRPhylo(data) {
    // test for valid JSON/JS nested array, then examine its structure
    var testObj;
    if ($.isPlainObject(data)) {
        testObj = data;
    } else {
        try {
            testObj = eval(data);
            if (!testObj) {
                // if data is not a string, result is null 
                return false;
            }
        } catch (err) {
            // var failed to evaluate to a JS object
            console.error(err);
            return false;
        }
    }
    // check for basic phylo fields
    return ('edge' in testObj) && ('tip.label' in testObj) && ('Nnode' in testObj);
}

/* Copied from vg.data.phylogram.js, for wider use (but keeping the code in
 * both places, to minimize dependencies in the Vega transform).
 */
function radiansToDegrees(r) {
    return (r * 180 / Math.PI);
}
function degreesToRadians(d) {
    return (d * Math.PI / 180);
}
function normalizeDegrees(d) {
    // convert to positive integer, e.g. -90 ==> 270
    return (d + (360 * 3)) % 360;
}

// export some members as a simple API
var api = [
    'jiggle',
    'isProbablyNewick',
    'isProbablyNEXUS',
    'isProbablyNeXML',
    'isProbablyRPhylo',
    'radiansToDegrees',
    'degreesToRadians',
    'normalizeDegrees'
];
$.each(api, function(i, methodName) {
    // populate the default 'module.exports' object
    exports[ methodName ] = eval( methodName );
});


@@ -2363,113 +2363,766 @@
 tObj
-;%0A    if ($.isPlainObject
+ = coerceToJSObject(data);%0A    if (!testObj) return false;%0A    // check for basic phylo fields%0A    return ('edge' in testObj) && ('tip.label' in testObj) && ('Nnode' in testObj);%0A    // TODO: Is tip.label an optional element? Choose something else instead?%0A%7D%0A%0Afunction isProbablyNexson
 (data)
-)
  %7B%0A    
-    testObj = 
+// test for valid JSON/JS nested array, then examine its structure%0A    var testObj = coerceToJSObject(
 data
+)
 ;%0A    
-%7D else %7B%0A        try %7B%0A            testObj
+if (!testObj) return false;%0A    // check for basic nexson structure%0A    return ('data' in testObj) && ('nexml' in testObj.data);%0A%7D%0A%0Afunction coerceToJSObject(data) %7B%0A    // return a proper JS object (or null if coercion fails)%0A    if ($.isPlainObject(data)) %7B%0A        // this is fine as-is%0A        return data;%0A    %7D%0A    try %7B%0A        var jsObject
  = e
@@ -3144,32 +3144,25 @@
     
-   
- 
 if (!
-testObj) %7B%0A    
+jsObject) %7B%0A
     
@@ -3203,36 +3203,33 @@
 , result is null
- %0A  
+%0A
               re
@@ -3224,36 +3224,29 @@
         
-  
 return 
-false;%0A    
+null;%0A
         
@@ -3243,36 +3243,32 @@
 ;%0A        %7D%0A    
- 
-   
 %7D catch (err) %7B%0A
@@ -3259,36 +3259,32 @@
 %7D catch (err) %7B%0A
-    
         // var f
@@ -3324,20 +3324,16 @@
         
-    
 console.
@@ -3342,179 +3342,120 @@
 ror(
-err);%0A            return false;%0A        %7D%0A    %7D%0A    // check for basic phylo fields%0A
+%22Coercion to JS object failed!%22);%0A        console.error(err);%0A   
     
+ 
 return 
-('edge' in testObj) && ('tip.label' in testObj) && ('Nnode' in testObj)
+null;%0A    %7D%0A    return jsObject
 ;%0A%7D%0A
-%0A
 /* C

Add sniffer for Nexson