function invoke_remote(funcName, args, return_cb) {
    var cb = function(fdata, fstatus) {
        if (fdata.err) {
	         //error(data.errdata, "Remote Exception Encountered", data.traceback);
	         alert(fdata.errdata + ":\n" + fdata.traceback);
	         throw new Error(fdata.errdata);
	     } else {
	         return_cb(fdata.payload);
	     } 
    }
    
    argStr = JSON.stringify(args);
    $.ajaxSetup({async: true});
    $.post("/rmi/" + funcName, {"jsondata": argStr}, cb, "json");
}

function modal_on(state, z) {
    var curtain = $("#modal_curtain");
    if (curtain.length <=0) {
        curtain = $("<div />");
        curtain.attr("id", "modal_curtain");
        $($("body").append(curtain));
    }
    curtain.height($().height());
    curtain.width($().width());
    if (!(z == undefined)) {
        curtain.css("z-index", z);
    } else {
        curtain.css("z-index", "");
    }
    if (state) {
        curtain.fadeIn();
    } else {
        curtain.fadeOut();
    }
}

/**
 * Modal Message class
 */
function ModalMessage(dispElem) {
    this.show = function() {
        this.container = $("<div />");
        this.container.addClass("modal_message");
        this.container.width(this.width);
        var top = $("<div class='modal_message_ul'></div><div class='modal_message_mid'></div><div class='modal_message_ur'></div><div class='clearbox'></div>");
        var bot = $("<div class='modal_message_ll'></div><div class='modal_message_mid'></div><div class='modal_message_lr'></div><div class='clearbox'></div>");
        var cont = $("<div />");
        cont.addClass("modal_message_content");

        var shadow = $("<div />");
        shadow.addClass("mm_shadow");
        
        var close = function(e) {
            e.data.self.close();
        }
        
        var ctrls = $("<div />");
        ctrls.addClass("modal_message_ctrls");
        if (this.successCallback != null) {
            var btn = $("<button>" + this.successButtonText + "</button>");
            btn.bind("click", this.successCallback);
            ctrls.append(btn);
        }
        var btn = $(" <button>Back</button>").bind("click", { self:this }, close);
        ctrls.append(btn);

        cont.append($(dispElem).show()).append(ctrls);
        this.container.append(top).append(cont).append(bot);

        var self = this;
        kd.registerTemp(function() { // TODO SUPER sloppy global ref of kd
            self.close();
        }, 27);
        
        modal_on(true);
        this.container.hide();
        shadow.hide()
        $("body").append(this.container);
        $("body").append(shadow)
        var top_off = ($(window).height() / 2) - (this.container.outerHeight() / 2) + $(window).scrollTop();
        var left_off = ($(window).width() / 2) - (this.container.outerWidth() / 2);

        shadow.width(this.container.outerWidth());
        shadow.height(this.container.outerHeight());
        shadow.css("top", top_off + 15).css("left", left_off + 15);
        
        this.container.find(".modal_message_mid").width(this.width - 20)
        this.container.css("top", top_off).css("left", left_off);
        this.container.fadeIn(100);
        shadow.fadeIn(100);
    }

    this.close = function() {
        var self = this;
        var cb = function() {
            self.container.remove();
            $(".mm_shadow").remove();
            window.getSelection().removeAllRanges();
        }
        $(".mm_shadow").fadeOut(100);
        this.container.fadeOut(100, cb);
        modal_on(false);
    }

    this.setSuccessCallback = function(callback) {
        this.successCallback = callback;
    }
    this.setSuccessButtonText = function(text) {
        this.successButtonText = text;
    }

    // constructor
    this.width = arguments.length >= 2 ? arguments[1] : 300;
    this.successCallback = null;
    this.successButtonText = "Go!";
    this.dispElem = dispElem;
}


function KeyDispatcher(doc) {
    this.registerPerm = function(callback, keyCode) {
        this._register(this._perm_recipients, callback, keyCode);
    }

    this.registerTemp = function(callback, keyCode) {
        this._register(this._temp_recipients, callback, keyCode);
    }

    this.unregisterPerm = function(keyCode) {
        delete this._perm_recipients[keyCode];
    }

    this._register = function(codeDic, callback, keyCode) {
        if (codeDic[keyCode] == null) {
            codeDic[keyCode] = [callback];
        } else {
            codeDic[keyCode].push(callback);
        }
    }
    
    this._fire = function(keyCode) {
        // fire perms
        if (this._perm_recipients[keyCode] != null) {
            for (var i in this._perm_recipients[keyCode]) {
                this._perm_recipients[keyCode][i]();
            }
        }
        // fire temp
        if (this._temp_recipients[keyCode] != null) {
            if (this._temp_recipients[keyCode].length > 0) {
                this._temp_recipients[keyCode].pop()();
            }
        }
    }

    // constructor
    this._temp_recipients = {};
    this._perm_recipients = {}

    var self = this;
    $(doc).keyup(function(e) {
        self._fire(e.keyCode);
    });
}

function get_url_base() {
    var l = window.location;
    return l.protocol + '//' + l.host + l.pathname;
}

$.prototype.enterpress = function(callback) {
    this.anypress(callback, 13);
    return this;
}

$.prototype.anypress = function(callback, keyCode) {
    var kc = keyCode;
    this.keyup(function(e) {
        if (e.keyCode == kc) { callback(e); }
    });
    return this;
}

String.prototype.trim = function() {
	return this.replace(/^\s+|\s+$/g,"");
}
String.prototype.ltrim = function() {
	return this.replace(/^\s+/,"");
}
String.prototype.rtrim = function() {
	return this.replace(/\s+$/,"");
}


