/*jslint browser: true, unparam: true, vars: true, white: true, plusplus: true, maxerr: 50, indent: 4, forin: true */
/*global plt,MACHINE,$,EXPORTS,TreeCursor*/
(function($) {

    "use strict";

    var makePrimitiveProcedure = plt.baselib.functions.makePrimitiveProcedure;
    var makeClosure = plt.baselib.functions.makeClosure;
    var finalizeClosureCall = plt.baselib.functions.finalizeClosureCall;
    var PAUSE = plt.runtime.PAUSE;
    var isString = plt.baselib.strings.isString;
    var isSymbol = plt.baselib.symbols.isSymbol;
    var isList = plt.baselib.lists.isList;
    var isEmpty = plt.baselib.lists.isEmpty;
    var listLength = plt.baselib.lists.length;
    var makeList = plt.baselib.lists.makeList;
    var makePair = plt.baselib.lists.makePair;
    var makeSymbol = plt.baselib.symbols.makeSymbol;
    var isArityMatching = plt.baselib.arity.isArityMatching;
    var makeArityMismatchError = plt.baselib.exceptions.makeArityMismatchError;


    // EventHandler and the other classes here will be defined below.
    // We're just trying to keep jslint happy.
    var EventHandler, DomEventSource;



    // FIXME: as soon as we get real parameters, use parameters
    // instead.  Global: defines the currently running big bang.
    // Parameterized around the call to bigBang.
    var currentBigBangRecord;



    var resourceStructType = 
        MACHINE.modules['whalesong/resource/structs.rkt'].getExternalExports().get('struct:resource');

    var eventStructType = 
        MACHINE.modules['whalesong/web-world/event.rkt'].getExternalExports().get('struct:event');




    // Returns true if it is a DOM element.
    // Comes from:
    // http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object
    var isElement = function(o){
        return (
            typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
            o && typeof o === "object" && o.nodeType === 1 && typeof o.nodeName==="string"
        );
    };

    var shallowCloneNode = function(node) {
        var result = node.cloneNode(false);
        var i;
        // copy over the attributes as well
        if (node.attributes) {
            for (i = 0; i < node.attributes.length; i++) {
                $(result).attr(node.attributes[i].name,
                               node.attributes[i].value);
            }
        }
        $(result).data($(node).data());
        return result;
    };


    // Deeply clones a dom node.  
    //
    // Note: we have to do a small hack to propagate the values of
    // select forms.  See: https://github.com/dyoo/whalesong/issues/91
    var deepClone = function(dom) {
        var theClone = $(dom).clone(true).get(0);
        var sourceSelects = $(dom).find("select");
        var destSelects = $(theClone).find("select");
        var i, w, o;
        for (i = 0; i < sourceSelects.length; ++i) {
            $(destSelects[i]).val($(sourceSelects[i]).val());
        }
        var allSrcElts = $(dom).find("*");
        var allDestElts = $(theClone).find("*");
        for (i = 0; i < allSrcElts.length; i++) {
            w = $(allSrcElts[i]).width();
            if (w) {
                $(allDestElts[i]).width(w);
                $(allDestElts[i]).height($(allSrcElts[i]).height());
            }

            $(allDestElts[i]).offset($(allSrcElts[i]).offset());
        }


        return theClone;
    };


    
    // CPS'd version of for-each, used on JavaScript arrays.
    //
    // Take care: elts can't be too large or else we'll stack
    // overflow.
    var forEachK = function(f, elts, k) {
        var loop = function(i) {
            if (i === elts.length) {
                return k();
            } else {
                return f(elts[i], function() { loop(i+1); });
            }
        };
        return loop(0);
    };



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

    // domNodeToArrayTree: dom -> dom-tree
    // Given a native dom node, produces the appropriate array tree representation
    var domNodeToArrayTree = function(domNode) {
        var result = [domNode];
        var c;
        for (c = domNode.firstChild; c !== null; c = c.nextSibling) {
	    result.push(domNodeToArrayTree(c));
        }
        return result;
    };


    var arrayTreeToDomNode = function(tree) {
        var result = shallowCloneNode(tree[0]);
        var i;
        for (i = 1; i < tree.length; i++) {
            result.appendChild(arrayTreeToDomNode(tree[i]));
        }
        return result;
    };


    var domToArrayTreeCursor = function(dom) {
        var domOpenF = 
            // To go down, just take the children.
            function(tree) { 
                return tree.slice(1);
            };
        var domCloseF = 
            // To go back up, take the tree and reconstruct it.
            function(tree, children) { 
                return [tree[0]].concat(children);
            };
        var domAtomicF =
            function(tree) {
                return tree[0].nodeType !== 1;
            };
        return TreeCursor.adaptTreeCursor(domNodeToArrayTree(deepClone(dom)),
                                          domOpenF,
                                          domCloseF,
                                          domAtomicF);
    };

    var treeText = function(tree) {
        var text = [];
        var visit = function(tree) {
            var i;
            if (tree[0].nodeType === 3) {
                text.push(tree[0].nodeValue);
            }
            for (i = 1; i < tree.length; i++) {
                visit(tree[i]);
            }
        };
        visit(tree);
        return text.join('');
    };

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

    

    // For the moment, we only support selection by id.
    var selectorMatches = function(selector, tree) {
        if (tree[0].nodeType === 1) {
            return tree[0].getAttribute('id') === selector;
        } else {
            return false;
        }
    };

    var EMPTY_PENDING_ACTIONS = plt.baselib.lists.EMPTY;


    //////////////////////////////////////////////////////////////////////
    // A MockView provides a functional interface to the DOM.  It
    // includes a cursor to the currently focused dom, the pending
    // actions to perform on the actual view, and a nonce to detect
    // freshness of the MockView.
    var MockView = function(lazyCursor, cursor, pendingActions, eventHandlers, nonce) {
        this.lazyCursor = lazyCursor;
        this.cursor = cursor;

        // (listof (view -> void))
        this.pendingActions = pendingActions;

        this.eventHandlers = eventHandlers;
        this.nonce = nonce;
    };

    var isMockView = plt.baselib.makeClassPredicate(MockView);

    MockView.prototype.getCursor = function() {
        if (this.cursor === void(0)) {
            this.cursor = this.lazyCursor();
        }
        return this.cursor;
    };

    MockView.prototype.toString = function() {
        return "<#view>";
    };

    MockView.prototype.getPendingActions = function() {
        return plt.baselib.lists.listToArray(this.pendingActions).reverse();
    };


    MockView.prototype.act = function(actionForCursor, actionForEventHandlers, actionForReal) {
        if (arguments.length !== 3) { throw new Error("act: insufficient arguments"); }
        return new MockView(undefined,
                            actionForCursor(this.getCursor()),
                            plt.baselib.lists.makePair(actionForReal, this.pendingActions),
                            actionForEventHandlers(this.eventHandlers),
                            this.nonce);
    };


    MockView.prototype.updateFocus = function(selector) {
        selector = selector.toString();
        return this.act(
            function(cursor) {
                var c = cursor.top();
                while (true) {
                    if (selectorMatches(selector, c.node)) {
                        return c;
                    }
                    if (c.canSucc()) {
                        c = c.succ();
                    } else {
                        throw new Error("unable to find " + selector);
                    }
                }
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                view.focus = document.getElementById(selector);
            }
        );
    };

    MockView.prototype.getText = function() {        
        var tree = this.getCursor().node;
        return treeText(tree);
    };

    MockView.prototype.updateText = function(text) {
        return this.act(
            function(cursor) {
                return cursor.replaceNode([cursor.node[0]]
                                          .concat([[document.createTextNode(text)]]));
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                $(view.focus).text(text);
            }
        );
    };

    MockView.prototype.getAttr = function(name) {        
        return $(this.getCursor().node[0]).attr(name);
    };

    MockView.prototype.hasAttr = function(name) {        
        return $(this.getCursor().node[0]).attr(name) !== undefined;
    };


    MockView.prototype.updateAttr = function(name, value) {
        return this.act(
            function(cursor) {
                return cursor.replaceNode([$(shallowCloneNode(cursor.node[0]))
                                           .attr(name, value).get(0)]
                                          .concat(cursor.node.slice(1)));
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                $(view.focus).attr(name, value);
            });
    };

    MockView.prototype.removeAttr = function(name) {
        return this.act(
            function(cursor) {
                return cursor.replaceNode([$(shallowCloneNode(cursor.node[0]))
                                           .removeAttr(name).get(0)]
                                          .concat(cursor.node.slice(1)));
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                $(view.focus).removeAttr(name);
            });
    };






    MockView.prototype.getCss = function(name) {        
        return $(this.getCursor().node[0]).css(name);
    };

    MockView.prototype.getWidth = function(name) {        
        return $(this.getCursor().node[0]).width();
    };

    MockView.prototype.getHeight = function(name) {        
        return $(this.getCursor().node[0]).height();
    };


    MockView.prototype.updateCss = function(name, value) {
        return this.act(
            function(cursor) {
                return cursor.replaceNode([$(shallowCloneNode(cursor.node[0]))
                                           .css(name, value).get(0)]
                                          .concat(cursor.node.slice(1)));
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                $(view.focus).css(name, value);
            });
    };



    MockView.prototype.getFormValue = function() {        
        return $(this.getCursor().node[0]).val();
    };

    MockView.prototype.updateFormValue = function(value) {        
        return this.act(
            function(cursor) {
                return cursor.replaceNode([$(shallowCloneNode(cursor.node[0]))
                                           .val(value).get(0)]
                                          .concat(cursor.node.slice(1)));
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                $(view.focus).val(value);
            });
    };



    MockView.prototype.left = function() {
        return this.act(
            function(cursor) {
                return cursor.left();
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                view.focus = view.focus.previousSibling;
            });
    };

    MockView.prototype.right = function() {
        return this.act(
            function(cursor) {
                return cursor.right();
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                view.focus = view.focus.nextSibling;
            });
    };

    MockView.prototype.up = function() {
        return this.act(
            function(cursor) {
                return cursor.up();
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                view.focus = view.focus.parentNode;
            });
   };

    MockView.prototype.down = function() {
        return this.act(
            function(cursor) {
                return cursor.down();
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                view.focus = view.focus.firstChild;
            });
    };


    MockView.prototype.forward = function() {
        return this.act(
            function(cursor) {
                return cursor.succ();
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                if (view.focus.firstChild) {
                    view.focus = view.focus.firstChild;
                } else if (view.focus.nextSibling) {
                    view.focus = view.focus.nextSibling;
                } else {
                    while (view.focus !== view.top) {
                        view.focus = view.focus.parentNode;
                        if (view.focus.nextSibling) {
                            view.focus = view.focus.nextSibling;
                            return;
                        }
                    }
                }
            });
};

    MockView.prototype.backward = function() {
        return this.act(
            function(cursor) {
                return cursor.pred();
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                if (view.focus.previousSibling) {
                    view.focus = view.focus.previousSibling;
                    while (view.focus.children().length > 0) {
                        view.focus = view.focus.firstChild;
                        while(view.focus.nextSibling) { view.focus = view.focus.nextSibling; }
                    }
                } else {
                    view.focus = view.focus.parentNode;
                }

            });
    };







    
    var mockViewIdGensym = 0;

    MockView.prototype.bind = function(name, worldF) {
        var that = this;

        // HACK: every node that is bound needs to have an id.  We
        // enforce this by mutating the node.
        if (! this.getCursor().node[0].id) {
            this.getCursor().node[0].id = ("__webWorldId_" + mockViewIdGensym++);
        }   
        return this.act(
            function(cursor) {
                return cursor;
            },
            function(eventHandlers) {
                var handler = new EventHandler(name,
                                               new DomEventSource(
                                                   name,
                                                   that.getCursor().node[0].id),
                                               worldF);
                var newHandlers = eventHandlers.concat([handler]);
                return newHandlers;
            },
            function(view) {
                // HACK: every node that is bound needs to have an id.  We
                // enforce this by mutating the node.
                if (! view.focus.id) {
                    view.focus.id = ("__webWorldId_" + mockViewIdGensym++);
                }
                var handler = new EventHandler(name, 
                                               new DomEventSource(
                                                   name, 
                                                   view.focus.id),
                                               worldF);
                view.addEventHandler(handler);
                currentBigBangRecord.startEventHandler(handler, 
                                                       function() {});
            });
    };

    MockView.prototype.show = function() {
        return this.act(
            function(cursor) {
                return cursor.replaceNode([$(shallowCloneNode(cursor.node[0]))
                                           .show().get(0)]
                                          .concat(cursor.node.slice(1)));
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                $(view.focus).show();
            }
        );
    };

    MockView.prototype.hide = function() {
        return this.act(
            function(cursor) {
                return cursor.replaceNode([$(shallowCloneNode(cursor.node[0]))
                                           .hide().get(0)]
                                          .concat(cursor.node.slice(1)));
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                $(view.focus).hide();
            }
        );
    };


    MockView.prototype.remove = function() {
        return this.act(
            function(cursor) {
                return cursor.deleteNode();
            },
            function(eventHandlers) {
                return eventHandlers;
            },
            function(view) {
                var elt = view.focus;
                if (view.focus.nextSibling) {
                    view.focus = view.focus.nextSibling;
                } else if (view.focus.previousSibling) {
                    view.focus = view.focus.previousSibling;
                } else {
                    view.focus = view.focus.parentNode;
                }
                $(elt).remove();
            });
    };


    MockView.prototype.appendChild = function(domNode) {
        return this.act(
            function(cursor) {
                if (cursor.canDown()) {
                    cursor = cursor.down();
                    while (cursor.canRight()) {
                        cursor = cursor.right();
                    }
                    return cursor.insertRight(domNodeToArrayTree(domNode));
                } else {
                    return cursor.insertDown(domNodeToArrayTree(domNode));
                }
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                var clone = $(deepClone(domNode));
                clone.appendTo($(view.focus));
                view.focus = clone.get(0);
            }
        );
    };

    MockView.prototype.insertRight = function(domNode) {
        return this.act(
            function(cursor) {
                return cursor.insertRight(domNodeToArrayTree(domNode));
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                var clone = $(deepClone(domNode));
                clone.insertAfter($(view.focus));
                view.focus = clone.get(0);
            }
        );
    };

    MockView.prototype.insertLeft = function(domNode) {
        return this.act(
            function(cursor) {
                return cursor.insertLeft(domNodeToArrayTree(domNode));
            },
            function(eventHandlers) { return eventHandlers; },
            function(view) {
                var clone = $(deepClone(domNode));
                clone.insertBefore($(view.focus));
                view.focus = clone.get(0);
            }
        );
    };



    MockView.prototype.id = function() {
        return this.getCursor().node[0].id;
    };

    MockView.prototype.isUpMovementOk = function() {
        return this.getCursor().canUp();
    };

    MockView.prototype.isDownMovementOk = function() {
        return this.getCursor().canDown();
    };

    MockView.prototype.isLeftMovementOk = function() {
        return this.getCursor().canLeft();
    };

    MockView.prototype.isRightMovementOk = function() {
        return this.getCursor().canRight();
    };

    MockView.prototype.isForwardMovementOk = function() {
        return this.getCursor().canSucc();
    };

    MockView.prototype.isBackwardMovementOk = function() {
        return this.getCursor().canPred();
    };


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


    
    

    // A View represents a representation of the DOM tree.
    var View = function(top, eventHandlers) {
        // top: dom node
        this.top = top;
        // focus: dom node
        this.focus = top;
        this.eventHandlers = eventHandlers;
    };

    View.prototype.toString = function() { return "#<View>"; };


    var defaultToRender = function(){};
 
   View.prototype.initialRender = function(top) {
       $(top).empty();
       // Special case: if this.top is an html, we merge into the
       // existing page.
       if ($(this.top).children("title").length !== 0) {
           $(document.head).find('title').remove();
       }
       $(document.head).append($(this.top).children("title").clone(true));
       $(document.head).append($(this.top).children("link").clone(true));
       
       $(top).append($(this.top));

       // The snip here is meant to accomodate weirdness with canvas dom
       // elements.  cloning a canvas doesn't preserve how it draws.
       // However, we attach a toRender using jQuery's data(), which does
       // do the preservation we need.  On an initial render, we walk
       // through all the elements and toRender them.

       // It may be that this will deprecate the afterAttach stuff
       // that I'm using earlier.
       ($(this.top).data('toRender') || defaultToRender)();
       $('*', this.top).each(
           function(index, elt) {
               ($(elt).data('toRender') || defaultToRender).call(elt);
           });
   };


    View.prototype.addEventHandler = function(handler) {
        this.eventHandlers.push(handler);
    };

    // Return a list of the event sources from the view.
    // fixme: may need to apply the pending actions to get the real set.
    View.prototype.getEventHandlers = function() {
        return this.eventHandlers;
    };

    View.prototype.getMockAndResetFocus = function(nonce) {
        this.focus = this.top;
        var that = this;
        return new MockView(function() { return domToArrayTreeCursor($(that.top).get(0)); },
                            undefined,
                            EMPTY_PENDING_ACTIONS,
                            this.eventHandlers.slice(0),
                            nonce);
    };





    var isView = plt.baselib.makeClassPredicate(View);
    var isResource = resourceStructType.predicate;


    // var resourcePath = function(r) { return resourceStructType.accessor(r, 0); };
    // var resourceKey = function(r) { return resourceStructType.accessor(r, 1); };
    var resourceContent = function(r) { return resourceStructType.accessor(r, 2); };



    var rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;

    // We have to do some kludgery to support the android browser,
    // which does not properly parse <link ...>.
    var rlink = /<link\b[^\/>]* \/>(.*?)/gi;

    var parseStringAsHtml = function(str) {
        var div = document.createElement("div");
        // inject the contents of the document in, removing the scripts
	// to avoid any 'Permission Denied' errors in IE
        div.innerHTML = str.replace(rscript, "").replace(rlink, "");
        var linkMatches = str.match(rlink);
        if (linkMatches) {
            for (var i = 0; i < linkMatches.length; i++) {
                $(div).append($(linkMatches[i]));
            }
        }
        return $(div);
    };

 
    // coerseToView: (U resource View) -> View
    // Coerse a value into a view.
    var coerseToView = function(x, onSuccess, onFail) {
        var dom;
        if (isView(x)) { 
            return onSuccess(x); 
        } else  if (isResource(x)) {
            try {
                dom = parseStringAsHtml(resourceContent(x).toString())
                    .css("margin", "0px")
                    .css("padding", "0px")
                    .css("border", "0px");
                dom.children("body").css("margin", "0px");
            } catch (exn1) {
                return onFail(exn1);
            }
            return onSuccess(new View(dom.get(0), []));
        } else if (isMockView(x)) {
            return onSuccess(new View(arrayTreeToDomNode(x.getCursor().top().node),
                                      x.eventHandlers.slice(0)));
        } else {
            try {
                dom = plt.baselib.format.toDomNode(x);
            } catch (exn2) {
                return onFail(exn2);
            }
            return onSuccess(new View(dom, []));
        }
    };

    var coerseToMockView = function(x, onSuccess, onFail) {
        var dom;
        if (isMockView(x)) { 
            return onSuccess(x); 
        } else  if (isResource(x)) {
            try {
                dom = parseStringAsHtml(resourceContent(x).toString())
                    .css("margin", "0px")
                    .css("padding", "0px")
                    .css("border", "0px");
                dom.children("body").css("margin", "0px");
            } catch (exn1) {
                return onFail(exn1);
            }
            return onSuccess(new MockView(undefined,
                                          domToArrayTreeCursor(dom.get(0)),
                                          EMPTY_PENDING_ACTIONS,
                                          [], 
                                          undefined));
        } else {
            try {
                dom = plt.baselib.format.toDomNode(x);
            } catch (exn2) {
                return onFail(exn2);
            }
            return onSuccess(new MockView(undefined,
                                          domToArrayTreeCursor(dom), 
                                          EMPTY_PENDING_ACTIONS, 
                                          [],
                                          undefined));
        }
    };


    var isDomNode = function(x) {
        return (x.nodeType === 1);
    };


    var coerseToDomNode = function(x, onSuccess, onFail) {
        var dom;
        if (isDomNode(x)) { 
            return onSuccess(x); 
        } else  if (isResource(x)) {
            try {
                dom = parseStringAsHtml(resourceContent(x).toString())
                    .css("margin", "0px")
                    .css("padding", "0px")
                    .css("border", "0px");
            } catch (exn1) {
                return onFail(exn1);
            }
            return onSuccess(dom.get(0));
        } else if (isMockView(x)) {
            return onSuccess(arrayTreeToDomNode(x.getCursor().top().node));
        } else {
            try {
                dom = plt.baselib.format.toDomNode(x);
            } catch (exn2) {
                return onFail(exn2);
            }
            return onSuccess(dom);
        }
    };








    //////////////////////////////////////////////////////////////////////
    //
    // The inputs into a big bang are WorldHandlers, which configure the big
    // bang in terms of the initial view, the inputs, event sources, etc.
    //

    var WorldHandler = function() {};
    var isWorldHandler = plt.baselib.makeClassPredicate(WorldHandler);


    var InitialViewHandler = function(view) {
        WorldHandler.call(this);
        // view: View
        this.view = view;
    };

    InitialViewHandler.prototype = plt.baselib.heir(WorldHandler.prototype);
    InitialViewHandler.prototype.toString = function() { return "#<initial-view>"; };
    var isInitialViewHandler = plt.baselib.makeClassPredicate(InitialViewHandler);


    var StopWhenHandler = function(stopWhen) {
        WorldHandler.call(this);
        // stopWhen: Racket procedure (World -> boolean)
        this.stopWhen = stopWhen;
    };

    StopWhenHandler.prototype = plt.baselib.heir(WorldHandler.prototype);
    StopWhenHandler.prototype.toString = function() { return "#<stop-when>"; };
    var isStopWhenHandler = plt.baselib.makeClassPredicate(StopWhenHandler);



    var ToDrawHandler = function(toDraw) {
        WorldHandler.call(this);
        // toDraw: Racket procedure (World View -> View)
        this.toDraw = toDraw;
    };

    ToDrawHandler.prototype = plt.baselib.heir(WorldHandler.prototype);
    ToDrawHandler.prototype.toString = function() { return "#<to-draw>"; };
    var isToDrawHandler = plt.baselib.makeClassPredicate(ToDrawHandler);





    // An EventHandler combines a EventSource with a racketWorldCallback.
    EventHandler = function(name, eventSource, racketWorldCallback) {
        WorldHandler.call(this);
        this.name = name;
        this.eventSource = eventSource;
        this.racketWorldCallback = racketWorldCallback;
    };
    EventHandler.prototype = plt.baselib.heir(WorldHandler.prototype);
    EventHandler.prototype.toString = function() { return "#<" + this.name + ">"; };
    var isEventHandler = plt.baselib.makeClassPredicate(EventHandler);



    var WithOutputToHandler = function(outputPort) {
        this.outputPort = outputPort;
    };
    WithOutputToHandler.prototype = plt.baselib.heir(WorldHandler.prototype);
    var isWithOutputToHandler = plt.baselib.makeClassPredicate(WithOutputToHandler);


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


    var find = function(handlers, pred) {
        var i;
        for (i = 0; i < handlers.length; i++) {
            if (pred(handlers[i])) {
                return handlers[i];
            }
        }
        return undefined;
    };

    var filter = function(handlers, pred) {
        var i, lst = [];
        for (i = 0; i < handlers.length; i++) {
            if (pred(handlers[i])) {
                lst.push(handlers[i]);
            }
        }
        return lst;
    };

    var hasOwnProperty = {}.hasOwnProperty;



    // convert an object to an event.
    // At the moment, we only copy over those values which are numbers or strings.
    var objectToEvent = function(obj) {
        var key, val;
        var result = makeList();
        // Note: for some reason, jslint is not satisfied that I check
        // that the object has a hasOwnProperty before I use it.  I've intentionally
        // turned off jslint's forin check because it's breaking here:
        for (key in obj) {
            if (hasOwnProperty.call(obj, key)) {
                val = obj[key];
                if (typeof(val) === 'number') {
                    result = makePair(makeList(makeSymbol(key),
                                               plt.baselib.numbers.makeFloat(val)),
                                      result);
                } else if (typeof(val) === 'string') {
                    result = makePair(makeList(makeSymbol(key), val),
                                      result);
                }                         
            }
        }
        return eventStructType.constructor([result]);
    };





    /* Event sources.
       
       An event source is a way to send input to a web-world program.

       An event source may be started or stopped.


       Pause and Unpause are semantically meant to be cheaper than start, stop, so
       that's why they're a part of this API.
    */

    var EventSource = function() {};
    EventSource.prototype.onStart = function(fireEvent, internalCall, k) {
        k();
    };

    EventSource.prototype.onStop = function(k) {
        k();
    };

    

    // TickEventSource sends tick events.
    var TickEventSource = function(delay) {
        this.delay = delay; // delay in milliseconds.

        this.id = undefined;
        // either undefined, or an integer representing the
        // id to cancel a timeout.
    };

    TickEventSource.prototype = plt.baselib.heir(EventSource.prototype);

    TickEventSource.prototype.onStart = function(fireEvent, internalCall, k) {
        if (this.id === undefined) {
            this.id = setInterval(
                function(evt) {
                    fireEvent(undefined,
                              objectToEvent(evt));
                },
                this.delay);
        }
        k();
    };

    TickEventSource.prototype.onStop = function(k) {
        if (this.id !== undefined) {
            clearInterval(this.id);
            this.id = undefined;
        }
        k();
    };






    var MockLocationEventSource = function() {
        this.elt = undefined;
    };
    MockLocationEventSource.prototype = plt.baselib.heir(EventSource.prototype);
    MockLocationEventSource.prototype.onStart = function(fireEvent, internalCall, k) {
        if (this.elt === undefined) {
            var mockLocationSetter = document.createElement("div");
	    
            var latInput = document.createElement("input");
            latInput.type = "text";
	    
            var latOutput = document.createElement("input");
            latOutput.type = "text";
            
            var submitButton = document.createElement("input");
            submitButton.type = "button";
            submitButton.value = "send lat/lng";
            submitButton.onclick = function() {
                fireEvent(undefined,
                          objectToEvent({ latitude: Number(latInput.value),
                                          longitude: Number(latOutput.value)}));
                return false;
            };
	    
            mockLocationSetter.style.border = "1pt solid black";
            mockLocationSetter.appendChild(
                document.createTextNode("mock location setter"));
            mockLocationSetter.appendChild(latInput);
            mockLocationSetter.appendChild(latOutput);
            mockLocationSetter.appendChild(submitButton);
            document.body.appendChild(mockLocationSetter);

            this.elt = mockLocationSetter;
        }
        k();
    };

    MockLocationEventSource.prototype.onStop = function(k) {
        if (this.elt !== undefined) { 
            document.body.removeChild(this.elt);
            this.elt = undefined;
        }
        k();
    };

    



    // This version really does use the geolocation object.
    var LocationEventSource = function() {
        this.id = undefined;
    };

    LocationEventSource.prototype = plt.baselib.heir(EventSource.prototype);

    LocationEventSource.prototype.onStart = function(fireEvent, internalCall, k) {
        var that = this;
        if (this.id === undefined) {
            var success = function(position) {
                if (hasOwnProperty.call(position, 'coords') &&
                    hasOwnProperty.call(position.coords, 'latitude') &&
                    hasOwnProperty.call(position.coords, 'longitude')) {
                    fireEvent(undefined,
                              objectToEvent({ 'latitude' : Number(position.coords.latitude),
                                              'longitude' : Number(position.coords.longitude) }));
                }
            };
            var fail = function(err) {
                // Quiet failure
            };
            // If we fail while trying to watch the position
            // using high accuracy, switch over to the coarse one.
            var onFailSwitchoverToCoerse = function() {
                navigator.geolocation.clearWatch(that.id);
                that.id = navigator.geolocation.watchPosition(
                    success,
                    fail);
            };
            if (!!(navigator.geolocation)) {
                navigator.geolocation.getCurrentPosition(success, fail);
                this.id = navigator.geolocation.watchPosition(
                    success,
                    onFailSwitchoverToCoerse,
                    { enableHighAccuracy : true,
                      // Try every ten seconds
                      maximumAge : 10000}); 
            }
        }
        k();
    };
    
    LocationEventSource.prototype.onStop = function(k) {
        if (this.id !== undefined) { 
            navigator.geolocation.clearWatch(this.id);
            this.id = undefined;
        }
        k();
    };






    // DomElementSource: string (U DOM string) -> EventSource
    // A DomEventSource allows DOM elements to send events over to
    // web-world.
    DomEventSource = function(type, elementOrId) {
        this.type = type;
        this.elementOrId = elementOrId;
        this.handler = undefined;
    };

    DomEventSource.prototype = plt.baselib.heir(EventSource.prototype);

    DomEventSource.prototype.onStart = function(fireEvent, internalCall, k) {
        var element = this.elementOrId;
        if (typeof(this.elementOrId) === 'string') {
            element = document.getElementById(this.elementOrId);
        }

        if (! element) { return; }
        if (this.handler !== undefined) {
            $(element).unbind(this.type, this.handler);
            this.handler = undefined;
        }

        this.handler = function(evt) {
            if (element !== undefined) {
                fireEvent(element, objectToEvent(evt));
            }
            evt.stopPropagation();
            evt.preventDefault();
        };
        $(element).bind(this.type, this.handler);
        k();
    };


    DomEventSource.prototype.onStop = function(k) {
        var element = this.elementOrId;
        if (typeof(this.elementOrId) === 'string') {
            element = document.getElementById(this.elementOrId);
        }

        if (this.handler !== undefined) {
            if (element !== undefined) {
                $(element).unbind(this.type, this.handler);
            }
            this.handler = undefined;
        }
        k();
    };







    var EventQueue = function() {
        this.elts = [];
    };
    EventQueue.prototype.queue = function(elt) {
        this.elts.push(elt);
    };

    EventQueue.prototype.dequeue = function() {
        return this.elts.shift();
    };

    EventQueue.prototype.isEmpty = function() {
        return this.elts.length === 0;
    };


    var EventQueueElement = function(who, handler, data) {
        this.who = who;
        this.handler = handler;
        this.data = data;
    };



    var defaultToDraw = function(MACHINE, world, view, success, fail) {
        coerseToMockView(world,
                         success,
                         fail);
    };


    var defaultStopWhen = function(MACHINE, world, view, success, fail) {
        return success(false);
    };


    // bigBang.
    var bigBang = function(MACHINE, world, handlers) {
        var oldCurrentBigBangRecord = currentBigBangRecord;

        var running = true;
        var dispatchingEvents = false;

        var top = $("<div/>").get(0);
        var view = (find(handlers, isInitialViewHandler) ||
                    { view : new View($('<div/>').get(0), []) }).view;
        var stopWhen = (find(handlers, isStopWhenHandler) ||
                        { stopWhen: defaultStopWhen }).stopWhen;
        var toDraw = (find(handlers, isToDrawHandler) || {toDraw : defaultToDraw} ).toDraw;

        var oldOutputPort = MACHINE.params.currentOutputPort;

        var eventQueue = new EventQueue();
        var eventHandlers = filter(handlers, isEventHandler).concat(view.getEventHandlers());
        view.eventHandlers = eventHandlers;

        MACHINE.params.currentDisplayer(MACHINE, top);
        
        // From this point forward, redirect standard output if requested.
        if (find(handlers, isWithOutputToHandler)) {
            MACHINE.params.currentOutputPort = find(handlers, isWithOutputToHandler).outputPort;
        }

        PAUSE(function(restart, internalCall) {
            var onCleanRestart, onMessyRestart, 
            startEventHandlers, stopEventHandlers, 
            startEventHandler, stopEventHandler,
            dispatchEventsInQueue, refreshView;

            onCleanRestart = function() {
                running = false;
                stopEventHandlers(
                    function() {
                        restart(function(MACHINE) {
                            MACHINE.params.currentOutputPort = oldOutputPort;
                            currentBigBangRecord = oldCurrentBigBangRecord;
                            finalizeClosureCall(MACHINE, world);
                        });
                    });
            };

            onMessyRestart = function(exn) {
                running = false;
                stopEventHandlers(
                    function() {
                        restart(function(MACHINE) {
                            currentBigBangRecord = oldCurrentBigBangRecord;
                            MACHINE.params.currentOutputPort = oldOutputPort;
                            plt.baselib.exceptions.raise(MACHINE, exn);
                        });
                    });
            };

            startEventHandlers = function(k) {
                forEachK(startEventHandler, eventHandlers, k);
            };

            stopEventHandlers = function(k) {
                forEachK(stopEventHandler, eventHandlers, k);
            };

            startEventHandler = function(handler, k) {
                var fireEvent = function(who) {
                    if (! running) { return; }
                    var args = [].slice.call(arguments, 1);
                    eventQueue.queue(new EventQueueElement(who, handler, args));
                    if (! dispatchingEvents) {
                        dispatchingEvents = true;
                        dispatchEventsInQueue(
                            function() {
                                refreshView(function() {}, onMessyRestart);
                            }, 
                            onMessyRestart);
                    }
                };
                handler.eventSource.onStart(fireEvent, internalCall, k);
            };

            stopEventHandler = function(handler, k) {
                handler.eventSource.onStop(k);
            };


            dispatchEventsInQueue = function(success, fail) {
                // Apply all the events on the queue, call toDraw, and then stop.
                // If the world ever satisfies stopWhen, stop immediately and quit.
                var nextEvent;
                var args;
                var racketWorldCallback;
                var mockView;
                dispatchingEvents = true;
                if(! eventQueue.isEmpty() ) {
                    // Set up the proxy object so we can do what appear to be functional
                    // queries.
                    mockView = view.getMockAndResetFocus();
                    nextEvent = eventQueue.dequeue();
                    if (nextEvent.who !== undefined) {
                        mockView = mockView.updateFocus(nextEvent.who.id);
                    }

                    racketWorldCallback = nextEvent.handler.racketWorldCallback;
                    args = nextEvent.data.slice(0);
                    var onGoodWorldUpdate = 
                        function (newWorld) {
                            world = newWorld;
                            stopWhen(internalCall,
                                     world,
                                     mockView,
                                     function(shouldStop) {
                                         if (shouldStop) {
                                             refreshView(onCleanRestart,
                                                         fail);
                                         } else {
                                             dispatchEventsInQueue(success, fail);
                                         }
                                     },
                                     fail);
                        };
                    if (isArityMatching(racketWorldCallback.racketArity, 1)) {
                        racketWorldCallback(internalCall, 
                                            world,
                                            onGoodWorldUpdate,
                                            fail);
                    } else if (isArityMatching(racketWorldCallback.racketArity, 2)) {
                        racketWorldCallback(internalCall, 
                                            world,
                                            mockView,
                                            onGoodWorldUpdate,
                                            fail);
                    } else { 
                        args = ([internalCall, world, mockView]
                                .concat(args)
                                .concat([onGoodWorldUpdate, fail]));
                        racketWorldCallback.apply(null, args);
                    }
                } else {
                    dispatchingEvents = false;
                    success();
                }
            };

            refreshView = function(success, failure) {
                // Note: we create a random nonce, and watch to see if the MockView we get back
                // from the user came from here.  If not, we have no hope to do a nice, efficient
                // update, and have to do it from scratch.
                var nonce = Math.random();
                var originalMockView = view.getMockAndResetFocus(nonce);
                toDraw(internalCall, 
                       world,
                       originalMockView,
                       function(newMockView) {
                           if (newMockView.nonce === nonce) {
                               var i;
                               var actions = newMockView.getPendingActions();
                               for (i = 0; i < actions.length; i++) {
                                   actions[i](view);
                               }
                               success();
                           } else {
                               view.top = arrayTreeToDomNode(newMockView.getCursor().top().node);
                               view.initialRender(top);
                               var oldEventHandlers = eventHandlers;
                               eventHandlers = newMockView.eventHandlers.slice(0);
                               startEventHandlers(function() {
                                   eventHandlers = eventHandlers.concat(oldEventHandlers);
                                   view.eventHandlers = eventHandlers;
                                   success();
                               });
                           }
                       },
                       function(err) {
                           failure(err);
                       });
            };

            currentBigBangRecord = { stop : onCleanRestart,
                                     stopWithExn : onMessyRestart,
                                     startEventHandler : startEventHandler,
                                     stopEventHandler : stopEventHandler };
            view.initialRender(top);
            startEventHandlers(
                function() {
                    refreshView(function() {}, onMessyRestart);
                });

        });
    };

    var wrapFunction = function(proc) {
        var f = function(internalCall) {
            var success = arguments[arguments.length - 2];
            var fail = arguments[arguments.length - 1];
            var args = [].slice.call(arguments, 1, arguments.length - 2);
            return internalCall.apply(null,
                                      [proc,
                                       success,
                                       fail].concat(args));
        };
        f.displayName = proc.displayName;
        f.racketArity = proc.racketArity;
        return f;
    };




    var DomElementOutputPort = function(id) {
        this.id = id;
    };

    DomElementOutputPort.prototype = plt.baselib.heir(plt.baselib.ports.OutputPort.prototype);

    DomElementOutputPort.prototype.writeDomNode = function (MACHINE, v) {
        $(document.getElementById(this.id)).append(v);
        $(v).trigger({type : 'afterAttach'});
        $('*', v).trigger({type : 'afterAttach'});
    };




    var isAttributeList = function(x) {
        var children;
        if (isList(x) && (! isEmpty(x))){
            if (isSymbol(x.first) && x.first.val === '@') {
                children = x.rest;
                while(! isEmpty(children)) {
                    if (isList(children.first) &&
                        listLength(children.first) === 2 &&
                        isSymbol(children.first.first) &&
                        isString(children.first.rest.first)) {

                        children = children.rest;

                    } else {
                        return false;
                    }
                }
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    };



    // We keep a cache of valid element names.  The only keys here are
    // those elements whose names are valid.  We don't record invalid
    // ones, since there's an unbound number of those.
    var validElementNames = {};  
    var isValidElementName = function(name) {
        if (! (hasOwnProperty.call(validElementNames, name))) {
            // Permissive parsing: see that the name is a valid
            // element type.
            // Is there a nicer way to do this besides exception
            // handling?
            try {
                document.createElement(name);
                validElementNames[name] = true;
            } catch(e) {
                return false;
            }
        }
        return true;
    };


    // An xexp is one of the following:
    // xexp :== (name (@ (key value) ...) xexp ...)
    //      :== (name xexp ...)
    //      :== string
    var isXexp = function(x) {
        var children;
        if (isElement(x)) {
            return true;
        }
        if (isString(x)) { 
            return true; 
        }
        if (isSymbol(x)) {
            return true;
        }
        if (isList(x) && !(isEmpty(x))) {
            if (isSymbol(x.first)) {
                if (! isValidElementName(x.first.val)) {
                    return false;
                }

                children = x.rest;
                // Check the rest of the children.  The first is special.
                if (isEmpty(children)) {
                    return true;
                }
                if (isAttributeList(children.first)) {
                    children = children.rest;
                }
                while (! (isEmpty(children))) {
                    if (! isXexp(children.first)) {
                        return false;
                    }
                    children = children.rest;
                }
                return true;
            } else {
                return false;
            }
        }
        return false;
    };




    var assignAttributes = function(node, x) {
        var children, key, value;
        if (isList(x) && (! isEmpty(x))){
            if (isSymbol(x.first) && x.first.val === '@') {
                children = x.rest;
                while(! isEmpty(children)) {
                    if (isList(children.first) &&
                        listLength(children.first) === 2 &&
                        isSymbol(children.first.first) &&
                        isString(children.first.rest.first)) {
                        
                        key = children.first.first;
                        value = children.first.rest.first;
                        $(node).attr(key.val, value.toString());

                        children = children.rest;

                    } else {
                        return;
                    }
                }
                return;
            } else {
                return;
            }
        } else {
            return;
        }
    };
    var xexpToDom = function(x) {
        var children;
        var name;
        var node;
        if (isElement(x)) {
            return x;
        }
        if (isString(x)) { 
            return document.createTextNode(x); 
        }
        if (isSymbol(x)) {
            return $("<div>&" + x.val + ";</div>").get(0).firstChild;
        }
        if (isList(x) && !(isEmpty(x))) {
            if (isSymbol(x.first)) {
                name = x.first.val;
                node = document.createElement(name);
                children = x.rest;
                // Check the rest of the children.  The first is special.
                if (isEmpty(children)) {
                    return node;
                }
                if (isAttributeList(children.first)) {
                    assignAttributes(node, children.first);
                    children = children.rest;
                }
                while (! (isEmpty(children))) {
                    node.appendChild(xexpToDom(children.first));
                    children = children.rest;
                }
                return node;
            } else {
                return false;
            }
        }
        return false;
    };

    var firstLessThan = function(x, y) {
        return x[0] < y[0];
    };

    var domToXexp = function(dom) {
        var child, attrs, name, convertedChildren, i, attributes;
        if (dom.nodeType === 1) {
            attributes = [];
            attrs = plt.baselib.lists.EMPTY;
            name = plt.baselib.symbols.makeSymbol(dom.nodeName.toLowerCase());
            child = dom.firstChild;
            convertedChildren = plt.baselib.lists.EMPTY;
            for (i = 0; i < dom.attributes.length; i++) {
                attributes.push([dom.attributes[i].nodeName, dom.attributes[i].nodeValue]);
            } 
            attributes.sort(firstLessThan);
            for (i = 0; i < attributes.length; i++) {
                attrs = plt.baselib.lists.makePair(
                    plt.baselib.lists.makeList(plt.baselib.symbols.makeSymbol(attributes[i][0]),
                                               attributes[i][1]),
                    attrs);
            }
            while(child !== null) {
                if (child.nodeType === 1) {
                    convertedChildren = 
                        plt.baselib.lists.makePair(
                            domToXexp(child),
                            convertedChildren);
                } else if (child.nodeType === 3) {
                    convertedChildren = plt.baselib.lists.makePair(
                        domToXexp(child),
                        convertedChildren);
                }
                // Ignore other types.
                child = child.nextSibling;
            }

            if (attrs === plt.baselib.lists.EMPTY) {
                return plt.baselib.lists.makePair(
                    name,
                    plt.baselib.lists.reverse(convertedChildren));
            } else {
                return plt.baselib.lists.makePair(
                    name,
                    plt.baselib.lists.makePair(
                        plt.baselib.lists.makePair(plt.baselib.symbols.makeSymbol("@"),
                                                   attrs),
                        plt.baselib.lists.reverse(convertedChildren)));
            }
        } else if (dom.nodeType === 3) {
            return dom.nodeValue;
        } else {
            // If we can't convert it, return false.
            return false;
        }
    };     










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

    var checkReal = plt.baselib.check.checkReal;
    var checkString = plt.baselib.check.checkString;
    var checkSymbolOrString = plt.baselib.check.checkSymbolOrString;
    var checkProcedure = plt.baselib.check.checkProcedure;


    var checkWorldHandler = plt.baselib.check.makeCheckArgumentType(
        isWorldHandler,
        'world handler');

    var checkMockView = plt.baselib.check.makeCheckArgumentType(
        isMockView, 'view');

    var checkMockViewOnElement = plt.baselib.check.makeCheckArgumentType(
        function(x) {
            return isMockView(x) && (!(x.getCursor().isOnAtomicElement()));
        },
        'element-focused view');


    var checkSelector = plt.baselib.check.makeCheckArgumentType(
        isString, 'selector');

    var checkXexp = plt.baselib.check.makeCheckArgumentType(
        isXexp, 'xexp');


    EXPORTS['big-bang'] = makeClosure(
        'big-bang',
        plt.baselib.arity.makeArityAtLeast(1),
        function(MACHINE) {
            var world = MACHINE.e[MACHINE.e.length - 1];
            var handlers = [];
            var i;
            for (i = 1; i < MACHINE.a; i++) {
                handlers.push(checkWorldHandler(MACHINE, 'big-bang', i));
            }
            return bigBang(MACHINE, world, handlers);
        });


    EXPORTS['initial-view'] = makeClosure(
        'initial-view',
        1,
        function(MACHINE) {
            var viewable = MACHINE.e[MACHINE.e.length - 1];
            PAUSE(function(restart) {
                coerseToView(viewable,
                             function(v) {
                                 restart(function(MACHINE) {
                                     finalizeClosureCall(MACHINE,
                                                         new InitialViewHandler(v));
                                 });
                             },
                             function(exn) {
                                 restart(function(MACHINE) {
                                     plt.baselib.exceptions.raiseFailure(
                                         MACHINE, 
                                         plt.baselib.format.format(
                                             "unable to translate ~s to view: ~a",
                                             [viewable, exn.message]));
                                 });
                             });
            });
        });

    EXPORTS['view?'] = makePrimitiveProcedure(
        'view?',
        1,
        function(M) {
            return isMockView(M.e[M.e.length - 1]);
        });


    EXPORTS['->view'] = makeClosure(
        '->view',
        1,
        function(MACHINE) {
            var viewable = MACHINE.e[MACHINE.e.length - 1];
            PAUSE(function(restart) {
                coerseToMockView(viewable,
                                 function(v) {
                                     restart(function(MACHINE) {
                                         finalizeClosureCall(MACHINE, v);
                                     });
                                 },
                                 function(exn) {
                                     restart(function(MACHINE) {
                                         plt.baselib.exceptions.raiseFailure(
                                             MACHINE, 
                                             plt.baselib.format.format(
                                                 "unable to translate ~s to view: ~a",
                                                 [viewable, exn.message]));
                                     });
                                 });
            });
        });
    
    EXPORTS['stop-when'] = makePrimitiveProcedure(
        'stop-when',
        1,
        function(MACHINE) {
            var stopWhen = wrapFunction(checkProcedure(MACHINE, 'stop-when', 0));
            return new StopWhenHandler(stopWhen);
        });

    EXPORTS['to-draw'] = makePrimitiveProcedure(
        'to-draw',
        1,
        function(MACHINE) {
            var toDraw = wrapFunction(checkProcedure(MACHINE, 'to-draw', 0));

            var coersingToMockView = function(MACHINE, world, view, success, fail) {
                return toDraw(MACHINE, world, view, 
                              function(v) { 
                                  coerseToMockView(v, success, fail);
                              },
                              fail);
            };
            return new ToDrawHandler(coersingToMockView);
        });

    EXPORTS['on-tick'] = makePrimitiveProcedure(
        'on-tick',
        plt.baselib.lists.makeList(1, 2),
        function(MACHINE) {
            var onTick = wrapFunction(checkProcedure(MACHINE, 'on-tick', 0));
            var delay = Math.floor(1000/28);
            if (MACHINE.a === 2) {
                delay = Math.floor(plt.baselib.numbers.toFixnum(checkReal(MACHINE, 'on-tick', 1)) * 1000);
            }
            return new EventHandler('on-tick', 
                                    new TickEventSource(delay), 
                                    onTick);
        });


    EXPORTS['view-focus?'] = makePrimitiveProcedure(
        'view-focus?',
        2,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-focus', 0);
            var selector = checkSelector(MACHINE, 'view-focus', 1);
            try {
                view.updateFocus(selector);
                return true;
            } catch (e) {
                return false;
            }
        });


    EXPORTS['view-focus'] = makePrimitiveProcedure(
        'view-focus',
        2,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-focus', 0);
            var selector = checkSelector(MACHINE, 'view-focus', 1);
            try {
                return view.updateFocus(selector);
            } catch (e) {
                plt.baselib.exceptions.raiseFailure(
                    MACHINE, 
                    plt.baselib.format.format(
                        "unable to focus to ~s: ~s",
                        [selector, e.message]));
            }
        });


    EXPORTS['view-left'] = makePrimitiveProcedure(
        'view-left',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-left', 0);
            try {
                return view.left();
            } catch (e) {
                plt.baselib.exceptions.raiseFailure(
                    MACHINE, 
                    "unable to focus left");
            }
        });

    EXPORTS['view-right'] = makePrimitiveProcedure(
        'view-right',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-right', 0);
            try {
                return view.right();
            } catch (e) {
                plt.baselib.exceptions.raiseFailure(
                    MACHINE, 
                    "unable to focus right");
            }
        });

    EXPORTS['view-up'] = makePrimitiveProcedure(
        'view-up',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-up', 0);
            try {
                return view.up();
            } catch (e) {
                plt.baselib.exceptions.raiseFailure(
                    MACHINE, 
                    "unable to focus up");
            }
        });

    EXPORTS['view-down'] = makePrimitiveProcedure(
        'view-down',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-down', 0);
            try {
                return view.down();
            } catch(e) {
                plt.baselib.exceptions.raiseFailure(
                    MACHINE, 
                    "unable to focus down");
            }
        });

    EXPORTS['view-forward'] = makePrimitiveProcedure(
        'view-forward',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-forward', 0);
            try {
                return view.forward();
            } catch(e) {
                plt.baselib.exceptions.raiseFailure(
                    MACHINE, 
                    "unable to focus forward");
            } 
        });

    EXPORTS['view-backward'] = makePrimitiveProcedure(
        'view-backward',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-backward', 0);
            try {
                return view.backward();
            } catch(e) {
                plt.baselib.exceptions.raiseFailure(
                    MACHINE, 
                    "unable to focus backward");
            }
        });


    EXPORTS['view-left?'] = makePrimitiveProcedure(
        'view-left?',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-left?', 0);
            return view.isLeftMovementOk();
        });

    EXPORTS['view-right?'] = makePrimitiveProcedure(
        'view-right?',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-right?', 0);
            return view.isRightMovementOk();
        });

    EXPORTS['view-up?'] = makePrimitiveProcedure(
        'view-up?',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-up?', 0);
            return view.isUpMovementOk();
        });

    EXPORTS['view-down?'] = makePrimitiveProcedure(
        'view-down?',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-down?', 0);
            return view.isDownMovementOk();
        });


    EXPORTS['view-forward?'] = makePrimitiveProcedure(
        'view-down?',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-forward?', 0);
            return view.isForwardMovementOk();
        });

    EXPORTS['view-backward?'] = makePrimitiveProcedure(
        'view-backward?',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-backward?', 0);
            return view.isBackwardMovementOk();
        });



    EXPORTS['view-text'] = makePrimitiveProcedure(
        'view-text',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-text', 0);
            return view.getText();
        });


    EXPORTS['update-view-text'] = makePrimitiveProcedure(
        'update-view-text',
        2,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'update-view-text', 0);
            var text = plt.baselib.format.toDisplayedString(MACHINE.e[MACHINE.e.length - 2]);
            return view.updateText(text);
        });




    EXPORTS['view-attr'] = makePrimitiveProcedure(
        'view-attr',
        2,
        function(MACHINE) {
            var view = checkMockViewOnElement(MACHINE, 'view-attr', 0);
            var name = checkSymbolOrString(MACHINE, 'view-attr', 1).toString();
            return view.getAttr(name);
        });

    EXPORTS['view-has-attr?'] = makePrimitiveProcedure(
        'view-has-attr?',
        2,
        function(MACHINE) {
            var view = checkMockViewOnElement(MACHINE, 'view-has-attr?', 0);
            var name = checkSymbolOrString(MACHINE, 'view-has-attr?', 1).toString();
            return view.hasAttr(name);
        });

    EXPORTS['update-view-attr'] = makePrimitiveProcedure(
        'update-view-attr',
        3,
        function(MACHINE) {
            var view = checkMockViewOnElement(MACHINE, 'update-view-attr', 0);
            var name = checkSymbolOrString(MACHINE, 'update-view-attr', 1).toString();
            var value = checkSymbolOrString(MACHINE, 'update-view-attr', 2).toString();
            return view.updateAttr(name, value);
        });

    EXPORTS['remove-view-attr'] = makePrimitiveProcedure(
        'remove-view-attr',
        2,
        function(MACHINE) {
            var view = checkMockViewOnElement(MACHINE, 'remove-view-attr', 0);
            var name = checkSymbolOrString(MACHINE, 'remove-view-attr', 1).toString();
            return view.removeAttr(name);
        });

    EXPORTS['view-css'] = makePrimitiveProcedure(
        'view-css',
        2,
        function(MACHINE) {
            var view = checkMockViewOnElement(MACHINE, 'view-css', 0);
            var name = checkSymbolOrString(MACHINE, 'view-css', 1).toString();
            return view.getCss(name);
        });

    EXPORTS['view-width'] = makePrimitiveProcedure(
        'view-width',
        1,
        function(MACHINE) {
            var view = checkMockViewOnElement(MACHINE, 'view-width', 0);
            return view.getWidth();
        });
    EXPORTS['view-height'] = makePrimitiveProcedure(
        'view-height',
        1,
        function(MACHINE) {
            var view = checkMockViewOnElement(MACHINE, 'view-height', 0);
            return view.getHeight();
        });

    EXPORTS['update-view-css'] = makePrimitiveProcedure(
        'update-view-css',
        3,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'update-view-css', 0);
            var name = checkSymbolOrString(MACHINE, 'update-view-css', 1).toString();
            var value = checkSymbolOrString(MACHINE, 'update-view-css', 2).toString();
            return view.updateCss(name, value);
        });


    EXPORTS['view-bind'] = makePrimitiveProcedure(
        'view-bind',
        3,
        function(MACHINE) {
            var view = checkMockViewOnElement(MACHINE, 'view-bind', 0);
            var name = checkSymbolOrString(MACHINE, 'view-bind', 1);
            var worldF = wrapFunction(checkProcedure(MACHINE, 'view-bind', 2));
            return view.bind(name, worldF);
        });


    EXPORTS['view-form-value'] = makePrimitiveProcedure(
        'view-form-value',
        1,
        function(MACHINE) {
            var view = checkMockViewOnElement(MACHINE, 'view-form-value', 0);
            return view.getFormValue();
        });


    EXPORTS['update-view-form-value'] = makePrimitiveProcedure(
        'update-view-form-value',
        2,
        function(MACHINE) {
            var view = checkMockViewOnElement(MACHINE, 'update-view-form-value', 0);
            var value = checkSymbolOrString(MACHINE, 'update-view-form-value', 1).toString();
            return view.updateFormValue(value);
        });

    EXPORTS['view-show'] = makePrimitiveProcedure(
        'view-show',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-show', 0);
            return view.show();
        });


    EXPORTS['view-hide'] = makePrimitiveProcedure(
        'view-hide',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-hide', 0);
            return view.hide();
        });


    EXPORTS['view-remove'] = makePrimitiveProcedure(
        'view-remove',
        1,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-remove', 0);
            return view.remove();
        });


    
    EXPORTS['view-append-child'] = makeClosure(
        'view-append-child',
        2,
        function(MACHINE) {
            var view = checkMockViewOnElement(MACHINE, 'view-append-child', 0);
            var x = MACHINE.e[MACHINE.e.length - 2];
            PAUSE(function(restart) {
                coerseToDomNode(x,
                                function(dom) {
                                     restart(function(MACHINE) {
                                         var updatedView = view.appendChild(dom);
                                         finalizeClosureCall(MACHINE, updatedView);
                                     });
                                },
                                function(err) {
                                    restart(function(MACHINE) {
                                         plt.baselib.exceptions.raiseFailure(
                                             MACHINE, 
                                             plt.baselib.format.format(
                                                 "unable to translate ~s to dom node: ~a",
                                                 [x, err.message]));
                                        
                                    });
                                });
            });
        });


    EXPORTS['view-insert-right'] = makeClosure(
        'view-insert-right',
        2,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-insert-right', 0);
            var x = MACHINE.e[MACHINE.e.length - 2];
            PAUSE(function(restart) {
                coerseToDomNode(x,
                                function(dom) {
                                     restart(function(MACHINE) {
                                         var updatedView = view.insertRight(dom);
                                         finalizeClosureCall(MACHINE, updatedView);
                                     });
                                },
                                function(err) {
                                    restart(function(MACHINE) {
                                         plt.baselib.exceptions.raiseFailure(
                                             MACHINE, 
                                             plt.baselib.format.format(
                                                 "unable to translate ~s to dom node: ~a",
                                                 [x, err.message]));
                                        
                                    });
                                });
            });
        });




    EXPORTS['view-insert-left'] = makeClosure(
        'view-insert-left',
        2,
        function(MACHINE) {
            var view = checkMockView(MACHINE, 'view-insert-left', 0);
            var x = MACHINE.e[MACHINE.e.length - 2];
            PAUSE(function(restart) {
                coerseToDomNode(x,
                                function(dom) {
                                     restart(function(MACHINE) {
                                         var updatedView = view.insertLeft(dom);
                                         finalizeClosureCall(MACHINE, updatedView);
                                     });
                                },
                                function(err) {
                                    restart(function(MACHINE) {
                                         plt.baselib.exceptions.raiseFailure(
                                             MACHINE, 
                                             plt.baselib.format.format(
                                                 "unable to translate ~s to dom node: ~a",
                                                 [x, err.message]));
                                        
                                    });
                                });
            });
        });



    EXPORTS['view-id'] = makePrimitiveProcedure(
        'view-id',
        1,
        function(MACHINE) {
            var view = checkMockViewOnElement(MACHINE, 'view-hide', 0);
            return view.id();
        });




    EXPORTS['on-location-change'] = makePrimitiveProcedure(
        'on-location-change',
        1,
        function(MACHINE) {
            var onChange = wrapFunction(checkProcedure(MACHINE, 'on-location-change', 0));
            return new EventHandler('on-location-change', 
                                    new LocationEventSource(), 
                                    onChange);
        });


    EXPORTS['on-mock-location-change'] = makePrimitiveProcedure(
        'on-mock-location-change',
        1,
        function(MACHINE) {
            var onChange = wrapFunction(checkProcedure(MACHINE, 'on-mock-location-change', 0));
            return new EventHandler('on-mock-location-change', 
                                    new MockLocationEventSource(), 
                                    onChange);
        });


    EXPORTS['open-output-element'] = makePrimitiveProcedure(
        'open-output-element',
        1,
        function(MACHINE) {
            var id = checkString(MACHINE, 'open-output-element', 0);
            return new DomElementOutputPort(id.toString());
        });


    EXPORTS['xexp?'] = makePrimitiveProcedure(
        'xexp?',
        1,
        function(MACHINE) {
            return isXexp(MACHINE.e[MACHINE.e.length - 1]);
        });


    EXPORTS['xexp->dom'] = makePrimitiveProcedure(
        'xexp->dom',
        1,
        function(MACHINE) {
            var xexp = checkXexp(MACHINE, 'xexp->dom', 0);
            return xexpToDom(xexp);
        });


    EXPORTS['view->xexp'] = makePrimitiveProcedure(
        'view->xexp',
        1,
        function(MACHINE) {
            var mockView = checkMockView(MACHINE, 'view-hide', 0);
            var domNode = arrayTreeToDomNode(mockView.getCursor().top().node);
            return domToXexp(domNode);
        });




    // For private importers of the web-world library, like the FFI's js/world library.
    EXPORTS['EventSource'] = EventSource;
    EXPORTS['EventHandler'] = EventHandler;
    EXPORTS['wrapFunction'] = wrapFunction;
    //////////////////////////////////////////////////////////////////////
}(jQuery));
