// Helper functions for whalesong.
//
// Note: this originally came from js-vm, and may have cruft that
// doesn't belong in whalesong.  I need to clean this up.



if (! this['plt']) { this['plt'] = {}; }

// Helpers library: includes a bunch of helper functions that will be used
//
//
// FIXME: there's a circularity between this module and types, and that circularly
// should not be there!


//////////////////////////////////////////////////////////////

// File of helper functions for primitives and world.


(function(scope) {
    var helpers = {};
    scope.helpers = helpers;


    // types refers to plt.types, and will be initialized later.
    var types = scope['types'];
    scope.link.ready('types', 
                     function() { 
                         types = scope['types'];
                     });






    // forEachK: CPS( array CPS(array -> void) (error -> void) -> void )
    // Iterates through an array and applies f to each element using CPS
    // If an error is thrown, it catches the error and calls f_error on it
    var forEachK = function(a, f, f_error, k) {
	var forEachHelp = function(i) {
	    if( i >= a.length ) {
		if (k) {
		    return k();
		} else {
		    return;
		}
	    }

	    try {
		return f(a[i], function() { return forEachHelp(i+1); });
	    } catch (e) {
		f_error(e);
	    }
	};
	return forEachHelp(0);
    };


    // reportError: (or exception string) -> void
    // Reports an error to the user, either at the console
    // if the console exists, or as alerts otherwise.
    var reportError = function(e) {
	var reporter;
	if (typeof(console) != 'undefined' && 
	    typeof(console.log) != 'undefined') {
	    reporter = (function(x) { console.log(x); });
	} else {
	    reporter = (function(x) { alert(x); });
	}
	if (typeof e == 'string') {
	    reporter(e);
	} else if ( types.isSchemeError(e) ) {
	    if ( types.isExn(e.val) ) {
		reporter( types.exnMessage(e.val) );
	    }
	    else {
		reporter(e.val);
	    }
	} else if ( types.isInternalError(e) ) {
	    reporter(e.val);
	} else if (e.message) {
	    reporter(e.message);
	} else {
	    reporter(e.toString());
	}
        //		if (plt.Kernel.lastLoc) {
        //			var loc = plt.Kernel.lastLoc;
        //			if (typeof(loc) === 'string') {
        //			reporter("Error was raised around " + loc);
        //			} else if (typeof(loc) !== 'undefined' &&
        //				   typeof(loc.line) !== 'undefined') {
        //			reporter("Error was raised around: "
        //				 + plt.Kernel.locToString(loc));
        //			}
        //		}
    };


    var raise = function(v) {
	throw types.schemeError(v);
    };



//     var throwCheckError = function(details, pos, args) {
// 	var errorFormatStr;
// 	if (args && args.length > 1) {
// 	    var errorFormatStrBuffer = ['~a: expects type <~a> as ~a arguments, given: ~s; other arguments were:'];
// 	    for (var i = 0; i < args.length; i++) {
// 		if ( i != pos-1 ) {
// 		    errorFormatStrBuffer.push(toWrittenString(args[i]));
// 		}
// 	    }
// 	    errorFormatStr = errorFormatStrBuffer.join(' ');
// 	}
// 	else {
// 	    errorFormatStr = "~a: expects argument of type <~a>, given: ~s";
// 	    details.splice(2, 1);
// 	}

// 	raise( types.incompleteExn(types.exnFailContract,
// 				   helpers.format(errorFormatStr, details),
// 				   []) );
//     };



//     var check = function(x, f, functionName, typeName, position, args) {
// 	if ( !f(x) ) {
// 	    throwCheckError([functionName,
// 			     typeName,
// 			     helpers.ordinalize(position),
// 			     x],
// 			    position,
// 			    args);
// 	}
//     };

    var isList = function(x) {
	var seenPairs = plt.baselib.hash.makeLowLevelEqHash();
	while (true) {
	    if (seenPairs.containsKey(x)) {
		return true;
	    } else if (x === types.EMPTY) {
		return true;
	    } else if (types.isPair(x)) {
		seenPairs.put(x, true);
		x = x.rest();
	    } else {
		return false;
	    }
	}
    };

    var isListOf = function(x, f) {
	var seenPairs = plt.baselib.hash.makeLowLevelEqHash();
	while (true) {
	    if (seenPairs.containsKey(x)) {
		return true;
	    } else if (x === types.EMPTY) {
		return true;
	    } else if (types.isPair(x)) {
		seenPairs.put(x, true);
		if (f(x.first())) {
		    x = x.rest();
		} else {
		    return false;
		}
	    } else {
		return false;
	    }
	}
    };

//     var checkListOf = function(lst, f, functionName, typeName, position, args) {
// 	if ( !isListOf(lst, f) ) {
// 	    helpers.throwCheckError([functionName,
// 				     'list of ' + typeName,
// 				     helpers.ordinalize(position),
// 				     lst],
// 				    position,
// 				    args);
// 	}
//     };


    //	// remove: array any -> array
    //	// removes the first instance of v in a
    //	// or returns a copy of a if v does not exist
    //	var remove = function(a, v) {
    //		for (var i = 0; i < a.length; i++) {
    //			if (a[i] === v) {
    //				return a.slice(0, i).concat( a.slice(i+1, a.length) );
    //			}
    //		}
    //		return a.slice(0);
    //	};

    // map: array (any -> any) -> array
    // applies f to each element of a and returns the result
    // as a new array
    var map = function(f, a) {
	var b = new Array(a.length);
	for (var i = 0; i < a.length; i++) {
	    b[i] = f(a[i]);
	}
	return b;
    };


    var concatMap = function(f, a) {
	var b = [];
	for (var i = 0; i < a.length; i++) {
	    b = b.concat( f(a[i]) );
	}
	return b;
    };


    var schemeListToArray = function(lst) {
	var result = [];
	while ( !lst.isEmpty() ) {
	    result.push(lst.first());
	    lst = lst.rest();
	}
	return result;
    }

    // deepListToArray: any -> any
    // Converts list structure to array structure.
    var deepListToArray = function(x) {
	var thing = x;
	if (thing === types.EMPTY) {
	    return [];
	} else if (types.isPair(thing)) {
	    var result = [];
	    while (!thing.isEmpty()) {
		result.push(deepListToArray(thing.first()));
		thing = thing.rest();
	    }
	    return result;
	} else {
	    return x;
	}
    }


    var flattenSchemeListToArray = function(x) {
	if ( !isList(x) ) {
	    return [x];
	}

	var ret = [];
	while ( !x.isEmpty() ) {
	    ret = ret.concat( flattenSchemeListToArray(x.first()) );
	    x = x.rest();
	}
	return ret;
    };



    var ordinalize = function(n) {
	// special case for 11th:
	if ( n % 100 == 11 ) {
	    return n + 'th';
	}
	var res = n;
	switch( n % 10 ) {
	case 1: res += 'st'; break;
	case 2: res += 'nd'; break;
	case 3: res += 'rd'; break;
	default: res += 'th'; break;
	}
	return res;
    }


//     var wrapJsValue = function(x) {
// 	if (x === undefined) {
// 	    return types.jsValue('undefined', x);
// 	}
// 	else if (x === null) {
// 	    return types.jsValue('null', x);
// 	}
// 	else if (typeof(x) == 'function') {
// 	    return types.jsValue('function', x);
// 	}
// 	else if ( x instanceof Array ) {
// 	    return types.jsValue('array', x);
// 	}
// 	else if ( typeof(x) == 'string' ) {
// 	    return types.jsValue("'" + x.toString() + "'", x);
// 	}
// 	else {
// 	    return types.jsValue(x.toString(), x);
// 	}
//     };


    var getKeyCodeName = function(e) {
	var code = e.charCode || e.keyCode;
	var keyname;
	switch(code) {
	case 16: keyname = "shift"; break;
	case 17: keyname = "control"; break;
	case 19: keyname = "pause"; break;
	case 27: keyname = "escape"; break;
	case 33: keyname = "prior"; break;
	case 34: keyname = "next"; break;
	case 35: keyname = "end"; break;
	case 36: keyname = "home"; break;
	case 37: keyname = "left"; break;
	case 38: keyname = "up"; break;
	case 39: keyname = "right"; break;
	case 40: keyname = "down"; break;
	case 42: keyname = "print"; break;
	case 45: keyname = "insert"; break;
	case 46: keyname = String.fromCharCode(127); break;
	case 106: keyname = "*"; break;
	case 107: keyname = "+"; break;
	case 109: keyname = "-"; break;
	case 110: keyname = "."; break;
	case 111: keyname = "/"; break;
	case 144: keyname = "numlock"; break;
	case 145: keyname = "scroll"; break;
	case 186: keyname = ";"; break;
	case 187: keyname = "="; break;
	case 188: keyname = ","; break;
	case 189: keyname = "-"; break;
	case 190: keyname = "."; break;
	case 191: keyname = "/"; break;
	case 192: keyname = "`"; break;
	case 219: keyname = "["; break;
	case 220: keyname = "\\"; break;
	case 221: keyname = "]"; break;
	case 222: keyname = "'"; break;
	default: if (code >= 96 && code <= 105) {
	    keyname = (code - 96).toString();
	}
	    else if (code >= 112 && code <= 123) {
		keyname = "f" + (code - 111);
	    }
	    else {
		keyname = String.fromCharCode(code).toLowerCase();
	    }
	    break;
	}
	return keyname;
    };





    // maybeCallAfterAttach: dom-node -> void
    // walk the tree rooted at aNode, and call afterAttach if the element has
    // such a method.
    var maybeCallAfterAttach = function(aNode) {
	var stack = [aNode];
	while (stack.length !== 0) {
	    var nextNode = stack.pop();
	    if (nextNode.afterAttach) {
		nextNode.afterAttach(nextNode);
	    }
	    if (nextNode.hasChildNodes && nextNode.hasChildNodes()) {
		var children = nextNode.childNodes;
		for (var i = 0; i < children.length; i++) {
		    stack.push(children[i]);
		}
	    }
	}
    };








    // makeLocationDom: location -> dom
    // Dom type that has special support in the editor through the print hook.
    // The print hook is expected to look at the printing of dom values with
    // this particular structure.  In the context of WeScheme, the environment
    // will rewrite these to be clickable links.
    var makeLocationDom = function(aLocation) {
	var locationSpan = document.createElement("span");
	var idSpan = document.createElement("span");
	var offsetSpan = document.createElement("span");
	var lineSpan = document.createElement("span");
	var columnSpan = document.createElement("span");
	var spanSpan = document.createElement("span");

	locationSpan['className'] = 'location-reference';
	idSpan['className'] = 'location-id';
	offsetSpan['className'] = 'location-offset';
	lineSpan['className'] = 'location-line';
	columnSpan['className'] = 'location-column';
	spanSpan['className'] = 'location-span';

	idSpan.appendChild(document.createTextNode(String(aLocation.id)));
	offsetSpan.appendChild(document.createTextNode(String(aLocation.offset)));
	lineSpan.appendChild(document.createTextNode(String(aLocation.line)));
	columnSpan.appendChild(document.createTextNode(String(aLocation.column)));
	spanSpan.appendChild(document.createTextNode(String(aLocation.span)));

	locationSpan.appendChild(idSpan);
	locationSpan.appendChild(offsetSpan);
	locationSpan.appendChild(lineSpan);
	locationSpan.appendChild(columnSpan);   
	locationSpan.appendChild(spanSpan);

	return locationSpan;
    };


    var isLocationDom = function(thing) {
	return (thing
		&&
		(thing.nodeType === Node.TEXT_NODE ||
		 thing.nodeType === Node.ELEMENT_NODE)
		&&
		thing['className'] === 'location-reference');
    };









    // Inheritance.
    var heir = function(parentPrototype) {
	var f = function() {}
	f.prototype = parentPrototype;
	return new f();
    };



 





    // clone: object -> object
    // Copies an object.  The new object should respond like the old
    // object, including to things like instanceof
    var clone = function(obj) {
        var C = function() {}
        C.prototype = obj;
        var c = new C();
        for (property in obj) {
	    if (obj.hasOwnProperty(property)) {
	        c[property] = obj[property];
	    }
        }
        return c;
    };






    ////////////////////////////////////////////////

    helpers.forEachK = forEachK;
    helpers.reportError = reportError;
    helpers.raise = raise;

//     helpers.throwCheckError = throwCheckError;
    helpers.isList = isList;
    helpers.isListOf = isListOf;
//     helpers.check = check;
//     helpers.checkListOf = checkListOf;
    
    //	helpers.remove = remove;
    helpers.map = map;
    helpers.concatMap = concatMap;
    helpers.schemeListToArray = schemeListToArray;
    helpers.deepListToArray = deepListToArray;
    helpers.flattenSchemeListToArray = flattenSchemeListToArray;

    helpers.ordinalize = ordinalize;
//     helpers.wrapJsValue = wrapJsValue;

    helpers.getKeyCodeName = getKeyCodeName;

    helpers.maybeCallAfterAttach = maybeCallAfterAttach;

    helpers.makeLocationDom = makeLocationDom;
    helpers.isLocationDom = isLocationDom;


    helpers.heir = heir;



    helpers.clone = clone;


    scope.link.announceReady('helpers');
})(this['plt']);

/////////////////////////////////////////////////////////////////