/*jslint devel: true, browser: false, unparam: true, sub: true, windows: false, vars: true, white: true, maxerr: 50, indent: 4 */

/*global $,plt,EXPORTS,document,window*/
(function() {
    "use strict";

    var VOID = plt.baselib.constants.VOID_VALUE;
    var PAUSE = plt.runtime.PAUSE;
    var makePrimitiveProcedure = plt.baselib.functions.makePrimitiveProcedure;
    var makeClosure = plt.baselib.functions.makeClosure;
    var makeCheckArgumentType = plt.baselib.check.makeCheckArgumentType;
    var checkSymbolOrString = plt.baselib.check.checkSymbolOrString;
    var checkString = plt.baselib.check.checkString;
    var checkJSFunction = makeCheckArgumentType(function(x) { return typeof(x) === 'function'; },
                                                "js function");
    var checkAny = makeCheckArgumentType(function(x) { return true; },
                                         "any");

    var isJsString = function(x) { return typeof(x) === 'string'; };
    var checkJsString = makeCheckArgumentType(isJsString, 'JavaScript string');



    var isJsNumber = function(x) { return typeof(x) === 'number'; };
    var checkNumber = plt.baselib.check.checkNumber;
    var checkJsNumber = makeCheckArgumentType(isJsNumber, 'JavaScript number');



    //////////////////////////////////////////////////////////////////////
    /* Lesser General Public License for more details.
     *
     * You should have received a copy of the GNU Lesser General Public
     * License along with this library; if not, write to the Free Software
     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
     *
     * Contact information:
     *   Dao Gottwald  <dao at design-noir.de>
     *
     * @version  1.6
     * @url      http://design-noir.de/webdev/JS/loadScript/
     */
    var _loadScriptQueue = {};
    var loadScript = function(url, callback, onError) {
        var queue = _loadScriptQueue;
        if (url in queue) { // script is already in the document
	    if (callback) {
	        if (queue[url]) // still loading
		    queue[url].push(callback);
	        else // loaded
		    callback();
	    }
	    return;
        }
        queue[url] = callback ? [callback] : [];
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.onload = script.onreadystatechange = function() {
	    if (script.readyState && script.readyState != "loaded" && script.readyState != "complete")
	        return;
	    script.onreadystatechange = script.onload = null;
	    document.getElementsByTagName("head")[0].removeChild(script);
	    var work = queue[url];
	    delete(queue[url]);
	    while (work.length)
	        work.shift()();
        };
        script.onerror = function() {
	    script.onreadystatechange = script.onload = null;
	    document.getElementsByTagName("head")[0].removeChild(script);
            onError();
        };
        script.src = url;
        document.getElementsByTagName("head")[0].appendChild(script);
    };



    EXPORTS['alert'] =
        makePrimitiveProcedure(
            'alert',
            1,
            function(MACHINE) {
                var elt = MACHINE.e[MACHINE.e.length - 1];
                alert(String(elt));
                return VOID;
            });

    EXPORTS['js-eval'] =
        makePrimitiveProcedure(
            'js-eval',
            1,
            function(MACHINE) {
                var elt = MACHINE.e[MACHINE.e.length - 1];
                var obj = (0,eval)('(' + String(elt) + ')');
                return obj;
            });

    EXPORTS['load-script'] =
        makeClosure(
            'load-script',
            1,
            function(MACHINE) {
                var url = checkString(MACHINE, 'load-string', 0);
                PAUSE(
                    function(restart) {
                        var onload = function() {
                            restart(function(MACHINE) {
                                plt.runtime.finalizeClosureCall(
                                    MACHINE, 
                                    VOID);
                            });
                        };
                        var onerror = function(e) {
                            restart(function(MACHINE) {
                                plt.baselib.exceptions.raiseFailure(
                                    MACHINE, 
                                    plt.baselib.format.format(
                                        "unable to load ~a: ~a",
                                        [url,
                                         ((e && e.message) ? e.message : "unknown error")]));
                            });
                        };
                        loadScript(url.toString(),
                                   onload,
                                   onerror);
                    }
                );                
            },
            void(0));
            


    EXPORTS['body'] = $(document.body);

    EXPORTS['$'] =
        makePrimitiveProcedure(
            '$',
            1,
            function(MACHINE) {
                var obj = MACHINE.e[MACHINE.e.length - 1];
                return $(obj);
            });

    EXPORTS['call-method'] = 
        makePrimitiveProcedure(
            'call-method',
            plt.baselib.arity.makeArityAtLeast(2),
            function(MACHINE) {
                var obj = MACHINE.e[MACHINE.e.length - 1];
                var methodName = MACHINE.e[MACHINE.e.length - 2];
                var args = [], i;
                for (i = 0; i < MACHINE.a - 2; i = i+1) {
                    args.push(MACHINE.e[MACHINE.e.length -1 - 2 - i]);
                }
                var result = obj[methodName].apply(obj, args);
                return result;
            });


    // Lift JavaScript functions to Whalesong functions.
    EXPORTS['js-function->procedure'] =
        makePrimitiveProcedure(
            'js-function->procedure',
            1,
            function(MACHINE) {
                var f = checkJSFunction(MACHINE, 'js function', 0);
                return makePrimitiveProcedure(
                    'lifted js function',
                    plt.baselib.arity.makeArityAtLeast(0),
                    function(MACHINE) {
                        var args = [], i;
                        for (i = 0; i < MACHINE.a ; i = i+1) {
                            args.push(MACHINE.e[MACHINE.e.length - 1 - i]);
                        }
                        return f.apply(null, args);
                    });
            });

    EXPORTS['js-async-function->procedure'] =
        makePrimitiveProcedure(
            'js-async-function->procedure',
            1,
            function(MACHINE) {
                var f = checkJSFunction(MACHINE, 'js function', 0);
                return makeClosure(
                    'lifted asynchronous js function',
                    plt.baselib.arity.makeArityAtLeast(0),
                    function(MACHINE) {
                        var args = [], i;
                        for (i = 0; i < MACHINE.a ; i = i+1) {
                            args.push(MACHINE.e[MACHINE.e.length - 1 - i]);
                        }
                        MACHINE.e.length -= MACHINE.a;
                        MACHINE.a = 0;
                        return plt.runtime.PAUSE(
                            function(restart) {
                                var onFail = function(e) {
                                    restart(function(MACHINE) {
                                        plt.baselib.exceptions.raiseFailure(
                                            MACHINE, 
                                            plt.baselib.format.format(
                                                "~a",
                                                [((e && e.message) ? e.message : e || "unknown error")]));
                                        
                                    });
                                };
                                var onSuccess = function(v) {
                                    // Common case, return single argument
                                    if (arguments.length === 1) {
                                        restart(function(MACHINE) {
                                            MACHINE.a = 0;
                                            plt.runtime.finalizeClosureCall(MACHINE, v);
                                        });
                                    } else {
                                        // General case: return multiple arguments
                                        var args = Array.prototype.slice.call(arguments, 0);
                                        args.unshift(MACHINE);
                                        restart(function(MACHINE) {
                                            MACHINE.a = 0;
                                            plt.runtime.finalizeClosureCall.apply(
                                                null, args);
                                        });
                                    }
                                }
                                args.unshift(onFail);
                                args.unshift(onSuccess);
                                return f.apply(null, args);
                            });
                    });
            });


    EXPORTS['window'] = window;


    EXPORTS['get-attr'] =
        makePrimitiveProcedure(
            'get-attr',
            plt.baselib.arity.makeArityAtLeast(2),
            function(MACHINE) {
                var obj = checkAny(MACHINE, 'get-attr', 0), attr, i;
                for (i = 1; i < MACHINE.a; i = i + 1) {
                    attr = checkSymbolOrString(MACHINE, 'get-attr', i).toString();
                    obj = obj[attr];
                }
                return obj;
            });


    EXPORTS['set-attr!'] =
        makePrimitiveProcedure(
            'set-attr!',
            3,
            function(MACHINE) {
                var obj = checkAny(MACHINE, 'set-attr!', 0);
                var attr = checkSymbolOrString(MACHINE, 'set-attr!', 1).toString();
                var val = checkAny(MACHINE, 'set-attr!', 2);
                obj[attr] = val;
                return VOID;
            });

    EXPORTS['js-string?'] = 
        makePrimitiveProcedure(
            'js-string?',
            1,
            function(MACHINE) {
                return typeof(checkAny(MACHINE, 'js-string?', 0)) === 'string';
            });

    EXPORTS['string->js-string'] =
        makePrimitiveProcedure(
            'string->js-string',
            1,
            function(MACHINE) {
                return checkString(MACHINE, 'string->js-string', 0).toString();
            });

    EXPORTS['js-string->string'] =
        makePrimitiveProcedure(
            'js-string->string',
            1,
            function(MACHINE) {
                return checkJsString(MACHINE, 'string->js-string', 0);
            });




    EXPORTS['js-number?'] = 
        makePrimitiveProcedure(
            'js-number?',
            1,
            function(MACHINE) {
                return isJsNumber(checkAny(MACHINE, 'js-string?', 0));
            });    
    EXPORTS['js-number->number'] = 
        makePrimitiveProcedure(
            'js-number->number',
            1,
            function(MACHINE) {
                return plt.baselib.numbers.makeFloat(checkJsNumber(MACHINE, 'js-string?', 0));
            });    

    EXPORTS['number->js-number'] = 
        makePrimitiveProcedure(
            'number->js-number',
            1,
            function(MACHINE) {
                return plt.baselib.numbers.toFixnum(checkNumber(MACHINE, 'js-string?', 0));
            });    


    EXPORTS['js-null?'] = 
        makePrimitiveProcedure(
            'js-null?',
            1,
            function(MACHINE) {
                return checkAny(MACHINE, 'js-null?', 0) === null;
            });    

    EXPORTS['js-null'] = null;



    // Javascript-specific extensions.  A small experiment.
    EXPORTS['viewport-width'] = 
        makePrimitiveProcedure(
            'viewport-width',
            0,
            function(MACHINE) {
                return $(window).width();
            });

    EXPORTS['viewport-height'] = 
        makePrimitiveProcedure(
            'viewport-height',
            0,
            function(MACHINE) {
                return $(window).height();
            });


    EXPORTS['in-javascript-context?'] =
        makePrimitiveProcedure(
            'in-javascript-context?',
            0,
            function(MACHINE) {
                return true;
            });
}());