/*jslint browser: true, passfail: false, forin: true, undef: true, white: true*/

/*global console, $, $$, $A, $H, $F, Autocompleter, BodyLoaded, BrowseActions, Class, Constants, Effect, Element, Event, FileProgress, Prototype, SWFUpload, Ajax, escape*/

/*and sketchier...these should prolly go in Constants*/
/*global contacts, lcontacts, PAGE_PATH*/
if (typeof(console) == 'undefined') {
    console = {
        'log': function () {},
        'profile': function () {},
        'profileEnd': function () {}
    };
}

/*global global_report_exception*/
function assert(cond, msg, force_report) {
    if (!cond) {
        msg = "Assertion Error: " + msg;
        if (!Constants.IS_PROD) {
            alert(msg);
        }
        global_report_exception(msg, window.location.href, window.location.href, true); // force = true
        throw msg;
    }
}

document.observe("dom:loaded", function () {
    var s = document.body.style;
    if (s.WebkitBoxShadow !== undefined || s.MozBoxShadow !== undefined || s.BoxShadow !== undefined || s.boxShadow !== undefined) {
        $(document.body).addClassName("has_box_shadow");
    }
    
    if (s.WebkitBorderRadius !== undefined || s.MozBorderRadius !== undefined || s.BorderRadius !== undefined || s.borderRadius !== undefined) {
        $(document.body).addClassName("has_border_radius");
    }
    
    var elm = new Element("a");
    var orig_color = "#ffffff";
    elm.style.color = orig_color;
    
    try {
        elm.style.color = 'rgba(1,1,1,0.5)';
    } catch (e) {
        //pass :0
    }
     
    if (elm.style.color != orig_color) {
        $(document.body).addClassName("has_rgba");
    }
});

Function.prototype.stop_calls_at = function (count) {
    var f = this;
    return function () {
        if (count-- > 0) {
            return f.apply(this, arguments);
        }
    };
};

var Jcached = {
    cache: {},
    set: function (name, value, expiration) {
        var c = Jcached.cache[name];

        if (!c) {
            Jcached.cache[name] = {};
            c = Jcached.cache[name];
        }

        c.value = value;
        c.expires = expiration ? (new Date()).getTime() + expiration : 0;
    },
    get: function (name) {
        var c = Jcached.cache[name];
        if (!c || (c.expires && (new Date()).getTime() > c.expires)) {
            delete Jcached.cache[name];
            return false;
        }

        return c.value;
    }
};

Function.prototype.cached = function (expiration) {
    // holds the value of thunk (no-arg function) in a cache for a period of time (or forever)
    var r = Math.random();
    var f = this;
    return function () {
        var x = Jcached.get(r);
        if (x !== false) {
            return x;
        }

        x = f();
        Jcached.set(r, x, expiration);
        return x;
    };
};

/*global DomUtil, FlashDetect, Modal, global_report_exception*/
var Util = {
    months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
    center: function (elm) {
        elm = $(elm);
        var l = (document.viewport.getWidth() - elm.getWidth()) / 2;
        elm.setStyle({left: Math.floor(l) + "px"});
    },
    pinTop: function (elm, slide) {
        elm = $(elm);
        if (slide) {
            elm.setStyle({top: window.pageYOffset + "px"});
        } else {
            var e = new Effect.Move(elm, {y: window.pageYOffset, mode: 'absolute', duration: 0.25});
        }
    },
    getTickWaiter: function (n, action) {
        var ticks = 0;
        return function () {
            if (ticks == n) {
                action();
            }
            ticks++;
        };
    },
    calcBox: function (t1, l1, t2, l2, box) {
        box.top = Math.min(t1, t2);
        box.left = Math.min(l1, l2);
        box.width = Math.abs(l1 - l2);
        box.height = Math.abs(t1 - t2);
    },
    initBox: function (top, left, box) {
        box.top = top;
        box.left = left;
        box.width = 0;
        box.height = 0;
    },
    pointOnBox: function (left, top, box) {
        return (top  >= box.top  && top  <= box.top  + box.height &&
                left >= box.left && left <= box.left + box.width);
    },
    cmpBox: function (left, top, box) {
        // returns cmp-style results regarding the point's position relative to the box
        if (top < box.top || left < box.left) {
            return -1;
        }

        if (top > box.top  + box.height || left > box.left + box.width) {
            return 1;
        }

        return 0;
    },
    boxOnBox: function (box1, box2) {
        var max_top = Math.max(box1.top, box2.top);
        var max_left = Math.max(box1.left, box2.left);
        var min_bottom = Math.min(box1.top + box1.height, box2.top + box2.height);
        var min_right = Math.min(box1.left + box1.width, box2.left + box2.width);

        return (max_top < min_bottom && max_left < min_right);
    },
    reduceBox: function (box, pct) { // reduces size of the box, keeping center
        var out = {};
        out.width = box.width * pct;
        out.height = box.height * pct;
        out.top = box.top + (box.height - out.height) / 2;
        out.left = box.left + (box.width - out.width) / 2;

        return out;
    },
    getBox: function (elm) {
        elm = $(elm);
        var d = elm.getDimensions();
        var p = elm.viewportOffset();
        return {top: p.top, left: p.left, width: d.width, height: d.height};
    },
    ts: function () {
        var d = new Date();
        return d.getUTCFullYear().toString() + "-" +
            (d.getUTCMonth() + 1).toString().lpad(2) + "-" +
            d.getUTCDate().toString().lpad(2) + " " +
            d.getUTCHours().toString().lpad(2) + ":" +
            d.getUTCMinutes().toString().lpad(2) + ":" +
            d.getUTCSeconds().toString().lpad(2);
    },
    start_of_day: function (date) {
        var d = new Date();
        d.setTime(date.getTime());
        d.setHours(0);
        d.setMinutes(0);
        d.setSeconds(0);
        d.setMilliseconds(0);

        return d;
    },
    to_mysql_date: function (d, include_seconds) {
        var date = d.getFullYear().toString() + "-" + (d.getMonth() + 1).toString().lpad(2) + "-" + d.getDate().toString().lpad(2);
        var time = d.getHours().toString().lpad(2) + ":" + d.getMinutes().toString().lpad(2) + ":" + d.getSeconds().toString().lpad(2) + "." + d.getMilliseconds().toString().lpad(3);

        if (!include_seconds) {
            return date;
        } else {
            return date + " " + time;
        }
    },
    from_mysql_date: function (v) {
        var parts = v.split(" ");
        var date = parts[0];
        var time = parts.length > 1 ? parts[1] : false;

        var date_parts = date.split("-");
        assert(date_parts.length == 3, "weird date format on {d}, expected yyyy-mm-dd".interpolate({'d': date}));

        var out = new Date(date_parts[0], parseInt(date_parts[1], 10) - 1, date_parts[2]);

        if (time) {
            var time_parts = time.split(":");
            assert(time_parts.length == 3, "weird time format on {t}, expected hh:mm:ss.ms".interpolate({'t': time}));
            out.setHours(time_parts[0]);
            out.setMinutes(time_parts[1]);

            var sec_parts = time_parts[2].split(".");
            out.setSeconds(sec_parts[0]);
            if (sec_parts.length > 1) {
                out.setMilliseconds(sec_parts[1]);
            }
        }

        return out;
    },
    make_table: function (dict, attrs) {
        var table = new Element("table", attrs);
        var tbody = new Element("tbody");
        table.insert(tbody);

        for (var key in dict) {
            var tr = new Element("tr");
            var title = new Element("td").insert(key);
            var desc = new Element("td").insert(dict[key]);
            tr.insert(title);
            tr.insert(desc);
            tbody.insert(tr);
        }
        return table;
    },
    time: function () {
        return (new Date()).getTime();
    },
    last_time: false,
    delta: function (init) {
        var time = Util.time();
        if (Util.last_time && (!init || typeof(init) != 'boolean')) {
            Util.log(time - Util.last_time);
        }
        Util.last_time = time;
        if (typeof(init) == 'string') {
            Util.log("^ " + init);
        }
    },
    toggle_names: {},
    toggle: function (name) {
        if (Util.toggle_names[name]) {
            Util.toggle_names[name] = false;
        } else {
            Util.toggle_names[name] = true;
        }

        return Util.toggle_names[name];
    },
    reset_toggle: function (name) {
        Util.set_toggle(name, false);
    },
    set_toggle: function (name, val) {
        Util.toggle_names[name] = val;
    },
    set_next_toggle: function (name, val) {
        Util.toggle_names[name] = !val;
    },
    url_hash: function () {
        var hash = window.location.href.split(window.location.pathname).last();
        if (hash.indexOf("#") >= 0) {
            return hash.split("#").last();
        } else {
            return "";
        }
    },
    copy_to_clipboard: function (text, alt_title, alt_body)
    {
        var cb_elt = $("hold_clipboard");
        cb_elt.value = text;
        if (cb_elt.createTextRange) {
            var range = cb_elt.createTextRange();
            if (range && (typeof(BodyLoaded) == 'undefined' || BodyLoaded == 1)) {
                try {
                    range.execCommand('Copy');
                } catch (e) {
                    alt_body = alt_body || "Please copy the text below:";
                    alt_title = alt_title || "Copy text";

                    DomUtil.fillVal(text, 'text-to-copy');
                    DomUtil.fillVal(alt_body, "copy-modal-body");
                    Modal.show(alt_title, DomUtil.fromElm('copy-modal'));
                    $('text-to-copy').select();
                }
            }
        } else {
            if (!$("flashcb")) {
                var divholder = document.createElement('div');
                divholder.id = "flashcb";
                document.body.appendChild(divholder);
            }
            $("flashcb").innerHTML = '';
            var divinfo = '<embed src="/static/swf/_clipboard.swf" FlashVars="clipboard=' + encodeURIComponent(cb_elt.value) + '" width="0" height="0" type="application/x-shockwave-flash"></embed>';
            $("flashcb").innerHTML = divinfo;
        }
    },
    report_exception: global_report_exception,
    scrollTop: function () {
        return window.scrollY || document.documentElement.scrollTop || 0;
    },
    scrollLeft: function () {
        return window.scrollX || document.documentElement.scrollLeft || 0;
    },
    setCursor: function (cursor) {
        if (!document.styleSheets[0].cssRules) {
            return;
        }
        (document.styleSheets[0].rules || document.styleSheets[0].cssRules)[0].style.cursor = cursor;
        (document.styleSheets[0].rules || document.styleSheets[0].cssRules)[1].style.cursor = cursor;
    },
    clearCursor: function () {
        if (!document.styleSheets[0].cssRules) {
            return;
        }
        (document.styleSheets[0].rules || document.styleSheets[0].cssRules)[0].style.cursor = 'auto';
        (document.styleSheets[0].rules || document.styleSheets[0].cssRules)[1].style.cursor = 'pointer';
    },
    noHorizScroll: function () {
        if (!(/Mac.*(Firefox\/3|Camino)/.match(navigator.userAgent))) {
            document.body.style.overflowX = 'hidden';
        }
    },
    allowHorizScroll: function () {
        document.body.style.overflowX = '';
    },
    scried: {},
    scry: function (id) {
        /* It's like $, but returns a cached reference to the extended element
         * It's only going to look it up once, so don't use if you're playing
         * games with creating new elements with old id's.
         *
         * If the DOM obj has a reference back to Javascript, eventually
         * kill it so it doesn't leak.
         *
         * The name is stolen from a mythical facebook function name.
         * Look it up in the dictionary if you wanna know what it means.
         */
        var scried = Util.scried;
        var elm = scried[id];
        if (!elm) {
            elm = $(id);
            scried[id] = elm;
        }
        return elm;
    },
    pathDepth: function (path) {
        var parts = path.split("/");

        var depth = 0;
        for (var i = 0; i < parts.length; i++) {
            if (parts[i].length) {
                depth++;
            }
        }

        return depth;
    },
    normalize: function (path) {
        if (!path) {
            return "/";
        }

        path = path.strip();

        var root = "";
        if (!path) {
            root = "";
        } else {
            root = path;
        }

        if (! root.startsWith("/")) {
            root = "/" + root;
        }
        if (root.endsWith("/")) {
            root = root.substr(0, root.length - 1);
        }

        return root;
    },
    normPath: function (path) {
        if (!path || path.charAt(path.length - 1) != '/') {
            return path;
        }

        return path.substr(0, path.length - 1);
    },
    normDir: function (dir_path) {
        return Util.normPath(dir_path) + "/";
    },
    parentDir: function (path) {
        return path.split("/").slice(0, -1).join("/") + "/";
    },
    urlquote: function (path) {
        return path.split("/").map(encodeURIComponent).join("/");
    },
    unevent: function (d) { // Douglas Crockford's purge from http://javascript.crockford.com/memory/leak.html, but just for mouse events
        if (d.attributes) {
            d.onclick = null;
            d.onmouseover = null;
            d.onmouseout = null;
            d.onmousedown = null;
            d.onmouseup = null;
            d.onmousemove = null;
        }

        var a = d.childNodes, i, l;
        if (a) {
            l = a.length;
            for (i = 0; i < l; i += 1) {
                Util.unevent(a[i]);
            }
        }
    },
    yank: function (elm) {
        // yank an element out of the dom without memory leaking
        Util.unevent(elm);

        //Element.remove(elm);

        // interesting way to delete a node that doesn't create pseudo-leaks in ie
        if (!Util.dom_trash_can) {
            Util.dom_trash_can = Util.scry('trash-can');
        }
        Util.dom_trash_can.insert(elm);
        Util.dom_trash_can.update();

        elm = null;

        return elm;
    },
    ie6: window.external && typeof window.XMLHttpRequest == "undefined",
    ie: Prototype.Browser.IE,
    linux_ff3: navigator.userAgent.toLowerCase().indexOf('linux') > -1,
    log: function () {
        Util.scry('ieconsole').innerHTML += $A(arguments).join(" ") + "<br>";
    },
    childElement: function (elm, index) {
        var a = Util.childElementWithIndex(elm, index);
        return a[0];
    },
    childElementWithIndex: function (elm, index) {
        var count = 0;
        var nodes = elm.childNodes;
        var l, i;

        l = nodes.length;
        for (i = 0; i < l; ++i) {
            var e = nodes[i];
            if (e.nodeType == 1 && count++ == index) {
                return [e, i];
            }
        }
        return [false, false];
    },
    childElementCache: {},
    childElementCached: function (key, elm, index, dont_cache) {
        var cached = Util.childElementCache[key];
        if (cached !== undefined && dont_cache !== true) {
            return elm.childNodes[cached];
        }

        var child = Util.childElementWithIndex(elm, index, true);
        Util.childElementCache[key] = child[1];
        return child[0];
    },
    childElementByIndexPath: function (elm, index_path) {
        var l = index_path.length;
        for (var i = 0; i < l; i += 1) {
            elm = Util.childElement(elm, index_path[i]);
        }
        return elm;
    },
    disableSelection: function (elm) { // Bret Taylor's disable selection from http://ajaxcookbook.org/disable-text-selection
        elm.onselectstart = function () {
            return false;
        };
        elm.unselectable = "on";
        // elm.style.MozUserSelect = "none";
        elm.style.cursor = "default";
    },
    enableSelection: function (elm) {
        elm.onselectstart = function () {
            return true;
        };
        elm.unselectable = "off";
        // elm.style.MozUserSelect = "";
        elm.style.cursor = "";
    },
    bsearch: function (lst, elm, key) {
        if (!key) {
            key = function (e) {
                return e;
            };
        }

        var hi = lst.length; // exclusive on hi
        var lo = 0; // inclusive on lo

        while (hi > lo) {
            var mid = Math.floor(hi / 2 + lo / 2);
            var test = key(lst[mid]);

            if (test > elm) {
                hi = mid;
            } else if (test < elm) {
                lo = mid + 1;
            } else {
                return mid;
            }
        }

        return -1;
    },
    nonce: function () {
        var d = new Date();
        var time_part = d.getTime().toString();
        var rand_part = Math.floor(Math.random() * 1000000).toString().lpad(6);
        return time_part + rand_part;
    },
    focus: function (elm) {
        elm = $(elm);
        try {
            elm.focus();
        } catch (e) {
            // it's okay if focus fails
        }
    },
    sumStyles: function (element, style_list) {
        var elm_total = 0;
        if (element) {
    	    style_list.each(
                function (attr) {
                    elm_total += parseInt(element.getStyle(attr), 10) || 0; // IE chokes on undefined borders
                }
            );
	    }
        return elm_total;
    },
    syncHeight: function () {
        // This gets maximum height of elemens with class sync-height and subtracts the padding and border from the height;
        var max_height = $$('.sync-height').invoke("getHeight").max() - Util.sumStyles($$('.sync-height')[0], ["border-left-width", "padding-left", "padding-right", "border-right-width"]);
        $$('.sync-height').invoke("setStyle", {"height": max_height > 0 ? max_height + "px" : "auto"});
    },
    formatBytes: function (value, digits, include_space, lazy) {

        value = parseFloat(value);
        var abs_value = Math.abs(value);

        var amt, suffix;
        if (abs_value < 1024) {
            digits = 0;
            include_space = true;
            amt = value;
            if (value != 1) {
                suffix = 'bytes';
            } else {
                suffix = 'byte';
            }
        } else if (abs_value < 900 * 1024) {
            amt = value / 1024;
            suffix = 'KB';
        } else if (abs_value < 900 * 1048576) {
            amt = value / 1048576;
            suffix = 'MB';
        } else if (abs_value < 900 * 1073741824 || (digits === 0 && value < 1048576 * 1048576)) {
            amt = value / 1073741824;
            suffix = 'GB';
        } else {
            amt = value / (1048576 * 1048576);
            suffix = 'TB';
        }

        amt = Math.round(amt * Math.pow(10, digits)) / parseFloat(Math.pow(10, digits));
        amt = amt.toFixed(digits);

        var result;
        if (lazy && digits > 0) {
            if (amt != Math.floor(amt)) {
                result = amt;
            } else {
                result = parseInt(Math.floor(amt), 10);
            }
        } else {
            result = amt;
        }

        if (include_space) {
            result = result + " " + suffix;
        }

        return result;

    },

    formatTime: function (seconds) {
        var sizeUnits = [86400, 3600, 60, 1];
        var sizeLabels = ["day", "hour", "min", "sec"];
        var label, value;

        for (var i = 0; i < sizeUnits.length; i += 1) {
            if (seconds >= sizeUnits[i]) {
                label = sizeLabels[i];
                value = parseInt(seconds / sizeUnits[i], 10) || 0;
                break;
            }
        }

        if (seconds < 1) {
            value = 0;
            label = sizeLabels[3];
        }

        if (value > 1) {
            label = label + "s";
        }

        return value + " " + label;
    },

    plural: function (number, word, exclude_number) {
        var pluralization;
        if (!exclude_number) {
            pluralization = number + " " + word;
        } else {
            pluralization = word;
        }

        if (number != 1) {
            pluralization += "s";
        }
        return pluralization;
    },

    is_right_click: function (event) {
        // Based on quirksmode.org example: http://www.quirksmode.org/js/events_properties.html
        var rightclick = false;
        if (event.which) {
            rightclick = (event.which == 3);
        } else if (event.button) {
            rightclick = (event.button == 2);
        }
        return rightclick;
    },
    removeClassNameRegex: {},
    removeClassName: function (elm, className) {
	// Caches regular expressions as they're quite slow to create each time.
        if (!elm) {
	        return;
	    }

    	var regexp = Util.removeClassNameRegex[className];
    	if (!regexp) {
    	    Util.removeClassNameRegex[className] = regexp = new RegExp("(^|\\s+)" + className + "(\\s+|$)");
    	}

        elm.className = elm.className.replace(regexp, ' ').strip();
        return elm;
    },
    observe: function (element, name, callback) {
        element = Element.extend(element);
	    if (element.addEventListener) {
            element.addEventListener(name, callback, false);
        } else {
            element.attachEvent("on" + name, callback);
        }
	},
    nop: function () {
        return false;
    },
    niceDate: function (date) {
        date = date || new Date();
        return 1 + date.getMonth() + "/" + date.getDate() + "/" + date.getFullYear();
    },
    replaceHtml: function (old_elm, html) {
        //  Based on Steven Levithan's replaceHtml http://blog.stevenlevithan.com/archives/faster-than-innerhtml
    	if (Prototype.Browser.IE) {
    		old_elm.innerHTML = html;
    		return old_elm;
		}

    	var new_elm = old_elm.cloneNode(false);
    	new_elm.innerHTML = html;
    	old_elm.parentNode.replaceChild(new_elm, old_elm);

    	return new_elm;
    },
    isNumber: function (number) {
        return !isNaN(Number(number, 10));
    },
    resolve_target: function (elm, selector) {
        elm = $(elm);
        while (elm && elm != document.body) {
            if (elm.match(selector)) {
                return elm;
            } else {
                elm = elm.parentNode && Element.extend(elm.parentNode);
            }
        }
        return false;
    },
    shorten_url: function (url, callback) {
        var abdr = new Ajax.DBRequest("/shorten_url", {
            parameters: { 'url': url },
            onSuccess: function (req) {
                callback(req.responseText);
            }
        });
    },
    flash_version: function () {
        return FlashDetect.major + "." + FlashDetect.revision;
    },
    falsy_to_empty: function (v) {
        return v || '';
    },
    check_crossdomain_loadable: function () {
        if (Prototype.Browser.IE) {
            return;
        }

        var scriptTag = document.createElement("script");
        scriptTag.setAttribute("type", "text/javascript");
        scriptTag.setAttribute("src", "https://" + Constants.BLOCK_CLUSTER + "/crossdomain.xml");

        window.crossdomainLoadable = false;
        $(scriptTag).observe("load", function () {
            window.crossdomainLoadable = true;
        });

        setTimeout(function () {
            Util.report_exception((window.crossdomainLoadable ? "+ Could" : "- Could NOT") + " load crossdomain.xml", window.location.href, window.location.href, true); // force=true
        }, 2000);

        document.getElementsByTagName("head")[0].appendChild(scriptTag);
    }
};
Util.scrollLeft = Util.scrollLeft.cached(50);
Util.scrollTop = Util.scrollTop.cached(50);

document.observe("dom:loaded", Util.syncHeight);

String.prototype.widthSplit = function (width) {
    width = width || 15;
    var out = [];
    var s = this;

    var start = 0;
    var snip = s.substring(start, start + width);
    while (snip !== '') {
        out.push(snip);
        start += width;
        snip = s.substring(start, start + width);
    }

    return out;
};

String.prototype.lpad = function (width, filler) {
    var str = this;
    filler = filler || "0";

    while (str.length < width) {
        str = filler + str;
    }
    return str;
};

String.prototype.pad_nums = function () {
    return this.replace(/(\d+)/, function (s) {
        return s.lpad(10);
    });
};

String.prototype.reverse = function () {
    var splitext = this.split("");
    var revertext = splitext.reverse();
    var reversed = revertext.join("");
    return reversed;
};

String.prototype.replace_last = function (bad, good) {
    var rstr = this.reverse();
    var rbad = bad.reverse();
    var rgood = good.reverse();

    return rstr.replace(rbad, rgood).reverse();
};

String.prototype.create = function (s) {
    return s;
};

String.prototype.snippet = function (maxchars, where) {
    maxchars = maxchars || 45;
    where = where || 0.75;

    if (this.length <= maxchars) {
        return this;
    }

    var dot_pos = this.lastIndexOf(".");
    var ext = "";

    if (dot_pos > 0) {
        ext = this.substr(dot_pos);
        maxchars = maxchars - ext.length;
    } else {
        dot_pos = this.length;
        ext = "";
    }

    maxchars = maxchars - this.create("...").length;

    var left = Math.floor(maxchars * where);
    var right_count = maxchars - left;
    var right = dot_pos - right_count;
    var pre = this.substr(0, left);
    var post = this.substr(right, dot_pos - right);

    return pre + "..." + post + ext;
};


var Emstring = Class.create(
    {
        initialize: function (s) {
            this.s = s;
            this.info = this.widthInfo();
            this.length = s.length ? this.info[this.s.length - 1] : 0;
        },
        create: function (s) {
            return new Emstring(s);
        },
        widthInfo: function () {
            var r = {};
            r[-1] = 0;

            for (var i = 0; i < this.s.length; i++) {
                r[i] = r[i - 1] + this.ems(this.s.charAt(i));
            }

            return r;
        },
        findSpot: function (em) {
            if (!em) {
                return 0;
            }

            var s = 0;
            var e = this.s.length;
            while (s <= e) {
                var mid = Math.floor(s / 2 + e / 2);
                var count = this.info[mid - 1];

                if (count > em) {
                    e = mid - 1;
                } else if (count < em) {
                    s = mid + 1;
                } else { //exact match
                    return mid;
                }
            }

            // missed exact match, but close
            if (s > mid) {
                return s;
            } else {
                return mid;
            }
        },
        ems: function (c) {
            if (c == 'i' || c == 'l') {
                return 0.3;
            }
            if (c == 'm' || c == 'w') {
                return 1;
            }
            return 0.6;
        },
        substr: function (start, len) {
            start = this.findSpot(start);
            len = len !== null ? this.findSpot(start + len) : this.s.length;

            return new Emstring(this.s.substr(start, len));
        },
        indexOf: function (s) {
            var at = this.s.indexOf(s);
            return at > -1 ? this.info[at - 1] : -1;
        },
        lastIndexOf: function (s) {
            var backwards_pos = this.s.reverse().indexOf(s.reverse());
            if (backwards_pos < 0) {
                return -1;
            }

            return this.info[(this.s.length - backwards_pos) - s.length - 1];
        },
        toString: function () {
            return this.s;
        },
        snippet: String.prototype.snippet
    });

var Job = {
    complete: {},
    handled: function (job_id) {
        if (!job_id) {
            return false;
        }

        var already_handled = !!Job.complete[job_id];
        Job.complete[job_id] = true;

        return already_handled;
    },
    peek: function (job_id) {
        if (!job_id) {
            return false;
        }

        return !!Job.complete[job_id];
    }
};

/*global Notify*/
var RequestWatcher = {
    reqs: [],
    working_msg: "Still working...",
    TIMEOUT: 5,
    watch: function (req, no_message) {
        var r = RequestWatcher.reqs;

        if (!r.length) {
            RequestWatcher.int_id = setInterval(RequestWatcher.check_up, 500);
        }

        if (no_message) {
            req.skip_message = true;
        }

        r.push([req, Util.time()]);
    },
    check_up: function () {
        RequestWatcher.scan(false); // req_to_remove=false
    },
    remove: function (req) {
        RequestWatcher.scan(req); // req_to_remove=req
    },
    scan: function (req_to_remove) {
        var now = Util.time();
        var l = RequestWatcher.reqs.length;
        var r = RequestWatcher.reqs;

        var out = [];

        for (var i = 0; i < l; i++) {
            var req = r[i][0];
            var started = r[i][1];
            var delta = now - started;

            if (req.transport.readyState == 4) {
                // done with the request
                Notify.clearIf(RequestWatcher.working_msg);
                continue;
            }

            if (delta > 2000 && !req.skip_message) {
                req.skip_message = true;
                Notify.ServerSuccess(RequestWatcher.working_msg);
            }

            if (delta > RequestWatcher.TIMEOUT * 1000 && req.job) {
                req.transport.abort();
            }

            if (req != req_to_remove) {
                out.push([req, started]);
            } else {
                req.transport.abort();
            }
        }

        RequestWatcher.reqs = out;

        if (!out.length) {
            clearInterval(RequestWatcher.int_id);
        }
    }
};

/*global ModalProgress*/
var ProgressWatcher = {
    job_info: {},
    INIT_POLL_INT: 1000,
    FAILS_MEAN_FAIL: 3,
    MODAL_WAIT_MS: 1000,
    watch: function (req) {
        ProgressWatcher.job_info[req.job_id] = {};
        var info = ProgressWatcher.job_info[req.job_id];

        info.req = req;
        info.poll_int = ProgressWatcher.INIT_POLL_INT;
        info.poll_count = 0;
        info.int_id = setInterval(ProgressWatcher.update_for(req.job_id), info.poll_int);
        info.failures = 0;
        info.start_time = Util.time();
    },
    update_for: function (id) {
        return function () {
            return ProgressWatcher.update(id);
        };
    },
    backoff: function (id) {
        var info = ProgressWatcher.job_info[id];
        clearInterval(info.int_id);
        info.poll_int = Math.min(Math.floor(info.poll_int * 1.5), 30000); // 30 seconds is longest poll interval
        info.int_id = setInterval(ProgressWatcher.update_for(id), info.poll_int);
    },
    update: function (id) {
        var req_info = ProgressWatcher.job_info[id];
        if (Job.peek(id)) {
            return ProgressWatcher.done(id);
        }
        req_info.poll_count++;

        if (req_info.poll_count % 10 === 0) {
            // time to slow this train down
            ProgressWatcher.backoff(id);
        }

        if (!req_info.modaled && Util.time() - req_info.start_time > ProgressWatcher.MODAL_WAIT_MS) {
            var options = req_info.req.options;
            ModalProgress.show(options.progress_text, options.cover_this);
            options.onProgress = ModalProgress.update;
            req_info.modaled = true;
        }

        var ar = new Ajax.Request("/job_status/" + id,
        {
            method: 'post',
            t: Constants.TOKEN,
            onSuccess: function (req) {
                var info = ProgressWatcher.job_info[id];
                var progress = req.responseText;
                var kill_watcher = false;

                if (progress.indexOf("err") === 0) {
                    ProgressWatcher.done(id);
                    ModalProgress.hide();

                    if (info.req.options.onFailure && !Job.handled(id)) {
                        info.req.options.onFailure(req);
                    }

                    return;
                }

                if (progress.indexOf("done") === 0) {
                    // go pick up the results, passing them back like normal
                    info.req.options.job = false; // return the handlers to normal mode

                    if (!Job.handled(id)) {
                        var ar = new Ajax.Request("/job_results/" + id, {
                            onSuccess: function (r) {
                                if (Job.handled(id)) {
                                    return;
                                }
                                Notify.clearIf(RequestWatcher.working_msg);
                                if (info.req.options.onSuccess) {
                                    info.req.options.onSuccess(r);
                                }
                            },
                            onFailure: function (r) {
                                if (Job.handled(id)) {
                                    return;
                                }
                                Notify.clearIf(RequestWatcher.working_msg);
                                if (info.req.options.onFailure) {
                                    info.req.options.onFailure(r);
                                }
                            }
                        });

                    }

                    // progress is 100%
                    var parts = progress.split("/");
                    progress = parts[1] + "/" + parts[1];

                    ProgressWatcher.done(id);
                    ModalProgress.hide();
                } else {
                    // update the progress counter
                    try {
                        if (info.req.options.onProgress) {
                            info.req.options.onProgress(req.responseText);
                        }
                    } catch (e) {}
                }
            },
            onFailure: function (req) {
                var info = ProgressWatcher.job_info[id];

                info.failures++;
                if (info.failures >= ProgressWatcher.FAILS_MEAN_FAIL) {
                    // we're not going to try again
                    if (info.req.options.onFailure) {
                        info.req.options.onFailure(req, true); // force=true
                    }
                    RequestWatcher.remove(info.req);
                    ProgressWatcher.done(id);
                    ModalProgress.hide();
                }
            }
        });
    },
    done: function (id) {
        var info = ProgressWatcher.job_info[id];
        clearInterval(info.int_id);
        delete ProgressWatcher.job_info[id];
        ModalProgress.hide();
    }
};

Ajax.DBRequest = Class.create(Ajax.Request,
{
    initialize: function ($super, url, options) {
        options = options || {};
        options.method = 'post';
        options.parameters = options.parameters || {};
        options.parameters.t = Constants.TOKEN;
        var cleanUp = options.cleanUp || function (ok) {};

        if (options.job) {
            this.job_id = Util.nonce();
            options.parameters.job_id = this.job_id;
            ProgressWatcher.watch(this);
        }

        RequestWatcher.watch(this, !!options.job); //no_still_working=!!options.job

        var origOnFailure = options.onFailure;
        var origOnSuccess = options.onSuccess;
        var origOnComplete = options.onComplete;

        options.onFailure = function (req) {
            if (Job.handled(req.request.job_id)) {
                return;
            }
            if (!options.noAutonotify) {
                Notify.ServerError();
            }
            cleanUp(false); // okay=false
            if (origOnFailure) {
                origOnFailure(req);
            }
        };

        options.onSuccess = function (req) {
            if (Job.handled(req.request.job_id)) {
                return;
            }

            if (!req.responseText.length) {
                if (!options.job) {
                    if (!options.noAutonotify) {
                        if (!req || req.status !== 0) { // User interupts ajax request so lets not show a error notification
                            Notify.ServerError();
                        }
                    }

                    if (origOnFailure) {
                        origOnFailure(req);
                    }
                }
            } else if (req.responseText.indexOf('err:') === 0) {
                if (!options.noAutonotify) {
                    Notify.ServerError(req.responseText.substr(4));
                }

                if (origOnFailure) {
                    origOnFailure(req);
                }
            } else {
                if (origOnSuccess) {
                    origOnSuccess(req);
                }
                if (origOnComplete) {
                    origOnComplete(req);
                }
            }

            cleanUp(true); // okay=true
        };

        if (options.job) {
            url += (url.indexOf("?") != -1 ? "&" : "?") + "long_running";
        }

        // make the request
        $super(url, options);
    }
});

/*global SuggestionInput, Forms, FileOps, Browse, Flash, Feed, Invitations, Referral, Sprite, TreeView, unescape*/
var Sharing = {

    reset_wizard: function () {
        // Remove folder_name/path input from invite_more form
        SuggestionInput.reset("new-collab-input");
        SuggestionInput.reset("custom-message-wizard");
        SuggestionInput.reset("new_folder_name");

        var input = $("invite-more-form").down("input[name='folder_name']");

        if (input) {
            input.remove();
        }
    },
    validate_folder: function (e, form, success_callback) {
        form = $(form);

        // Add a pass through for an already shared folder to just send them to invite more
        var path_elm = form.down("#folder_name");
        if (path_elm) {
            if ($$("#modal-content #copy-move-treeview .highlight .s_folder_user_blue").length) {
                var escaped_path = Util.urlquote($F(path_elm));
                return Sharing.show_invite_more_modal(escaped_path);
            }
        }

        var target = $(e.target);

        if (target && target.tagName != "input") {
            var m = target.up("#modal-content");
            target = m.down("input.button");
        }

        assert(form, "Trying to validate a folder where the form doesn't exist");
        Forms.ajax_submit(form, false,
            function () {
                if (success_callback && typeof(success_callback) == "function") {
                    success_callback(e, form);
                }
            },
            false,
            target);
        return false;
    },

    start_wizard: function (event) {
        Sharing.reset_wizard();
        Event.stop(event);
        Modal.icon_show(BrowseActions.getIcon('share_new'), "Share a Folder", $("shared-folder-wizard"));
        $("create-new-sf").focus();
    },

    wizard_next: function (event) {
        Event.stop(event);
        if ($('create-new-sf').checked) { // New shared folder
            Sharing.validate_folder(event, "validate-folder-name", Sharing.from_new_to_invitation);

        } else {
            Modal.show(new Element("span").insert(Sprite.make("folder_user", {}).addClassName("modal-h-img")).insert("Choose Folder to Share"), $("existing-shared-folder-wizard"));
            TreeView.move('copy-move-treeview', 'share-existing-treeview');
            $("modal").observe("db:treeview_selected", function (e) {
                $("folder_name").setValue(e.memo.path);
            });

            // prep by selecting the first
            var first_link = $('first-treeview-link');
            if (!Util.ie) {
                first_link.onclick();
            }
        }
        $$("#modal-content .suggestion-input").each(SuggestionInput.register);
    },

    from_new_to_invitation: function (e, form) {
        var folder_name = $F(form.down("#new_folder_name")).strip();
        assert(folder_name, "Moving from new folder to invite modal with no path.");

        Sharing.show_invite_more_wizard(folder_name);
        Modal.vars.action = Sharing.submit_share_new_wizard;
        Modal.vars.path = "/" + folder_name;
        var folder_name_input = new Element("input", {type: "hidden", name: "folder_name"});
        folder_name_input.setValue(folder_name);

        $("invite-more-form").insert(folder_name_input);
        $("share-invite-button").setValue("Share folder");
    },

    from_existing_to_invitation: function (e, form, path) {
        if (!path) {
            path = $F(form.down("#folder_name")).strip();
        }
        assert(path, "Moving from choose a folder to invite modal with no path.");

        Sharing.show_invite_more_wizard(FileOps.raw_filename(path));
        Modal.vars.action = Sharing.submit_share_existing_wizard;
        Modal.vars.path = path.strip();

        var folder_path = new Element("input", {type: "hidden", name: "path"});
        folder_path.setValue(path);

        $("invite-more-form").insert(folder_path);
        $("share-invite-button").setValue("Share folder");

    },

    show_invite_more_wizard: function (folder_name) {
        assert(folder_name, "Folder name required");
        DomUtil.fillVal("folder", "invite-more-wizard-share-type");
        Modal.icon_show("folder_user", "Share  \"" + folder_name.escapeHTML() + "\" with others", $("invite-more-wizard"));
        if (!Sharing.auto_completer) {
            Sharing.auto_completer = new Autocompleter.Contacts("new-collab-input", "new-whobulk", contacts, lcontacts, {tokens: [',', ';']});
        }
    },

    show_cli: function () {
        Referral.select_all = 0;
        Sharing.old_state = Modal.vars;
        Modal.onHide = Sharing.hide_cli;
        Referral.show_login_modal();
    },

    hide_cli: function () {
        Referral.select_no_contacts();
        Sharing.add_from_cli();
        Sharing.load_contacts();
        delete Modal.onHide;
        return false;
    },

    add_from_cli: function () {
        var emails = Referral.get_selected_emails();

        Sharing.show_invite_more_wizard(FileOps.raw_filename(Sharing.old_state.path));
        Modal.vars = Sharing.old_state;

        var email_input = $("new-collab-input");

        SuggestionInput.do_blank("new-collab-input");
        var current_value = $F(email_input);

        if (emails) {
            email_input.setValue(current_value + (current_value.length > 0 ? ", " : "") + emails);
            email_input.addClassName('suggestion-input-unfaded');
            email_input.defaulted = false;
        }

        delete Modal.onHide;
    },

    submit_share_new_wizard: function (e) {
        var form = $("invite-more-form");
        assert(form, "Couldn't find the invite more form.");

        Forms.ajax_submit(form, "/share_ajax/new",
            function () {
                Modal.hide();
                if (Sharing.is_new) {
                    window.location = "/home" + Util.urlquote(Modal.vars.path);
                } else {
                    Notify.ServerSuccess("The shared folder \"" + FileOps.raw_filename(Modal.vars.path) + "\" has been created.");
                    Browse.reload(Browse.current_path, true);
                    TreeView.reset();
                }
            },

            function () {
                Forms.enable(form.down("input[type='submit']"));
            },
            e.target);
        return false;
    },

    submit_share_existing_wizard: function (e) {
        var form = $("invite-more-form");
        assert(form, "Couldn't find the invite more form.");

        Forms.ajax_submit(form, "/share_ajax/existing?long_running",
            function () {
                Modal.hide();
                if (Sharing.is_new) {
                    window.location = "/home" +  Util.urlquote(Modal.vars.path);
                } else {
                    Notify.ServerSuccess("Shared folder \"" + FileOps.raw_filename(Modal.vars.path).escapeHTML() + "\" created.");
                    Browse.reload(Browse.current_path, true);
                }
            },

            function () {
                Forms.enable(form.down("input[type='submit']"));
            },
            e.target);
        return false;
    },
    show_share_existing_modal: function (path) {
        Sharing.reset_wizard();
        Sharing.from_existing_to_invitation(false, false, decodeURIComponent(path));
    },
    show_invite_more_modal: function (path) {
        Sharing.reset_wizard();
        path = decodeURIComponent(path);
        PAGE_PATH = path;
        assert(path.length, "show_invite_more_modal: No shared folder path.");

        Sharing.show_invite_more_wizard(FileOps.raw_filename(path));
        Modal.vars.action = Sharing.submit_invite_more_wizard;
        Modal.vars.path = path.strip();

        var folder_path = new Element("input", {type: "hidden", name: "path"});
        folder_path.setValue(path);

        $("share-invite-button").setValue("Invite more");
        $("invite-more-form").insert(folder_path);
    },
    submit_invite_more_wizard: function (e) {
        var path = Browse.current_path || PAGE_PATH;

        assert(path.length, "submit_invite_more_wizard: No shared folder path.");

        var form = $("invite-more-form");
        Forms.ajax_submit(form,
            "/share_ajax/invite_more",
            function () { // On Success
                window.location.reload();
            },
            false,
             e.target);
        return false;
    },
    show_leave_modal: function (folder_path) {
        Modal.icon_show("folder_user_delete", "Leave the Shared Folder \"" + FileOps.filename(folder_path).escapeHTML() + "\"", DomUtil.fromElm("leave-confirm"));
        var form = $("leave-share-form");
        form.action = form.action.replace("%25path%25",  folder_path);
    },
    submit_leave: function (e) {
        var path = Browse.current_path || PAGE_PATH;
        assert(path.length, "submit_leave: No shared folder path.");

        var form = $("leave-share-form");
        Forms.ajax_submit(form, false,
            function () { // On Success
                if (Feed.ns_id) {
                    window.location = "/share";
                } else {
                    Notify.ServerSuccess("You removed yourself from the shared folder.");
                    Modal.hide();
                    Browse.reload(Browse.current_path, true);
                }
            },
            function () {
            },
            e.target);

        return false;
    },

    show_unshare_modal: function (folder_path) {
        Modal.icon_show("link_break", "Unshare the Folder \"" + FileOps.filename(folder_path).escapeHTML() + "\"", DomUtil.fromElm("unshare-confirm"));
        var form = $("unshare-form");
        form.action = form.action.replace("%25path%25",  folder_path);
    },
    submit_unshare: function (e) {

        var form = $("unshare-form");

        var path = Browse.current_path || PAGE_PATH;
        assert(path.length, "submit_unshare: No shared folder path.");

        Forms.ajax_submit(form, false,
            function () { // On Success
                if (Feed.ns_id || Sharing.REJOIN) {
                    window.location = "/share";
                } else {
                    Notify.ServerSuccess("You unshared this folder.");
                    Modal.hide();
                    Browse.reload(Browse.current_path, true);
                }
            },
            false,
            e.target);

        return false;
    },
    remove_div: function (me) {
        $(me).up('.bs-row').remove();
        return false;
    },

    del_user: function (who, button, path, options) {
        options = options || {};
        var person = options.self_remove ? "yourself" : who;
        var keep_files = options.keep_files ? "please" : "";
        var message = options.message || "remove " + person;

        if (options.skip_confirm || confirm("Are you sure you want to " + message + "?")) {
            var params = "who=" + encodeURIComponent(who) + "&keep_files=" + keep_files;
            var abdr = new Ajax.DBRequest("/share_ajax/del_user/" + PAGE_PATH + "?" + params, {
                onSuccess: function (req) {
                    if (options.kill_div) {
                        Sharing.remove_div(button);
                        Notify.ServerSuccess(who + " removed successfully.");
                    }
                }
            });
        }
        return false;
    },
    leave: function () {
        Modal.show("Leave Shared Folder?", DomUtil.fromElm('leave-confirm'));
    },
    unshare: function () {
        Modal.show("Unshare Folder?", DomUtil.fromElm('unshare-confirm'));
    },
    ignore: function () {
        Modal.icon_show("folder_delete", "Permanently Remove Shared Folder?", DomUtil.fromElm('ignore-confirm'));
    },
    rejoin: function (path) {
        Modal.icon_show(BrowseActions.getIcon("rejoin"), "Rejoin the Shared Folder '" + FileOps.filename(path) + "'?", DomUtil.fromElm('rejoin-confirm'));
    },
    cancel_user: function (who, button, path) {
        return Sharing.del_user(who, button, path, {message: "cancel " + who + "'s invite", kill_div: true});
    },
    kick_user: function (name, who, button, path) {
        DomUtil.fillVal(name, 'kick-confirm-victim');
        Modal.show("Kick " + name + " out of Folder?", DomUtil.fromElm('kick-confirm'), {button: button, victim: who});
    },
    reinvite_user: function (who, button, path) {
        var adbr = new Ajax.DBRequest("/share_ajax/reinvite_user/" + PAGE_PATH, {
            parameters: {
                action: 'reinvite_user',
                who:    who
            },
            onSuccess: function (req) {
                Notify.ServerSuccess(who + " was re-invited successfully");
            }
        });
        return false;
    },
    load_contacts: function () {
        if (Sharing.loading_contacts) {
            return false;
        }

        Sharing.loading_contacts = true;

        var adbr = new Ajax.DBRequest("/get_contacts", {
            onSuccess: function (req) {
                var contacts_dict = req.responseText.evalJSON(false);
                window.contacts = contacts_dict.contacts;
                window.lcontacts = contacts_dict.lcontacts;
                Sharing.loading_contacts = false;

                if (Sharing.auto_completer) {
                    Sharing.auto_completer.options.array = window.contacts;
                    Sharing.auto_completer.options.larray = window.lcontacts;
                }

            }
        });
    }
};

/*global TreeView, FileQueue, InlineUploadStatus, FlashDetect*/
var Upload = {
    SWFU: false,
    init: function (late_game) {
        var i = {};
        i[Constants.tcn] = Upload.touch;
        var f = Upload.initSWFU(i);
        if (!late_game) {
            Event.observe(window, 'load', f);
        } else {
            f(); // run it now!
        }

        Upload.operaHack();
        FileQueue.clear();

        if (!late_game) {
            Upload.checkForFallback.delay(Util.linux_ff3 ? 0 : 5); // if we haven't loaded the flash in 5 seconds, give up
        }
    },
    initSWFU: function (pp) {

        return function () {
            var swf_upload_control = new SWFUpload({
                // Backend settings
                upload_url: "http://" + Constants.BLOCK_CLUSTER + "/upload",
                file_post_name: "file",

                // Flash file settings
                file_size_limit : "307200", // 300MB
                file_types : "*",
                file_types_description : "All Files",
                file_upload_limit : "0", // Even though I only want one file I want the user to be able to try again if an upload fails

                // Button settings
                button_placeholder_id : "spanButtonPlaceholder",
                button_window_mode: SWFUpload.WINDOW_MODE.TRANSPARENT,
                button_width: 120,
                button_height: 30,
                button_image_url: Util.linux_ff3 ? "/static/images/upload_button.gif" : "",
                button_text:  Util.linux_ff3 ? "<span class='flash-button'>Select files...</span>" : "",
                button_text_style: ".flash-button {color: #ffffff; font-size: 11pt;" +
                                                   'font-family: "lucida grande","lucida sans unicode",tahoma,verdana,arial,sans-serif;' +
                                                   "text-align: center; line-height: 16px;}",

                // Event handler settings
                swfupload_loaded_handler : Upload.flashLoaded,

                file_dialog_start_handler : FileQueue.chooseFiles,
                file_queued_handler : Upload.fileQueued,
                file_queue_error_handler : Upload.fileQueueError,
                file_dialog_complete_handler : Upload.fileDialogComplete,

                //upload_start_handler : Upload.uploadStart,
                upload_progress_handler : Upload.uploadProgress,
                upload_error_handler : Upload.uploadError,
                upload_success_handler : Upload.uploadSuccess,
                upload_complete_handler : Upload.uploadComplete,

                // Flash Settings
                flash_url : "/static/swf/swfupload.swf",

                custom_settings : {
                    progress_target : "fsUploadProgress",
                    upload_successful : false
                },

                post_params: pp,
                debug: Constants.upload_debug || false
            });
            Upload.SWFU = swf_upload_control;
        };
    },
    reset: function () {
        if (FileQueue.uploading) {
            Upload.SWFU.cancelUpload();
        }
        FileQueue.clear();

        var uploader = $$(".swfuploader").first();
        if (uploader) {
            uploader.remove();
        }
        delete Upload.SWFU;
    },
    updatePostParams: function (dict) {
        var pp = Upload.SWFU.getSetting("post_params");
        for (var key in dict) {
            pp[key] = dict[key];
        }

        Upload.SWFU.setPostParams(pp);
    },
    fileBrowse: function () {
        Upload.SWFU.cancelUpload();
        Upload.SWFU.selectFiles();
    },
    fileQueueError: function (fileObj, error_code, message)  {
        try {
            // Handle this error separately because we don't want to create a FileProgress element for it.
            switch (error_code) {
            case SWFUpload.QUEUE_ERROR.FILE_EXCEEDS_SIZE_LIMIT:
                var msg = "<p>The upload limit online is 300MB. You can upload larger files with the <a href='/install'>Dropbox desktop application</a>.</p>";
                var cont = new Element("div");
                var p = new Element("p", {
                    'style': 'margin-bottom:0; text-align: right;'
                });
                var button = new Element("input", {
                    'type': 'button',
                    'className': 'button',
                    'value': 'Okay'
                });
                button.observe("click", function () {
                    FileOps.show_upload(Browse.current_path);
                });
                p.insert(button);
                cont.insert(msg);
                cont.insert(p);
                Modal.icon_show("alert", "Upload Error", cont);
                break;
            case SWFUpload.QUEUE_ERROR.ZERO_BYTE_FILE:
                alert("The file you selected is empty.  Please select another file.");
                break;
            case SWFUpload.QUEUE_ERROR.INVALID_FILETYPE:
                alert("The file you choose is not an allowed file type.");
                break;
            default:
                alert("An error occurred in the upload. Try again later.");
                this.debug("Error Code: " + error_code +
                           ", File name: " + fileObj.name +
                           ", File size: " + fileObj.size +
                           ", Message: " + message);
                break;
            }
        } catch (e) {}
    },
    fileQueued: function (fileObj) {
        FileQueue.push(fileObj);
    },

    fileDialogComplete: function (num_files_selected) {
    },

    uploadNext: function () {
        FileQueue.start_time = FileQueue.start_time || new Date().getTime();
        Upload.SWFU.startUpload();
    },

    pause: function () {
        Upload.SWFU.stopUpload();
    },

    uploadProgress: function (fileObj, bytesLoaded, bytesTotal) {
        FileQueue.update(fileObj, bytesLoaded, bytesTotal);
    },

    uploadSuccess: function (fileObj, server_data) {
        if (server_data.strip() === "") {
            FileQueue.errored(fileObj);
            Notify.ServerError();
        } else if (server_data == "quota") {
            FileQueue.errored(fileObj);
            Notify.ServerError("Your upload failed because you are over quota.");
        } else {
            FileQueue.update(fileObj, "done");
        }
    },

    uploadComplete: function (fileObj) {
        FileQueue.completed(fileObj);

        if (FileQueue.empty()) {
            FileQueue.doneUploading();
        } else {
            Upload.uploadNext();
        }
    },

    uploadError: function (fileObj, error_code, message) {
        var file = fileObj;
        FileQueue.queueSize -= fileObj.size;

        if (parseInt(error_code, 10) != -280) {
            var flashVersion = FlashDetect.major + "." + FlashDetect.revision;
            Util.report_exception("Uploader Error: " + error_code + " " + message + " " + Object.toJSON(fileObj) + " FLASH VERSION: " + flashVersion, window.location.href);
            Util.check_crossdomain_loadable();
        }

        switch (error_code) {
        case SWFUpload.UPLOAD_ERROR.MISSING_UPLOAD_URL:
            this.debug("Error Code: No backend file, File name: " + file.name + ", Message: " + message);
            break;
        case SWFUpload.UPLOAD_ERROR.UPLOAD_LIMIT_EXCEEDED:
            this.debug("Error Code: Upload Limit Exceeded, File name: " + file.name +
                       ", File size: " + file.size +
                       ", Message: " + message);
            break;
        case SWFUpload.UPLOAD_ERROR.HTTP_ERROR:
            this.debug("Error Code: HTTP Error, File name: " + file.name +
                       ", Message: " + message);
            break;
        case SWFUpload.UPLOAD_ERROR.UPLOAD_FAILED:
            this.debug("Error Code: Upload Failed, File name: " + file.name +
                       ", File size: " + file.size + ", Message: " + message);
            break;
        case SWFUpload.UPLOAD_ERROR.IO_ERROR:
            this.debug("Error Code: IO Error, File name: " + file.name +
                       ", Message: " + message);
            break;
        case SWFUpload.UPLOAD_ERROR.SECURITY_ERROR:
            this.debug("Error Code: Security Error, File name: " + file.name +
                       ", Message: " + message);
            break;
        case SWFUpload.UPLOAD_ERROR.FILE_CANCELLED:
            this.debug("Error Code: Upload Cancelled, File name: " + file.name +
                       ", Message: " + message);
            break;
        case SWFUpload.UPLOAD_ERROR.UPLOAD_STOPPED:
            this.debug("Error Code: Upload Stopped, File name: " + file.name +
                       ", Message: " + message);
            break;
        default:
            this.debug("Error Code: " + error_code +
                       ", File name: " + file.name +
                       ", File size: " + file.size +
                       ", Message: " + message);
            break;
        }

        FileQueue.errored(fileObj, error_code);

        if (FileQueue.empty() && FileQueue.uploading) {
            FileQueue.doneUploading();
        }


    },
    grabURL: function () {
        var path = $F('file-box');
        if (/(^http|^https|^ftp):\/\//.match(path)) {
            $('url').value = path;
        }
        return true;
    },
    set_dest: function (path) {
        var path_parts = path.split("/");
        var folder_name = path_parts[path_parts.length - 1];
        if (!folder_name.length) {
            folder_name = "My Dropbox";
            path = "/";
        }

        DomUtil.fillVal(folder_name.escapeHTML(), 'dest-folder-text');
        DomUtil.fillVal(path, 'dest-folder');

        $$(".dest-folder").each(
            function (elm) {
                elm.value = path;
            }
        );

        var basic_link = Util.scry('basic-uploader-url');
        if (basic_link) {
            basic_link.href = basic_link.href.replace(/(\/upload)(.*)(\?basic=1)/,
                function (s, u, f, b) {
                    return u + Util.urlquote(path) + b;
                });
        }
    },
    treeview_handler: function (path, obj) {
        Upload.set_dest(path);
        FileQueue.clear();
    },
    new_folder: function () {
        TreeView.hide();
        Modal.show("Create New Folder...", DomUtil.fromElm('create-folder'), {'action': Upload.do_new_folder});
        if (!Util.ie) {
            $('first-treeview-link').onclick();
        }
    },
    do_new_folder: function () {
        if (!Modal.vars.selected_path) {
            Notify.ServerError("Please select a parent folder.");
            return;
        }

        var to = $F('entered-name');
        var straight_from = decodeURIComponent(Modal.vars.selected_path);
        var from = Util.urlquote(straight_from);

        var adbr = new Ajax.DBRequest("/cmd/new" + from + "?to_path=" + to, {
            onSuccess: function (req) {
                Upload.treeview_handler(Util.normPath(straight_from) + "/" + to);
                TreeView.reset();
            },
            cleanUp: function () {
            }
        });
    },
    flashLoaded: function () {
        Upload.flash_loaded = true;
        $('upload-loading').hide();
        $('upload-buttons').show();
    },
    checkForFallback: function () {
        if (!Upload.flash_loaded) {
            location.replace("/upload?basic=1");
        } else {
            clearInterval(Upload.opera_tid);
        }
    },
    operaHack: function () {
        if (Prototype.Browser.Opera) {
            Upload.opera_tid = setInterval(function () {
                $('opera-dummy-div').toggle();
            }, 200);
        }
    }
};

var Sprite = {
    SPACER: "/static/images/icons/icon_spacer.gif",
    src: function (elm, name) {
        elm = $(elm);
        Sprite.clear(elm);
        elm.addClassName("s_" + name);
    },
    replace: function (elm, orig, replacement) {
		Util.removeClassName(elm, "s_" + orig);
        elm.className += " s_" + replacement;
    },
    clear: function (elm) {
        elm = $(elm);
        elm.className = elm.classNames().reject(function (x) {
            return !x.indexOf("s_");
        }).join(" ");
    },
    make: function (name, attr) {
        attr = attr || {};
        attr.src = Sprite.SPACER;

        var classNames = "sprite s_" + name + " " + attr['class'] || "";
        var img = new Element("img", attr);
        img.addClassName(classNames);
        return img;
    },
    html: function (name, attr) {
        var elm = Sprite.make(name, attr);
        var div = new Element("div");
        div.update(elm);
        return div.innerHTML;
    },
    get: function (elm) {
        return elm.className;
    },
    set: function (elm, val) {
        elm.className = val;
        elm.src = Sprite.SPACER;
    }
};

var TreeView = {
    disable_shares: false,
    tv: {},
    set_params: function (params) {
        TreeView.ajax_params = params;
    },
    init: function (h, autohide, tree_id) {
        tree_id = tree_id || 'treeview';
        TreeView.tv[tree_id] = {};
        var treeview = TreeView.tv[tree_id];

        treeview.autohide = autohide === null ? true : autohide;
        treeview.handler = h;
        treeview.viewdiv = $(tree_id);
        treeview.hidefunc = TreeView.hide.bindAsEventListener(this);
    },
    reset: function () {
        var dbr = new Ajax.DBRequest("/ajax_subtreeview", {
            parameters: TreeView.ajax_params,
            onSuccess: function (req) {
                for (var tree_id in TreeView.tv) {
                    TreeView.tv[tree_id].viewdiv.down(".treeview-folders").update(req.responseText);
                }
            }
        });
    },
    toggle: function (e, tree_id) {
        Event.stop(e);
        var treeview = TreeView.tv[tree_id || 'treeview'];

        if (treeview.shown) {
            treeview.shown = false;
            TreeView.hide(e, tree_id);
        } else {
            treeview.shown = true;
            TreeView.show(e.target, tree_id);
        }
        return false;
    },
    hide: function (e, tree_id) {
        var treeview = TreeView.tv[tree_id || 'treeview'];
        if (!e || !$(e.target).descendantOf(treeview.viewdiv)) {
            treeview.viewdiv.hide();
            Event.stopObserving(window, 'click', treeview.hidefunc);
            treeview.shown = false;
        }
    },
    show: function (link, tree_id) {
        var treeview = TreeView.tv[tree_id || 'treeview'];

        link = $(link);
        link.blur();
        var linkPos = link.cumulativeOffset();
        treeview.viewdiv.setStyle({'top': (linkPos.top + link.getHeight()) + "px", 'left': (linkPos.left - 4) + "px"});
        treeview.viewdiv.show();
        Event.observe(window, 'click', treeview.hidefunc);
    },
    toggleNode: function (button) {
        button = $(button);

        var img = button.down('img');
        if (img.className.match("bullet_plus")) {
            Sprite.replace(img, "bullet_plus", "bullet_minus");
        } else {
            Sprite.replace(img, "bullet_minus", "bullet_plus");
        }
        button.up().next('div').toggle();
        button.blur();
        return false;
    },
    toggleNodeAjax: function (button, parent_path) {
        if (button.fetched_children) {
            return TreeView.toggleNode(button);
        }
        button = $(button);
        var img = button.down('img');
        var orig_sprite = Sprite.get(img);
        img.src = "/static/images/icons/ajax-loading-small.gif";

        var dbr = new Ajax.DBRequest("/ajax_subtreeview" + parent_path, {
            parameters: TreeView.ajax_params,
            onSuccess: function (req) {
                var d =  new Element("div", {style: "display: none;"}).update(req.responseText);
                button.up().insert({after: d});
                button.fetched_children = true;

                Sprite.set(img, orig_sprite);
                return TreeView.toggleNode(button);
            },
            cleanUp: function (ok) {
                if (/loading/.match(img.src)) {
                    Sprite.set(img, orig_sprite);
                }
            }
        });

        return false;
    },
    handle: function (path, obj) {
        var tree_ids = $H(TreeView.tv).keys();
        var treeview = $(obj).ancestors().find(function (div) {
            return tree_ids.include(div.id);
        });
        if (!treeview) {
            return;
        }

        treeview = TreeView.tv[treeview.id];
        $("modal").fire("db:treeview_selected", {path: path});
        if (treeview.handler) {
            treeview.handler(path, obj);
        }
        if (treeview.autohide) {
            TreeView.hide(treeview.id);
        }
    },
    move: function (tree_id, location_id) {
        assert($(tree_id), "Couldn't find tree_id");
        assert($(location_id), "Couldn't find location_id");
        $(location_id).update($(tree_id));
        $(tree_id).show();
    },
    disable_shared: function (tree_id) {
        var tree = $(tree_id);

        if (tree.share_disabled) {
            return;
        }
        tree.share_disabled = true;

        var shared_folder_imgs = tree.select('.s_folder_user'); // abstraction violation. it's for performance...
        var l = shared_folder_imgs.length;
        for (var i = 0; i < l; i++) {
            var elm = shared_folder_imgs[i];
            Sprite.replace(elm, 'folder_user', 'folder_user_gray');

            var link = elm.up();
            link._onclick = link.onclick;
            link.onclick = Util.nop;
        }
    },
    enable_shared: function (tree_id) {
        var tree = $(tree_id);

        if (!tree.share_disabled) {
            return;
        }
        tree.share_disabled = false;

        var shared_folder_imgs = tree.select('.s_folder_user_gray'); // abstraction violation. it's for performance...
        var l = shared_folder_imgs.length;
        for (var i = 0; i < l; i++) {
            var elm = shared_folder_imgs[i];
            Sprite.replace(elm, 'folder_user_gray', 'folder_user');

            var link = elm.up();
            link.onclick = link._onclick;

        }
    }
};

var ProgressBar = {
    MAGIC: 42,
    make: function (id, width, start_text) {
        width = width || 300;
        var wstr = width.toString() + "px";
        start_text = typeof(start_text) != 'undefined' ? start_text :"0%";
        var outer = new Element('div', {'class': 'outer-progress-bar', 'style': 'width: ' + wstr});
        var inner = new Element('div', {'class': 'inner-progress-bar', 'id': "pb_" + id, 'style': 'width: ' + wstr});
        var under = new Element('div', {'class': 'under-pb progress-bar', 'style': 'width: ' + wstr});
        var over  = new Element('div', {'style': 'display: none', 'class': 'over-pb progress-bar', 'id': "pb_" + id + "_over"});
        var upct  = new Element('div', {'class': 'pb-percentage', 'id': "pb_" + id + "_upct", 'style': 'width: ' + wstr});
        upct.update(start_text);
        var opct  = new Element('div', {'class': 'pb-percentage', 'id': "pb_" + id + "_opct", 'style': 'width: ' + wstr});
        opct.update(start_text);

        under.insert(upct);
        over.insert(opct);
        inner.insert(under);
        inner.insert(over);
        outer.insert(inner);

        over.progress_width = width;
        return outer;
    },
    reset: function (id) {
        ProgressBar.set(id, 0);
    },
    set: function (id, frac, text) {
        frac = Math.min(frac, 1);
        text = typeof(text) != 'undefined' && text !== false ? text : Math.floor(frac * 100).toString() + "%";

        var over = $("pb_" + id + "_over");
        if (!over) {
            return;
        }

        var wideness = over.progress_width * frac;
        over.show();
        over.makeClipping().setStyle({'width': wideness.toString() + 'px', backgroundColor: '#348DD3'});

        $("pb_" + id + "_upct").innerHTML = text;
        $("pb_" + id + "_opct").innerHTML = text;
    },
    get_frac: function (id) {
        var over = $("pb_" + id + "_over");
        return parseInt(over.style.width, 10) / over.progress_width;
    },
    errorState: function (id, errorText) {
        errorText = errorText || "Error";
        var over = $("pb_" + id + "_over");
        if (!over) {
            return;
        }

        var wideness = over.progress_width;
        over.show();
        over.makeClipping().setStyle({'width': wideness.toString() + 'px', backgroundColor: '#d23a3a'});
        $("pb_" + id + "_upct").innerHTML = errorText;
        $("pb_" + id + "_opct").innerHTML = errorText;
    }
};

var ModalProgress = {
    show: function (text, cover_this) {
        if (!text) {
            return;
        }
        cover_this = $(cover_this || 'browse-box');

        var loadingDiv = $('modal-progress-overlay');
        loadingDiv.clonePosition(cover_this);

        if (!loadingDiv.getWidth()) {
            return;
        }

        $('modal-progress-text').update(text);
        $('modal-progress-bar').setOpacity(1);
        $('modal-progress-bar').update(ProgressBar.make("modal-progress", 150, ""));

        // keep the modal progress bar >= 120px from the top of the screen (and visible)
        $('modal-progress-content').style.top = (Math.max(0, Util.scrollTop() - cover_this.cumulativeOffset().top) + 120) + "px";

        Effect.Appear(loadingDiv, {to: 0.7, duration: 0.25 });
        Effect.Appear('modal-progress-content', {duration: 0.25 });
    },
    update: function (progress) {
        if (progress.indexOf("/") > 0) {
            var parts = progress.split("/");
            progress = Number(parts[0]) / Number(parts[1]);
        }
        if (progress) {
            ProgressBar.set("modal-progress", progress, "");
        }
    },
    hide: function () {
        Effect.Fade('modal-progress-overlay', { duration: 0.25 });
        Effect.Fade('modal-progress-content', { duration: 0.25 });
    }
};

var InlineUploadStatus = {
    upload_box: false,
    last_update: 0,
    last_update_position: 0,
    previous_bps_list: [],

    show: function (container_id) {
        if (!InlineUploadStatus.upload_box) {
            InlineUploadStatus.build(container_id);
        }

        InlineUploadStatus.upload_box.show();
    },
    build: function () {
        if (!InlineUploadStatus.upload_box) {
            InlineUploadStatus.upload_box = $("inline-upload-status");
        }
        $("right-content").insert({top: InlineUploadStatus.upload_box});
    },
    hide: function () {
        if (InlineUploadStatus.upload_box) {
            InlineUploadStatus.upload_box.hide();
        }
    },

    update: function () {
        if ($$("#right-content #inline-upload-status").length === 0) {
            InlineUploadStatus.build();
        }

        if (FileQueue.currentFilename) {
            var global = $("inline-upload-status");
            global.removeClassName("error");
            global.removeClassName("complete");

            var total = FileQueue.numShown();
            var current_num = total - FileQueue.toUpload + 1;
            global.down(".upload-info-filename").update("Uploading file " + current_num +  " of " + total + " (" + FileQueue.formattedSpeed + ")");

            global.down(".upload-info-status").update("<strong>Time Left:</strong> " + FileQueue.formattedTime);

            var percent = (parseInt(100 * FileQueue.totalPercentage, 10) || 0) + "%";
            global.down(".upload-info-percent").update(percent);
            global.down(".upload-info-icon").update(Sprite.make("sync"));

            global.down(".upload-file-progress").style.width = percent;
        }
    },
    errored: function (errors, total) {
        var global = $("inline-upload-status");
        global.addClassName("error");
        global.removeClassName("complete");

        global.down(".upload-info-filename").update("Problems with " + errors + " of " + total + " files");
        global.down(".upload-info-percent").update("100%");
        var total_time = (Util.time() - FileQueue.start_time) / 1000;
        global.down(".upload-info-status").update();
        global.down(".upload-info-icon").update(Sprite.make("redx"));
        global.down(".upload-file-progress").style.width = "100%";
    },
    complete: function () {
        var global = $("inline-upload-status");
        global.addClassName("complete");

        global.down(".upload-info-filename").update("Uploaded " + FileQueue.numShown() + " of " + FileQueue.numShown() + " files");
        global.down(".upload-info-percent").update("100%");
        var total_time = (Util.time() - FileQueue.start_time) / 1000;
        global.down(".upload-info-status").update("<strong>Time taken:</strong> " + Util.formatTime(total_time));
        global.down(".upload-info-icon").update(Sprite.make("check"));
        global.down(".upload-file-progress").style.width = "100%";

    }
};
/*global Browse, GlobalUpload, UploadFile*/
var FileQueue = {
    fileRows: {},
    fileProgress: {},
    uploading: false,
    toUpload: 0,
    queueSize: 0,
    completedSize: 0,
    completed_files: {},
    empty: function () {
        return !FileQueue.toUpload;
    },
    numShown: function () {
        return $H(FileQueue.fileRows).keys().length;
    },
    lastOne: function () {
        return 1 == FileQueue.toUpload;
    },
    push: function (fileObj) {
        var name = fileObj.name;
        var id = fileObj.id;

        FileQueue.queueSize += fileObj.size;
        FileQueue.fileRows[id] = fileObj;
        if (!FileQueue.toUpload) {
            GlobalUpload.files_added();
        }

        FileQueue.toUpload++;

        UploadFile.add(fileObj);

        var button_content = $('choose-button').down(".hotbutton-content");
        button_content.innerHTML = button_content.innerHTML.replace("Choose", "Add more");
    },
    remove: function (fileObj) {
        var id = fileObj.id;

        Upload.SWFU.cancelUpload(id);

        fileObj.filestatus = SWFUpload.FILE_STATUS.CANCELLED;

        UploadFile.update(fileObj);

        delete FileQueue.fileRows[id];

        if (!FileQueue.completed_files[id]) {
            FileQueue.toUpload = Math.max(0, FileQueue.toUpload - 1);
        }

        if (FileQueue.numShown() === 0 && FileQueue.uploading) {
            FileQueue.doneUploading();
        }
    },
    update: function (fileObj, progress, max) {
        var text = false;

        var updateSize;
        if (progress == "done") {
            text = "Done";
            progress = max = 1;
        } else if (progress / max == 1) {
            text = "Saving...";
        }

        if (text == "Done") {
            FileQueue.completedSize += fileObj.size;
            updateSize = FileQueue.last_update_position;
        } else {
            updateSize = FileQueue.completedSize + progress / max * fileObj.size;
        }

        // Calculate the speed and time remaning
        var average_bps = fileObj.averageSpeed / 8;
        var formattedSpeed = Util.formatBytes(average_bps, 1, true) + "/sec";

        var time_left = (FileQueue.queueSize - updateSize) / average_bps;
        var formattedTime = Util.formatTime(time_left + FileQueue.toUpload); // Add a second for each file to account for "Saving..."

        FileQueue.last_update_position = updateSize;

        if (!text) {
            text = parseInt((updateSize / FileQueue.queueSize).toFixed(2) * 100, 10);
            text += "%";
        }

        FileQueue.currentFilename = fileObj.name.snippet(20);
        FileQueue.formattedSpeed = formattedSpeed;
        FileQueue.formattedTime = formattedTime;
        FileQueue.statusText = text;
        FileQueue.totalPercentage = updateSize / FileQueue.queueSize;
        FileQueue.current_file = fileObj;

        if (fileObj.filestatus == SWFUpload.FILE_STATUS.COMPLETE) {
            UploadFile.update(fileObj);
        }

        if (!FileQueue.update_timer) {
            FileQueue.update_timer = true;

            FileQueue.timer = setInterval(function () {
                UploadFile.update();
                GlobalUpload.update();
            }, 250);
        }

    },
    errored: function (fileObj, error_code) {
        if (error_code != SWFUpload.UPLOAD_ERROR.FILE_CANCELLED) {
            FileQueue.errors = (FileQueue.errors + 1) || 1;
        }
        UploadFile.update(fileObj);

        if (FileQueue.toUpload === 0 && FileQueue.uploading) {
            FileQueue.doneUploading();
        }
    },
    completed: function (fileObj) {
        var row = FileQueue.fileRows[fileObj.id];
        if (row) {
            FileQueue.toUpload = Math.max(0, FileQueue.toUpload - 1);
            FileQueue.completed_files[fileObj.id] = true;
        }
    },
    clear: function (clear_all) {
        FileQueue.fileRows = {};
        FileQueue.queueSize = 0;
        FileQueue.errors = 0;
        FileQueue.toUpload = 0;
        FileQueue.completedSize = 0;
        FileQueue.uploading = false;
        window.onbeforeunload = null;
        Modal.onHide = null;
    },
    chooseFiles: function () {

    },
    uploadFiles: function () {
        if (!FileQueue.toUpload) {
            return;
        }

        Upload.updatePostParams({'dest': $$('.dest-folder')[0].getValue(), 't': Constants.TOKEN});

        if (!FileQueue.uploading) {
            Upload.uploadNext();
            FileQueue.uploading = true;
            FileQueue.updateInterval = setInterval(InlineUploadStatus.update, 250);
            window.onbeforeunload = function confirmLeave() {
                return "Leaving this page will cancel your uploads.";
            }; // Prototype sucks at onbeforeunload
            Modal.hide();
        }
    },
    colorButtons: function (primary) {
        $A(['choose-button', 'upload-button']).each(function (button) {
            if (button == primary) {
                $(button).removeClassName('grayed');
            } else {
                $(button).addClassName('grayed');
            }
        });
    },
    doneUploading: function () {
        GlobalUpload.complete();
        InlineUploadStatus.complete();

        clearInterval(FileQueue.timer);
        FileQueue.update_timer = false;
        FileQueue.uploading = false;

        clearInterval(FileQueue.updateInterval);
        DomUtil.fillVal('', "uploading-speed");
        DomUtil.fillVal('', "uploading-time-left");
        if (FileQueue.errors) {
            InlineUploadStatus.errored(FileQueue.errors, FileQueue.numShown());
        }

        FileQueue.last_update_position = 0;
        Browse.reload();

        Modal.onHide = null;
        window.onbeforeunload = null;
    }
};

var GlobalUpload = {
    init: function () {
        $("init-global-upload").show();
        $("global-upload-progress").hide();
    },
    files_added: function () {
        $("upload-start-buttons").show();
        $("upload-running-buttons").hide();
        $("upload-finished-buttons").hide();
        $$("#upload-start-buttons .button")[0].removeClassName("grayed");
    },
    update: function () {
        $("upload-start-buttons").hide();
        $("upload-running-buttons").show();
        $("upload-finished-buttons").hide();
        $("init-global-upload").hide();

        var global = $("global-upload-progress");
        global.show();
        global.removeClassName("complete");

        var file_info = global.select("td")[1];
        var total = FileQueue.numShown();
        var current_num = total - FileQueue.toUpload + 1;

        file_info.update("Uploading file " + current_num +  " of " + total + " (" + FileQueue.formattedSpeed + ")");

        var percent_complete = global.select("td")[2];

        var percent = (parseInt(100 * FileQueue.totalPercentage, 10) || 0) + "%";

        global.select("td")[2].update(percent);
        global.down(".upload-file-progress").style.width = (100 * FileQueue.totalPercentage || 0).toFixed(2) + "%";

        var time_elm = $("upload-time");
        time_elm.update("<strong>Time left:</strong> " + FileQueue.formattedTime);
    },
    complete: function () {
        $("upload-start-buttons").hide();
        $("upload-running-buttons").hide();
        $("upload-finished-buttons").show();

        var global = $("global-upload-progress");
        // global.addClassName("complete");

        global.down().style.width = "100%";
        global.select("td")[2].update("100%");

        var icon_elm = global.select("td")[0];
        icon_elm.update(Sprite.make("check"));

        var total_time = (Util.time() - FileQueue.start_time) / 1000;
        $("upload-time").update("<strong>Time taken:</strong> " + Util.formatTime(total_time));

        var file_info = global.select("td")[1];
        file_info.update("Uploaded " + FileQueue.numShown() + " of " + FileQueue.numShown() + " files");
    }
};

/*global Tooltip*/
var UploadFile = {
    add: function (fileObj) {
        var container = $("upload-files-container");
        // Render
        var fileContainer = new Element("div", { 'id': fileObj.id });
        fileContainer.addClassName("upload-file");
        var table = '<table class="upload-file-info"><tr><td class="upload-info-icon"></td><td class="upload-info-filename"></td><td class="upload-info-status"></td><td class="upload-info-action"></td></tr></table>';
        fileContainer.innerHTML = '<div class="upload-file-progress"></div>' + table;

        container.insert(fileContainer);
        UploadFile.update(fileObj);
    },
    remove: function (fileId) {
        $(fileId).remove();
    },
    update: function (fileObj) {
        fileObj = fileObj || FileQueue.current_file;
        var file_elm = $(fileObj.id);
        assert(file_elm,  "Could not find file_elm for " + fileObj.name);

        // Update icon
        var icon = FileOps.filename_to_icon(fileObj.name);
        file_elm.down(".upload-info-icon").update(Sprite.make(icon, {}));

        // Update filename
        file_elm.down(".upload-info-filename").update(fileObj.name.escapeHTML().truncate(40));

        // update status
        var className = "upload-file";

        var remove_a   = new Element("a", { 'href': '#' });
        var remove_img = Sprite.make("thick_x", {});

        remove_a.update(remove_img);
        remove_a.observe("click", function (e) {
            FileQueue.remove(fileObj);
        });

        var width = 0;
        var status      = file_elm.down(".upload-info-status"),
            action      = file_elm.down(".upload-info-action"),
            progress    = file_elm.down(".upload-file-progress");

        assert(status, "Couldn't find status elm");
        assert(action, "Couldn't find action elm");
        assert(progress, "Couldn't find progress elm");

        status.update();
        var percent_done = parseInt(fileObj.percentUploaded, 10) + "%";
        switch (fileObj.filestatus) {
        case SWFUpload.FILE_STATUS.QUEUED:
            file_elm.className = className + " queued";
            action.update(remove_a);
            break;

        case SWFUpload.FILE_STATUS.IN_PROGRESS:
            file_elm.className = className + " in_progress";
            if (percent_done == "100%") {
                status.update("Saving");
            } else {
                status.update(percent_done);
            }
            width = percent_done;
            $$(".uploadnotch").invoke("remove");

            var img = Sprite.make("arrow_blue", {});
            img.addClassName("uploadnotch");
            file_elm.insert(img);
            break;

        case SWFUpload.FILE_STATUS.ERROR:
            file_elm.className = className + " error";
            status.update("Error");
            width = "100%";
            var error_img = Sprite.make("information");

            error_img.observe("mouseover", function () {
                if (parseFloat(Util.flash_version(), 10) < 10.32) {
                    Tooltip.show(error_img, "Upload failed.  Please try upgrading to the latest version of <a href='http://get.adobe.com/flashplayer/' target='_blank'>Adobe Flash</a> and try again.");
                } else {
                    Tooltip.show(error_img, "Sorry, it looks like the advanced uploader has a problem with your system. Please use the <a href='#' onclick='FileOps.show_basic_upload(Browse.current_location); return false;'>basic uploader</a> to upload via the website");
                }
            });
            action.update(error_img);
            break;

        case SWFUpload.FILE_STATUS.COMPLETE:
            file_elm.className = className + " complete";
            status.update("100%");
            width = "100%";
            action.update();
            $$(".uploadnotch").invoke("remove");
            break;

        case SWFUpload.FILE_STATUS.CANCELLED:
            file_elm.className = className + " cancelled";
            width = "100%";
            status.update("Cancelled");
            action.update();
            break;
        }

        if (width === 0) {
            progress.style.visibility = "hidden";
        } else {
            progress.style.visibility = "";
        }
        progress.style.width = width;
    }
};

var Platform = {
    Windows: navigator.userAgent.indexOf("Win") > -1,
    XP: navigator.userAgent.indexOf("Windows NT 5") > -1,
    Vista: navigator.userAgent.indexOf("Windows NT 6") > -1,
    Mac: navigator.appVersion.indexOf("Mac") > -1
};

var Revisions = {
    confirm_purge: function (name) {
        DomUtil.fillVal(name.escapeHTML(), "purge-victim");
        Modal.show("Purge File?", DomUtil.fromElm('purge-confirm'));
        return false;
    },
    purge: function () {
        $('purge-form').submit();
    }
};

var DomUtil = {
    fromElm: function (elm) {
        return $(elm).innerHTML;
        //return $(elm).childElements().invoke('cloneNode', true); //better, but skips text nodes
    },
    fillVal: function (val, className) {
        $$("." + className).each(
            function (x) {
                x = $(x);
                if (x.tagName == 'INPUT') {
                    x.value = val;
                    x.defaultValue = val;
                } else {
                    x.innerHTML = val;
                }
            });
    }
};

var FileSearch = {
    last_search: "",
    PER_PAGE: 10,
    MAX_RETURNED: 100,
    searched: {},
    results: [],
    result_hash: {},

    search: function (search_string, offset) {
        $("filesearchpaging").update();

        search_string = search_string.strip();
        FileSearch.last_search = search_string;

        if (search_string.length < 3 || search_string == "Search your Dropbox files") {
            $("filesearchresults").update();
            return;
        }

        var results = FileSearch.search_local(search_string);

        if (FileSearch.should_search_server(search_string)) {
            FileSearch.search_server.defer(search_string);

        } else if (results.length === 0) {
            FileSearch.render_empty();
        }

        if (results && results.length) {
            FileSearch.render(results, offset);
        }
        return false;
    },

    invalidate_cache: function () {
        FileSearch.searched = {};
        FileSearch.results = [];
        FileSearch.result_hash = {};
    },

    search_local: function (search_string) {
        var terms = search_string.split(/\s+/);
        var results = [];

        for (var i = 0; i < FileSearch.results.length; i += 1) {
            var fname = FileSearch.results[i].filename.toLowerCase();
            var all_match = true;

            for (var t = 0, l = terms.length; t < l; t += 1) {
                all_match &= fname.indexOf(terms[t].toLowerCase()) > -1;
            }
            if (all_match) {
                results.push(FileSearch.results[i]);
            }
        }
        return results;
    },

    search_server: function (search_string) {
        assert(FileSearch.searched[search_string] === undefined, "We already searched for " + search_string);

        FileSearch.searching = true;

        var dbr = new Ajax.DBRequest("/search", {
            parameters: {'search_string': search_string},
            onSuccess: function (req) {
                FileSearch.searching = false;
                var results = req.responseText.evalJSON();

                FileSearch.searched[search_string] = results.length;

                if (results && results.length > 0) {
                    FileSearch.process_results(results);

                }
                FileSearch.search(FileSearch.last_search);
            }
        });
    },

    process_results: function (results) {
        var new_results = 0;

        for (var i = 0; i < results.length; i += 1) {
            var r = results[i];

            var hash = r.href;

            if (!FileSearch.result_hash[hash]) {
                FileSearch.results.push(r);
                FileSearch.result_hash[hash] = true;
                new_results += 1;
            }
        }

    },

    render: function (results, offset) {
        results.sort(function (a, b) {
            if (a.filename.length > b.filename.length) {
                return 1;
            } else if (a.filename.length == b.filename.length) {
                return 0;
            } else {
                return -1;
            }
        });

        offset = offset || 0;
        var short_results = results.slice(offset, offset + FileSearch.PER_PAGE);
        var ul = new Element("ul");
        for (var i = 0; i < short_results.length; i += 1) {
            ul.insert(FileSearch.render_item(short_results[i]));
        }

        var as_link = '<br/><span style="font-weight:normal;">(<a href="' + '/advanced_search?submit=y&include_files=y&include_folders=y&all_terms=' + Util.urlquote(FileSearch.last_search)  + '">advanced search</a>)</span>';

        var sr = new Element("h6").update(results.length + (results.length >= FileSearch.MAX_RETURNED ? "+" : "") + " " + Util.plural(results.length, "result", 1) + " for '" + FileSearch.last_search.escapeHTML().truncate(30) + "'" + as_link);
        $("filesearchresults").update(sr);

        var has_more = offset + FileSearch.PER_PAGE < results.length;
        var has_less = offset > 0;
        var fsp = $("filesearchpaging");

        if (has_more || has_less) {
            if (has_less) {
                var prev = new Element("a", {'href': "#"});
                prev.update("&laquo; prev");
                prev.observe("click", function (e) {
                    FileSearch.search(FileSearch.last_search, offset - FileSearch.PER_PAGE);
                    Event.stop(e);
                });
                fsp.insert(prev);
            }

            if (has_more && has_less) {
                fsp.insert(" | ");
            }

            if (has_more) {
                var next = new Element("a", {'href': "#"});
                next.update("next &raquo;");
                next.observe("click", function (e) {
                    FileSearch.search(FileSearch.last_search, offset + FileSearch.PER_PAGE);
                    Event.stop(e);
                });
                fsp.insert(next);
            }
        }

        $("filesearchresults").insert(ul);

    },

    render_item: function (item) {
        var li = new Element("li");
        var a = new Element("a");

        if (item.icon.startsWith("folder")) {
            a.href = "#" + Util.urlquote(item.fq_path);
            a.observe("click", function () {
                Browse.reload(Util.urlquote(item.fq_path));
            });
        } else {
            a.href = item.href;
        }

        var img;
        a.title = item.fq_path + " - " + Util.formatBytes(item.size, 2, true);

        img = Sprite.make(item.icon);
        img.addClassName("link-img");

        a.insert(img);

        var emstr = new Emstring(item.filename.escapeHTML());
        var chop_loc = emstr.findSpot(13);
        a.insert(item.filename.escapeHTML());
        li.update(a);

        return li;
    },

    render_empty: function () {
        if (!FileSearch.searching) {
            $("filesearchresults").update(new Element("h6", {'style': 'margin:0 0 2px 0;padding:0;'}).update("No results found for '" + FileSearch.last_search.escapeHTML().truncate(30) + "'"));
        }
    },

    should_search_server: function (search_string) {
        if (FileSearch.searched[search_string] === undefined) {
            return true;
        } else {
            return false;
        }
    },

    warmup: function () {
        if (!FileSearch.warm) {
            var dbr = new Ajax.DBRequest("/search/warmup");
            FileSearch.warm = true;
        }
    }
};

/*global Browse, ActAsBlock, BrowseKeys*/
var Modal = {
    show: function (title, content, vars, focus, width, keep_content) {
        $$("#modal-content .error-message, #modal-content .error-removable").invoke("hide");

        if (FileQueue.uploading && !keep_content) {
            alert("You can't do this while uploading.");
            return false;
        }

        assert(content, "Missing modal content!");

        Modal.vars = vars || {};
        var icon = Modal.vars.icon || 'help';
        width = width || 500;

        var modal_top = (document.viewport.getScrollOffsets().top + 150);
        Util.scry('modal').setStyle({top: modal_top + "px"});
        Util.scry('modal').setStyle({width: width + "px", margin: "0 0 0 " + Math.floor(-width / 2).toString() + "px"});
        Util.scry('modal-title').update(title);

        if (!keep_content) {
            if (FileQueue.numShown()) {
                Upload.reset();
            }
            var first_kid = Util.childElement($('modal-content'), 0);
            if (first_kid && first_kid != content) {
                $('grave-yard').insert(first_kid);
            }

            var content_div = new Element('div');
            content_div.update(content);
            Util.scry('modal-content').insert(content_div);

            if (content.show) {
                content.show();
            }

            Element.show('modal');
        }
        Util.scry('modal-overlay').setOpacity(0.6);
        Util.scry('modal-overlay').show();
        Util.scry('modal-behind').setStyle({height: (Util.scry('modal').getHeight() + 20) + "px",
                                            width: (Util.scry('modal').getWidth() + 20) + "px",
                                            margin: "0 0 0 " + Math.floor(-width / 2 - 10).toString() + "px",
                                            top: (modal_top - 10) + "px"});
        Util.scry('modal-behind').setOpacity(0.2);
        Util.scry('modal-behind').show();

        if (focus) {
            // We have
            $('modal-content').select("#" + focus.id).first().focus();
        } else {
            if (!Util.ie) {
                var firstButton = Util.scry('modal').down('input[type=button]') || Util.scry('modal').down('input[type=submit]');
                if (firstButton) {
                    firstButton.focus();
                }
            }
        }

        if (!Modal.track_id) {
            Modal.track_resizes();
        }

        ActAsBlock.register(false, "modal");
        document.observe("keydown", Modal.keydown);
        return false;
    },
    keydown: function (e) {
        var key = BrowseKeys.getKey(e);

        if (key == 27) {
            Modal.hide();
        }
    },
    icon_show: function (icon, title, content, vars, focus, width, keep_content) {
        var fancy_title = new Element("div");
        fancy_title.insert(Sprite.make(icon, {'class': 'modal-h-img'}));
        fancy_title.insert(title);

        return Modal.show(fancy_title, content, vars, focus, width, keep_content);
    },
    shown: function () {
        return Util.scry('modal').visible();
    },
    hide: function (e) {
        if (e) {
            Event.stop(e);
        }

        if (Modal.confirm_close) {
            if (!confirm("Leaving this box may cut your operation short.\n\nAre you sure you want to?")) {
                return;
            }
            Modal.confirm_close = false;
        }

        if (Modal.onHide) {
            var result = Modal.onHide();
            if (!result) {
                return;
            }
        }
        Modal.onHide = null;

        Element.hide('modal-behind');
        Element.hide('modal-overlay');

        if (!FileQueue.numShown()) {
            Element.hide('modal');
        } else {
            $("modal").style.marginLeft = "-10000000px";
            if (FileQueue.uploading) {
                InlineUploadStatus.show();
            }
        }

        if (Modal.track_id) {
            clearInterval(Modal.track_id);
            Modal.track_id = false;
        }

        document.stopObserving("keydown", Modal.keydown);
    },
    track_resizes: function () {
        Modal.track_id = setInterval(Modal.resize_bg, 150);
    },
    resize_bg: function () {
        var h = Util.scry('modal').getHeight();
        if (Modal.old_height != h || Util.scry('modal-behind').getHeight() < h) {
            Modal.old_height = h;
            Util.scry('modal-behind').setStyle({height: (h + 20) + "px"});
        }
    },
    vars: {}
};

var Tabs = {
    init: function ()
    {
        var tabs_li = $A(document.getElementsByClassName("tab")).concat($A(document.getElementsByClassName("subtab")));
        for (var i = 0;i < tabs_li.length; i++)
        {
            var a = tabs_li[i].down("a");
            var url = a.href.split("/");
            if (tabs_li[i].hasClassName("subtab")) {
                a.href = "#" + url[url.length - 1];
            }

            if (Util.ie6 || Prototype.Browser.Opera) {
                var width = a.getWidth() - parseInt(a.getStyle("padding-left"), 10) * 2;
                width = (width + 2 + (width % 2)); // Hacks, IE6 - width must be even for absolute positioning to work properly
                a.style.width = width + "px";
            }

            var tl = Sprite.make("rounded_tl", { 'class': 'rounded_tl'});
            var tr = Sprite.make("rounded_tr", { 'class': 'rounded_tr'});
            a.appendChild(tl);
            a.appendChild(tr);
        }

        i = 20;
        $$(".events_bubble").each(function (elm) {
            var neg_half_width = (-1 * elm.getWidth() / 2) + "px";
            elm.style.marginLeft = neg_half_width;
            elm.style.marginRight = neg_half_width;
            elm.style.right = "6px";
            elm.parentNode.style.zIndex = i--; // For IE7+ - it sucks at zIndex's - element takes the zIndex of the parentNode
        });
    },

    check_url: function (defaulttab)
    {
        var current = Util.url_hash();
        if (!current || Tabs.last_shown == current) {
            return;
        }

        Tabs.last_shown = current;
        if (Util.url_hash()) {
            Tabs.showTab(Util.url_hash() + '-tab', Util.url_hash());
        } else {
            Tabs.showTab(defaulttab + '-tab', defaulttab);
        }
    },

    showTab: function (elm, tab)
    {
        //  Get element and remove the highlight from the link
        elm = $(elm);
        if (elm) {
            elm.fire("db:tabshown");
        }
        // Set all the tabs to not selected
        var tab_list = document.getElementsByClassName("subtab");
        var i;
        for (i = 0; i < tab_list.length; i++) {
            tab_list[i].removeClassName("selected");
        }

        // Set all the content to be hidden
        var content_tabs = document.getElementsByClassName("content-tab");

        for (i = 0; i < content_tabs.length; i++) {
            content_tabs[i].hide();
        }

        // Show the selected content
        var sTab = $(tab + "-tab") || $$(".subtab").first();
        var sContent = $(tab + "-content") || $$(".content-tab").first();
        if (sTab) {
            sTab.addClassName("selected");
        }
        if (sContent) {
            sContent.show();
            Util.syncHeight();
            var inputs = sContent.select('input[type=text]', 'textarea');
            if (inputs) {
                Util.focus(inputs[0]); // focus on the first input area on the tab
            }
        }
        return false;
    }
};

var Hosts = {
    edit: function (id) {
        var elm = $("host" + id);
        if (elm.editing) {
            return;
        }

        elm.editing = true;
        var content = elm.innerHTML.unescapeHTML();
        elm.previous = content;

        elm.innerHTML = "<input type='text' class='skinny-input' size='20' maxlength='256' style=\"word-wrap: break-word;\" value=\"" + content.escapeHTML().gsub('"', '&quot;') + "\">&nbsp;" +
            "<input type='button' onclick='Hosts.doneEditing(\"" + id + "\");' class='button' value='Save'>&nbsp;" +
            "<input type='button' onclick='Hosts.cancelEditing(\"" + id + "\");' class='button grayed' value='Cancel'>";
        var i = elm.down('input');
        Event.observe(i, 'keydown', Hosts.checkKey(id));
        i.select();
        return false;
    },
    doneEditing: function (id) {
        var elm = $("host" + id);
        var newName = elm.down('input').value;

        var dbr = new Ajax.DBRequest("/computer_edit?host_id=" + id + "&name=" + Util.urlquote(newName), {
            onSuccess: function (req) {
                Hosts.unedit(elm, req.responseText);
            }
        });
    },
    cancelEditing: function (id) {
        var elm = $("host" + id);
        Hosts.unedit(elm, elm.previous);
    },
    unedit: function (elm, value) {
        elm.editing = false;
        elm.innerHTML = value.escapeHTML();
    },
    unlink: function (id, name, plat) {
        DomUtil.fillVal(name.escapeHTML(), 'unlink-confirm-name');
        Modal.icon_show("computer_delete", "Unlink Computer?", DomUtil.fromElm('unlink-confirm'), {host_id: id, plat: plat});
    },
    doUnlink: function (id, plat) {
        var dbr = new Ajax.DBRequest("/computer_edit?host_id=" + id + "&unlink=yessir", {
            onSuccess: function (req) {
                Hosts.killRow(id);
                Hosts.dec_count(plat);
            }
        });
    },
    dec_count: function (plat) {
        var count_div = $(plat + "-count");
        if (!count_div) {
            return;
        }

        var parts = count_div.innerHTML.split(" ");
        var count = parseInt(parts.shift(), 10);
        var desc = parts.join(" ");

        if (!count) {
            return;
        }

        count--;
        if (count == 1 && desc.charAt(desc.length - 1) == 's') {
            desc = desc.substr(0, desc.length - 1);
        } else if (count != 1 && desc.charAt(desc.length - 1) != 's') {
            desc = desc + "s";
        }

        count_div.innerHTML = count.toString() + " " + desc;
    },
    killRow: function (id) {
        var table = $("host" + id).up("table");
        $("host" + id).up("tr").remove();
        if (Hosts.rowCount() === 0) {
            var row = new Element("tr");
            var td = new Element("td", {colspan: 4});
            td.innerHTML = "<center>You no longer have any hosts linked.</center>";
            table.insert(row);
            row.insert(td);
        }
    },
    rowCount: function () {
        return $$(".host-row").length;
    },
    checkKey: function (id) {
        return function (e) {
            e = e || window.event;

            if (e.keyCode == Event.KEY_RETURN) {
                Hosts.doneEditing(id);
            }
            if (e.keyCode == Event.KEY_ESC) {
                Hosts.cancelEditing(id);
            }
        };
    }
};


Autocompleter.Contacts = Class.create(Autocompleter.Base, {
    initialize: function (element, update, array, larray, options) {
        this.baseInitialize(element, update, options);
        this.options.array = array;
        this.options.larray = larray;
        this.options.frequency = 0.2;
    },

    getUpdatedChoices: function () {
        if (!window.contacts) {
            Sharing.load_contacts();
            return;
        }

        if (!this.options.array && window.contacts && window.lcontacts) {
            this.options.array = window.contacts;
            this.options.larray = window.lcontacts;
        }

        this.updateChoices(this.options.selector(this));
    },

    setOptions: function (options) {
        this.options = Object.extend({
            choices: 5,
            selector: function (instance) {
                var ret       = []; // Beginning matches
                var entry     = instance.getToken().toLowerCase();

                var l = instance.options.array.length;
                var c = instance.options.choices;
                var a = instance.options.array;
                var la = instance.options.larray;

                for (var i = 0; i < l && ret.length < c; i++) {
                    var regex_parts = [];
                    if (entry.indexOf(" ") == -1) {
                        regex_parts.push("\\s+");
                    }
                    if (entry.indexOf("+") == -1) {
                        regex_parts.push("\\+");
                    }
                    if (entry.indexOf("@") == -1) {
                        regex_parts.push("@");
                    }
                    if (entry.indexOf(".") == -1) {
                        regex_parts.push("\\.");
                    }
                    if (entry.indexOf("&lt;") == -1) {
                        regex_parts.push("&lt;");
                    }
                    var regex = RegExp("(" + regex_parts.join("|") + ")");
                    var elem = a[i];
                    var lelm = la[i];
                    var parts = regex_parts.length ? lelm.split(regex) : [lelm];

                    var foundPos = 0;
                    var pl = parts.length;
                    for (var p = 0; p < pl; p++) {
                        if (!parts[p]) {
                            continue;
                        }
                        if (parts[p].indexOf(entry) === 0) {
                            ret.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
                            elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
                            foundPos + entry.length) + "</li>");
                            break;
                        }
                        foundPos += parts[p].length;
                    }
                }
                return "<ul>" + ret.join('') + "</ul>";
            }
        }, options || { });
    },
    selectEntry: function () {
        var entry = this.getCurrentEntry();
        var curText = entry.innerHTML;
        // find the email address in between the brackets if they're in there
        var delims = [["&gt;", "&lt;"], [">", "&lt;"]]; // the latter is for safari on the mac. don't ask why
        for (var i = 0; i < delims.length; i++) {
            var gtlt = delims[i];
            var gtPos = curText.lastIndexOf(gtlt[0]);
            if (gtPos == curText.length - gtlt[0].length) {
                var ltPos = curText.lastIndexOf(gtlt[1]);
                if (ltPos != -1) {
                    entry.innerHTML = curText.substr(ltPos + gtlt[1].length, gtPos - ltPos - gtlt[1].length);
                }
            }
        }

        // if we have a lot of tokens, choose one to use as the separator and auto-add it
        var tok = this.options.tokens.length > 1 ? this.options.tokens[0] + " ": "";
        entry.innerHTML += tok;

        this.active = false;
        this.updateElement(entry);
        $(this.element).fire("db:autocompleted");
    }
});

var LeftNavBox = {
    close: function (ex) {
        var t = $(ex).up().up('div');
        var d = 1.0;
        var ef;
        ef = new Effect.BlindUp(t, {duration: d});
        return false;
    }
};

var Invitations = {
    submit: function (e) {
        e = e || window.event;

        if (e.keyCode == Event.KEY_RETURN) {
            Invitations.send();
        }
    },
    send: function () {
        var input = $('invite-recip');
        Invitations.do_send($F(input), input);
    },
    do_send: function (recip, clear_this) {
        var email_count = recip.strip().split(/[;,\s]+/).length;

        if (!email_count || recip === "") {
            Notify.ServerError("Please enter an e-mail address.");
            return;
        }
        if (recip != $('invite-recip').title) {
            var req = new Ajax.DBRequest("/send_invite", {
                parameters: {'emails': recip },
                onSuccess: function (req) {
                    Notify.ServerSuccess(req.responseText.substr(5));
                    if (clear_this) {
                        clear_this.setValue('');
                    }
                },
                onFailure: function (req) {
                    if (req.responseText.startsWith("err:")) {
                        Notify.ServerError(req.responseText.substr(4));
                    } else {
                        Notify.ServerError();
                    }
                },
                noAutonotify: true
            });
        } else {
            Notify.ServerError("Please enter an email address.");
        }
        return false;
    },
    clearInput : function () {
        if ($('invite-recip').value == "E-mail address") {
            $('invite-recip').value = "";
            $('invite-recip').setStyle({"color" : ""});
        }
    },
    addCustomMessage: function (event) {
        Event.stop(event);
        var source_a = event.target.tagName == "A" ? $(event.target) : $(event.target).up("a");

        source_a.addHTML = source_a.innerHTML;
        source_a.update(Sprite.make("email_delete", {'class': 'link-img'}));
        source_a.appendChild(document.createTextNode("Remove custom message"));

        source_a.stopObserving('click');
        source_a.observe('click', Invitations.hideCustomMessage);
        var textarea = new Element('textarea', {'title': 'Enter a custom message here', 'name': 'custom_message', 'class': 'custom-message suggestion-input act_as_block textinput', 'rows': 3, 'cols': 25, 'style': 'margin-top: 0.75em;'});
        textarea.setValue(Invitations.custom_message || textarea.title);
        SuggestionInput.register(textarea);
        source_a.up().previous("div").insert({bottom: textarea});
        SuggestionInput.register(textarea);
        ActAsBlock.resize(textarea);
        return false;
    },
    hideCustomMessage: function (event) {
        Event.stop(event);
        var source_a = event.target.tagName == "A" ? $(event.target) : $(event.target).up("a");

        source_a.stopObserving('click');
        source_a.observe('click', Invitations.addCustomMessage);
        source_a.update(source_a.addHTML);

        var input = source_a.up().up().select(".custom-message")[0];
        Invitations.custom_message = $F(input);
        input.parentNode.removeChild(input);
        return false;
    }
};

/*global EventDatePicker, HotButton*/
var Feed = {
    firstTime: true,
    COMMENT_DEFAULT: "Add a comment to the Recent Events feed",
    addComment: function () {
        if (!$F('comment').length || $('comment').value == Feed.COMMENT_DEFAULT) {
            return false;
        }

        var dbr = new Ajax.DBRequest("/share_ajax/add_comment", {
            parameters: {'comment': $F('comment'), 'ns_id': Feed.ns_id},
            onSuccess: function (req) {
                Feed.addCommentRow(req.responseText);
                $('comment').value = "";
            }
        });
        return false;
    },
    addCommentRow: function (blurb) {
        var tr = new Element("tr");
        var td1 = new Element("td", {'valign': 'top', 'class': 'note'});
        var icon = new Element("img", {'src': '/static/images/icons/comment.gif'});
        td1.insert(icon);
        var td2 = new Element("td", {'valign': 'top', 'class': 'note'});
        td2.innerHTML = blurb;
        var td3 = new Element("td", {'valign': 'top', 'class': 'note', 'width': '100', 'nowrap': 'nowrap', 'align': 'right'});
        td3.innerHTML = "(just added)";
        tr.update(td1);
        tr.insert(td2);
        tr.insert(td3);

        $('event-table').down('tr').insert({before: tr});
    },

    feedPages: {},
    page_num: 0,
    ns_id: "false",
    page_size: 10,
    showLoading: function (no_text, cover_this, just_icon) {
        just_icon = true;
        var loadingDiv = $('feed-loading');
        cover_this = $(cover_this);

        if (!loadingDiv) {
            loadingDiv = new Element("div", {id: 'feed-loading'});
            var div_html = '<table style="height: 100%; width: 100%; background:#fff;"><tr><td valign="top"><div id="feed-loading-text" style="padding-top: 16px;text-align:center;"></div></td></tr></table>';
            loadingDiv.update(div_html);
            document.body.appendChild(loadingDiv);
        }

        loadingDiv.clonePosition(cover_this);
        if (loadingDiv.getWidth() === 0) {
            return;
        }

        if (Util.ie) {
            loadingDiv.style.left = cover_this.getBoundingClientRect().left + "px";
        }

        loadingDiv.setOpacity(0.9);
        if (no_text) {
            $('feed-loading-text').update();
        } else {
            $('feed-loading-text').update("<img src='/static/images/icons/ajax-loading.gif' style='vertical-align: bottom;'/>" + (just_icon ? "" : "Loading..."));
        }

        $('feed-loading').show();
    },
    hideLoading: function () {
        $('feed-loading').hide();
    },

    changeNamespace: function (ns_id, source) {
        Feed.ns_id = ns_id;
        Feed.getPage(0, Feed.page_size);
    },

    changeDate: function (date, no_reload) {
        Feed.date = date;
        Feed.nice_date = Util.niceDate(date);
        if (!no_reload) {
            Feed.getPage(0, Feed.page_size);
        }
    },

    getPage: function (num, page_size) {
        Feed.page_size = page_size;
        Feed.page_num = num;

        if (Feed.feedPages[Feed.get_key(num)]) {
            Feed.show(num, page_size);
        } else {
            Feed.showLoading(false, $('events-content'));
            var ns_str = Feed.ns_id != "false" ? "&ns_id=" + Feed.ns_id.toString() : "&is_home=yes";
            var page_str = page_size ? "&feed_items=" + page_size.toString() : "";

            var dbr = new Ajax.DBRequest("/next_events?cur_page=" + (num).toString() + ns_str + page_str, {
                parameters: {'date': Feed.nice_date ? Feed.nice_date : "" },
                onSuccess: function (req) {
                    Feed.feedPages[Feed.get_key(num)] = req.responseText;
                    Feed.show(num, page_size);
                }
            });
        }
        return false;
    },

    show: function (num, page_size) {
        Feed.hideLoading();
        $("events-content").update(Feed.feedPages[Feed.get_key(num)]);
        var addcomment = $("add-comment-button");
        if (addcomment) {
            HotButton.register(addcomment);
        }
    },

    tabClick: function (ns_id) {
        var event_table = $("event-table");
        if (!event_table) {
            Feed.getPage(0);
    		Feed.clearNewEvents();
        }
    },
    clearNewEvents: function (ns_id) {
        $$(".events_bubble").invoke("hide");
    },

    url_check: function (ns_id, page_size, page_num, nice_date) {
        ns_id = ns_id || "false";
        if (Feed.ns_id != ns_id) {
            $$("#filter-list .selected").invoke("removeClassName", "selected");
            $("sf" + ns_id).addClassName("selected");
        }

        var changed = (Feed.ns_id != ns_id || Feed.page_size != page_size || Feed.page_num != page_num || Feed.nice_date != nice_date);
        Feed.ns_id = ns_id || Feed.ns_id;
        Feed.page_size = page_size || Feed.page_size;
        Feed.page_num = page_num || Feed.page_num;

        if (nice_date) {
            Feed.changeDate(new Date(nice_date), true);
        }

        if (changed) {
            if (nice_date) {
                EventDatePicker.change_date(Feed.date);
            }
            Feed.getPage(Feed.page_num, Feed.page_size);
        }
    },
    set_url: function (options) {
        var ns_id       = options.ns_id !== undefined ? options.ns_id : Feed.ns_id,
            page_size   = options.page_size || Feed.page_size,
            page_num    = options.page_num !== undefined ? options.page_num : Feed.page_num,
            date        = options.date !== undefined ? options.date : Feed.nice_date;

        var hash = ["events", ns_id, page_size, page_num, date].join(":");

        window.location.href = "#" + hash;
    },

    get_key: function (num) {
        return Feed.page_size + "_" + Feed.ns_id + "_" + Feed.date + "_" + num + "_" + Feed.nice_date;
    },
    show_rss_modal: function (url, ns_id) {
        assert(ns_id, "RSS Feed modal with no ns_id");
        Modal.icon_show('feed', 'Subscribe to this RSS Feed', $('rss-modal'));
        $("rss_url").setValue(url);
        BrowseActions.addCopyUrlFlash(url);
        $("copy_success").update();
        $("reset-rss-link").href = "/reset_rss/" + ns_id;
    }
};

Effect.BlindFadeUp = function (elm, opts) {
    var ef;
    ef = new Effect.BlindUp(elm, opts);
    ef = new Effect.Fade(elm, opts);
};

Effect.BlindFadeDown = function (elm, opts) {
    var ef;
    ef = new Effect.BlindDown(elm, opts);
    ef = new Effect.Appear(elm, opts);
};

Effect.Flash = function (elm, opts, cycle) {
    assert(opts.startcolor && opts.endcolor, "Start and end colors must be specified");
    assert(opts.cycles, "Fade cycles must be specified");

    cycle = cycle || 0;
    var ef = new Effect.Highlight(elm, {duration: 1, startcolor: opts.startcolor, endcolor: opts.endcolor, restorecolor: opts.endcolor, afterFinish: function () {
        var ef2 = new Effect.Highlight(elm, {duration: 1, startcolor: opts.endcolor, endcolor: opts.startcolor, restorecolor: opts.startcolor, afterFinish: function () {
            if (cycle < opts.cycles) {
                Effect.Flash(elm, opts, cycle + 1);
            }
        }});
    }});
};

var Base = {
    mouseOut: false,
    sf_hidden: false,
    showSharedFolders: function (link, e) {
        Event.stop(e);

        var folderList = $('shared-folder-dropdown');

        Event.observe(folderList, 'mouseover', Base.mouseOverList);
        Event.observe(folderList, 'mouseout', Base.mouseOutList);
        Event.observe(e.target, 'mouseout', Base.mouseOutList);
        Event.observe(e.target, 'mouseover', Base.mouseOverList);
        Event.observe(document, 'click', Base.hideSharedFolders);

        Base.sf_hidden = false;

        // wait a sec before we show it
        setTimeout(
            function () {
                if (Base.sf_hidden) {
                    return;
                }

                var shadow = $('share-menu-shadow');
                var numFolders = folderList.select('li').length;
                folderList.clonePosition($(link).down("img"), {offsetTop: 8, offsetLeft: 1, setWidth: false, setHeight: false});
                folderList.style.height = (numFolders > 9) ? "200px" : "";
                folderList.show();

                shadow.clonePosition($(link).down("img"), {offsetTop: 9, offsetLeft: 2, setWidth: false, setHeight: false});
                shadow.clonePosition(folderList, {setTop: false, setLeft: false});
                shadow.setOpacity(0.2);
                shadow.show();
            }, 350);

        return false;
    },
    hideSharedFolders: function (e) {
        var folderList = $('shared-folder-dropdown');
        $('shared-folder-dropdown').hide();
        $('share-menu-shadow').hide();

        Event.stopObserving(window, 'click', Base.hideSharedFolders);
        Event.stopObserving(folderList, 'mouseover', Base.mouseOverList);
        Event.stopObserving(folderList, 'mouseout', Base.mouseOutList);
        clearTimeout(Base.mouseOut);
        Base.mouseOut = false;
        Base.sf_hidden = true;
    },
    mouseOverList: function () {
        clearTimeout(Base.mouseOut);
        Base.mouseOut = false;
    },
    mouseOutList: function (e, bigger_num) {
        clearTimeout(Base.mouseOut);
        Base.mouseOut = setTimeout(Base.hideSharedFolders, bigger_num || 200);
    }
};

var Notify = {
    ServerError: function (msg) {
        var is_important = false;
        if (msg && msg.indexOf('important') === 0) {
            is_important = true;
            msg = msg.substr(10);
        }
        return Notify.showDiv(msg, 'server-error', "There was a problem completing this request.", is_important, "#ffdddd", "#fff0f0");
    },
    ServerSuccess: function (msg) {
        return Notify.showDiv(msg, 'server-success', "Your request completed successfully.", false, "#e5fdd0", "#f7fff0");
    },
    showDiv: function (msg, div, default_msg, is_important, startcolor, endcolor) {
        Notify.last_msg = msg;
        Notify.clearAll();

        msg = msg || default_msg;
        var box = Util.scry(div);
        if (!box) {
            return;
        }
        box.down('span').update(msg);

        Util.center(box);

        var ef;
        ef = new Effect.BlindFadeDown(box, {duration: 0.5, scaleTo: 75, queue: {scope: 'notify'}});
        ef = new Effect.Flash($(div).down("td"),  { cycles: 3, startcolor: startcolor, endcolor: endcolor });
        ef = new Effect.Flash($(div).down(".r0"), { cycles: 3, startcolor: startcolor, endcolor: endcolor });

        if (!is_important) {
            ef = new Effect.BlindFadeUp(box, {duration: 0.3, scaleFrom: 75, delay: 10, queue: {scope: 'notify'}});
        }

        if (Util.ie6) {
            box.scrollTo();
        }
    },
    clearAll: function () {
        $$('.notify').invoke('hide');

        // and clear out any outstanding effects
        var q = Effect.Queues.get('notify');
        q.effects = [];
        clearInterval(q.interval);
        q.interval = null;
    },
    clearIf: function (msg) {
        if (Notify.last_msg == msg) {
            Notify.clearAll();
        }
    }
};

/*global Bubble*/
var Tooltip = {
    attach: function (elm, content, trigger) {
        // content = content.widthSplit(30).invoke('escapeHTML').join("&#8203;<wbr>");
        elm = $(elm);
        trigger = trigger ? $(trigger) : null;

        var id = elm.id ? elm.id : Math.floor(Math.random() * 10000 + 1);
        var d = Bubble.make(content, elm.tail_position);
        d.setStyle({display: "none", position: "absolute" });
        $('floaters').insert(d);

        if (elm.match("#modal-content *")) {
            d.style.zIndex = "13001";
        } else {
            d.style.zIndex = "";
        }
        if (elm.tail_position == "right") {
            var offset = Util.ie ? 32 : 12;
            d.style.marginLeft = -(d.getWidth() + elm.getWidth() + offset) + "px";
        }

        elm.tooltip = d;

        elm.out_target = trigger ? true : false;
        elm.observe('mouseout', Tooltip.mouseout('target', elm));
        elm.observe('mouseover', Tooltip.mouseover('target', elm));

        elm.out_trigger = trigger ? false : true;
        if (trigger) {
            trigger.observe('mouseout', Tooltip.mouseout('trigger', elm));
            trigger.observe('mouseover', Tooltip.mouseover('trigger', elm));
        }

        elm.out_tooltip = true;
        d.observe('mouseout', Tooltip.mouseout('tooltip', elm));
        d.observe('mouseover', Tooltip.mouseover('tooltip', elm));
    },
    update: function (elm, content) {
        if (elm.tooltip) {
            $(elm.tooltip).update(content);
        }
    },
    mouseover: function (thing, elm) {
        return function () {
            elm['out_' + thing] = false;
        };
    },
    mouseout: function (thing, elm) {
        return function () {
            elm['out_' + thing] = true;

            Tooltip.hide_if_out.defer(elm);
        };
    },
    show_by: function (e) {
        var d = $(e.tooltip);
        d.show();
        var offsetHeight = Math.floor(d.getHeight() / 2);
        d.clonePosition(e, {setWidth: false, setHeight: false, offsetTop: Math.floor(e.getHeight() / 2) - offsetHeight, offsetLeft: e.getWidth() + 1});
    },
    hide_if_out: function (e) {
        if (!e.out_target || !e.out_trigger || !e.out_tooltip) {
            return;
        }

        var d = $(e.tooltip);
        d.hide();
    },
    show: function (elm, content, trigger, tail_position) {
        tail_position = tail_position || "left";

        elm = $(elm);
        if (!elm.tail_position) {
            elm.tail_position = tail_position;
        }
        trigger = trigger ? $(trigger) : null;

        if (!elm.tooltip) {
            Tooltip.attach(elm, content, trigger);
        }

        Tooltip.show_by(elm);
    }
};

/*global SortSet*/
var BrowseURL = {
    PATH: 0,
    SORT: 1,
    DEL:  2,
    get_sort: function () {
        return Util.url_hash().split(":")[BrowseURL.SORT];
    },
    get_path: function () {
        return Util.url_hash().split(":")[BrowseURL.PATH];
    },
    get_del: function () {
        return Util.url_hash().split(":")[BrowseURL.DEL];
    },
    make_url: function (field, new_val) {
        var p = Util.url_hash().split(":");
        p[field] = new_val;
        return "#" + p.join(":");
    },
    get_sort_url: function (id, dir) {
        return BrowseURL.make_url(BrowseURL.SORT, id + "," + (dir?1:0));
    },
    get_path_url: function (path) {
        if (path.charAt(path.length - 1) != '/') {
            path += "/";
        }

        if (Browse.root) {
            var parts = path.split("/");
            path = "/" + parts.slice(Browse.root_depth + 1).join("/");
        }

        var url = BrowseURL.make_url(BrowseURL.PATH, path);

        if (path.indexOf(Browse.home) == -1) {
            url = "/home" + url;
        }

        return url;
    },
    set_path_url: function (fq_path) {
        var url = BrowseURL.make_url(BrowseURL.PATH, fq_path);
        location.href = url;
    },
    get_del_url: function (del) {
        return BrowseURL.make_url(BrowseURL.DEL, del ? 1 : 0);
    },
    check_url: function () {
        var hash = Util.url_hash();
        if (!hash.length && BrowseURL.last_hash && BrowseURL.last_hash.length) {
            return;
        }
        if (BrowseURL.last_hash != hash) {
            var parts = hash.split(":");
            var old = (BrowseURL.last_hash || '').split(":");
            BrowseURL.last_hash = hash;

            if (old.length > BrowseURL.SORT - 1 && old[BrowseURL.SORT] != parts[BrowseURL.SORT]) {
                SortSet.url_sort();
            }

            if ((old[BrowseURL.PATH] != parts[BrowseURL.PATH]) || (old[BrowseURL.DEL] != parts[BrowseURL.DEL])) {
                var deleted_changed = old[BrowseURL.DEL] != parts[BrowseURL.DEL];
                Browse.deleted_shown = parts[BrowseURL.DEL] == '1'? 1 : 0;
                Browse.reload((Browse.root || "") + parts[BrowseURL.PATH], deleted_changed); // force=deleted_changed
            }
        }
    }
};

/*global BrowseKeys*/
var SortSet = {
    sort: function (elm, cmp) {
        BrowseKeys.clear_highlight();
        elm.blur();
        var direction = SortSet.pick(elm);
        Browse.sort(cmp, !direction); // reverse=!direction
        return true;
    },
    pick: function (elm) {
        var dir = Util.toggle(elm.id);

        var options = $$('.sort_option');
        options.each(
            function (x) {
                if (x != elm) {
                    Sprite.src(x.down('img'), 'downtick-spacer');
                    x.onmouseout = null;
                    x.onmouseover = null;
                    Util.reset_toggle(x.id);
                    x.href = BrowseURL.get_sort_url(x.id, false);
                } else {
                    var which = dir ? "up" : "down";
                    Sprite.src(x.down('img'), 'sort-' + which + 'tick-on');
                    x.onmouseout = function () {
                        Sprite.src(x.down('img'), 'sort-' + which + 'tick-off');
                    };
                    x.onmouseover = function () {
                        Sprite.src(x.down('img'), 'sort-' + which + 'tick-on');
                    };
                    x.href = BrowseURL.get_sort_url(x.id, dir);
                }
            });
        return dir;
    },
    make_url: function (id, dir) {
        return id + "," + (dir?1:0);
    },
    last_url: '',
    url_sort: function () {
        var piece = BrowseURL.get_sort();

        if (piece && piece.length) {
            var parts = piece.split(",");
            var elm = document.getElementById(parts[0]);
            if (!elm) {
                return false;
            }
            Util.set_next_toggle(elm.id, parts[1] == '1' ? true : false);
            elm.onclick();

            return true;
        }

        return false;
    }
};

var Sort = {
    FILES_BY_NAME: function (x, y) {
        // sort directories first
        var dir_diff = y.dir - x.dir;
        if (dir_diff) {
            return dir_diff;
        }

        // sort alphabetical
        var xname = x.filename.toLowerCase();
        var yname = y.filename.toLowerCase();
        return xname == yname ? 0 : (xname.pad_nums() < yname.pad_nums() ? -1 : 1);
    },
    FILES_BY_SIZE: function (x, y) {
        // sort directories first
        var dir_diff = y.dir - x.dir;
        if (dir_diff) {
            return -1 * dir_diff;
        }

        if (x.dir) { // sort directories by name, not size
            return Sort.FILES_BY_NAME(x, y);
        }

        // sort bigger first
        return y.bytes - x.bytes;
    },
    FILES_BY_MODIFIED: function (x, y) {
        var xts = x.ts;
        var yts = y.ts;

        // sort latest first
        return xts == yts ? 0 : (xts < yts ? 1 : -1);
    }
};

/*global DBCheckbox*/
var Browse = {
    listen: function () {
        $("browse-files").observe("mouseover", Browse.over);
        $("browse-files").observe("mouseout", Browse.out);
        $("browse-files").observe("mousedown", Browse.down);
        $("browse-files").observe("mouseup", Browse.up);
        $("browse-files").observe("click", Browse.click);
        $("browse-files").oncontextmenu = Browse.onContext;
    },
    over: function (e) {
        var file_div = Util.resolve_target(e.target, ".browse-file-box-details");
        if (file_div && file_div.file) {
            if (!file_div.title) {
                file_div.title = file_div.file.filename;
            }
            file_div.file.over(e);
        }
    },
    out: function (e) {
        var file_div = Util.resolve_target(e.target, ".browse-file-box-details");
        if (file_div && file_div.file) {
            file_div.file.out(e);
        }
    },
    down: function (e) {
        if (Browse.down_file) {
            return;
        }

        var file_div = Util.resolve_target(e.target, ".browse-file-box-details");
        if (file_div && file_div.file) {
            Browse.down_file = file_div.file;
            file_div.file.down(e);
        }
    },
    up: function (e) {
        var file_div = Util.resolve_target(e.target, ".browse-file-box-details");
        if (file_div && file_div.file) {
            Browse.down_file = null;
            file_div.file.up(e);
        }
    },
    click: function (e) {
        var target = e.target;
        var file_div = Util.resolve_target(target, ".browse-file-box-details");

        if (file_div && file_div.file) {
            var file = file_div.file;
            var is_a = Util.resolve_target(target, "a");
            if (is_a) {
                if (file.dir) {
                    Event.stop(e);
                    Browse.reload(file.where);
                    window.location.href = BrowseURL.get_path_url(file.where);
                }
            } else if (file.filename && e.target.className.indexOf("checkbox") == -1) {
                file.click_select(e);
            }
        }
    },
    msg: false,
    files: [],
    selected_files: [],
    checked_files: [],
    drop_targets: [],
    selection: [],
    drag_watch: null,
    drag_end_watch: null,
    dragging: false,
    drag_startPos: null,
    drag_box: {},
    details: true,
    first_load: true,
    last_sort: [Sort.FILES_BY_NAME, false],
    highlight_index: -1,

    emptyCheck: function () {
        if (!Browse.files.length) {
            Browse.show_message("Folder contains deleted files.");
        }
    },
    show_message: function (msg) {
        if (typeof(msg) != typeof('string')) {
            var message = $('browse-files').down(".browse-message");
            if (message) {
                message.show();
            }

            return;
        }

        Browse.msg = msg;

        var d = Element("div", {'class': 'browse-message'});
        d.update(msg);
        $('browse-files').insert(d);
    },
    hide_message: function () {
        $$(".browse-message").invoke("hide");
    },
    setRoot: function (root) {
        if (root.charAt(root.length - 1) == '/') {
            root = root.substr(0, root.length - 1);
        }

        Browse.home = root;
        Browse.root = "";
        Browse.root_depth = 1;
    },
    sort: function (cmp, reverse, force) {
        if (!force && cmp == Browse.last_sort[0] &&
            reverse == Browse.last_sort[1]) {
            return;
        }

        Browse.last_sort = [cmp, reverse];

        if (reverse) {
            var orig_cmp = cmp;
            cmp = function (x, y) {
                return -1 * orig_cmp(x, y);
            };
        }

        Browse.files.sort(cmp);
        Browse.drop_targets.sort(cmp);
        Browse.refill();
        Browse.refresh_positions();

        return false;
    },
    resort: function () {
        BrowseKeys.clear_highlight();
        var l = Browse.last_sort;
        Browse.sort(l[0], l[1], true); // force=true
        Browse.refresh_drop_positions();
    },
    refill: function () {
        var container = $('browse-files');

        for (var i = 0; i < Browse.files.length; i += 1) {
            var x = Browse.files[i];
            container.insert(x.div);
            if (x.selected) {
                x.select();
            }
        }
    },
    add_file: function (file, need_new) {
        if (file.filename) {
            Browse.files.push(file);
        } else {
            Browse.has_parent_link = true;
            Browse.parent_link = file;
        }

        if (!Browse.file_div) {
            Browse.file_div = $('browse-files');
        }
        file.render(need_new, file.href);
        // file.setOpacity();
        file.cachePos.bind(file).defer();
        if (file.drop_target) {
            Browse.drop_targets.push(file);
        }
        if (file.bytes < 0) {
            Browse.deleted_shown = true;
        }
    },
    refresh_drop_positions: function () {
        Browse.drop_targets.invoke("cachePos");
    },
    refresh_positions: function () {
        Browse.files.invoke("cachePos");
        if (Browse.has_parent_link) {
            Browse.parent_link.cachePos();
        }
        Browse.redraw_checks();
    },
    redraw_checks: function () {
        /* TODO - Make this less stupid */
        var files = Browse.checked_files.slice();
        for (var i = 0; i < files.length; i += 1) {
            files[i].decheck();
            files[i].check();
        }
    },
    remove_selected: function () {
        Browse.files.each(function (x) {
            if (x.selected) {
                x.div.remove();
            }
        });
        Browse.files = Browse.files.findAll(function (x) {
            return !x.selected;
        });
        Browse.refresh_positions();
    },
    find_icons: function () {
        if (!Browse.files.length) {
            return;
        }

        Browse.updateOffset();

        var test = Browse.files[0];
        if (test.box) {
            var old_box_top = test.box.top;
            test.cachePos();
        }

        if (!test.box || old_box_top != test.box.top) {
            Browse.refresh_positions();
        }
    },
    cache_selection: function () {
        Browse.selection = Browse.files.pluck('selected');
    },
    get_selected: function () {
        return Browse.selected_files;
    },
    check_all: function () {
        BrowseActions.kill_dropdowns();

		var files  = Browse.files;
		var	length = files.length;

        Browse.deselect_all();
        if (Browse.checked_files.length == length) {
            Browse.decheck_all();
        } else {
            for (var i = 0; i < length; i += 1) {
                if (!files[i].checked) {
                    files[i].check(i);
                }
            }
            if (Browse.checked_files.length == length) { // If editing all files might not get checked
                DBCheckbox.select(Browse.global_checkbox());
            }
        }
    },
    check_range: function (start, end, decheck) {
        var inc = start > end ? -1 : 1;

        for (var i = start; i != end + inc; i += inc) {
            var file = Browse.files[i];
            if (decheck && file.checked) {
                file.decheck();
            } else if (!decheck && !file.checked) {
                file.check();
            }
        }
    },
    uncheck_range: function (start, end) {
        Browse.check_range(start, end, true);
    },
    check_these: function (file_list) {
        for (var i = 0; i < file_list.length; i += 1) {
            file_list[i].check();
        }
    },
    hide_checked: function () {
        Browse.hidden_checked_files = Browse.checked_files.slice();
        Browse.decheck_all();
    },
    restore_checked: function () {
        Browse.check_these(Browse.hidden_checked_files);
        Browse.hidden_checked_files = false;
    },
    decheck_all: function () {
		var checked_files = Browse.checked_files;
        var i = checked_files.length;

		while (i--) {
			checked_files[i].decheck(i, true);
        }

		Browse.checked_files = [];
        Browse.deselect_all();
    },
    deselect_all: function () {
        BrowseActions.clear();
        var length = Browse.selected_files.length;
        for (var i = 0; i < length; i += 1) {
            Browse.selected_files[0].deselect();
        }
    },
    deselect_all_but: function (one) {
        Browse.deselect_all();
        one.select();
    },
    clicked_scrollbar: function (e) {
        var b = $('browse-files');
        var p = b.viewportOffset();
        var left = p.left + b.clientWidth;
        var right = p.left + b.offsetWidth;

        return left < e.clientX && e.clientX < right;
        // something about b.componentFromPoint(e.clientX, e.clientY) for IE
    },
    drag_start: function (e) {
        return; // no drag select yet
        /*
        Event.stop(e);
        Browse.find_icons();

        if (Browse.clicked_scrollbar(e)) {
            return;
        }

        if (e.ctrlKey)
            Browse.cache_selection();

        Browse.drag_watch = Browse.drag.bindAsEventListener(this);
        Browse.drag_end_watch = Browse.drag_end.bindAsEventListener(this);
        Event.observe(document, "mousemove", Browse.drag_watch);
        Event.observe(document, "mouseup", Browse.drag_end_watch);

        Browse.drag_startPos = {top: e.clientY, left: e.clientX};
        Util.initBox(e.clientY, e.clientX, Browse.drag_box);
        Browse.dragging = true;

        $('browse-selection').setOpacity(0.5);
        */
    },
    drag: function (e) {
        if (!Browse.dragging) {
            return;
        }
        Event.stop(e);

        var box = Browse.drag_box;

        Util.calcBox(e.clientY, e.clientX, Browse.drag_startPos.top, Browse.drag_startPos.left, box);
        Browse.draw_box(box);
        box.ctrl = e.ctrlKey;
        box.shift = e.shiftKey;

        if (box.width + box.height > 10) {
            Browse.select_under.defer();
        }
    },
    select_under: function () {
        var box = Browse.drag_box;
        var shiftKey = box.shift;
        var ctrlKey = box.ctrl;

        if (!Browse.dragging) {
            return;
        }
        for (var i = 0; i < Browse.files.length; i++) {
            var f = Browse.files[i];
            if (f.overlaps(box)) {
                if (ctrlKey) {
                    f.toggle(Browse.selection[i]);
                } else {
                    f.select();
                }
            } else {
                if (ctrlKey) {
                    f.set(Browse.selection[i]);
                } else if (!shiftKey) {
                    f.deselect();
                }
            }
        }
    },
    select_range_to: function (elm, additive) {
        var other = Browse.shift_start;
        if (!other) {
            Browse.shift_start = elm;
            elm.select();
        } else {
            var selecting = false;
            for (var i = 0; i < Browse.files.length; i++) {
                var f = Browse.files[i];

                if (!additive) {
                    f.set(selecting);
                } else if (selecting) {
                    f.select();
                }

                if (f == elm || f == other) {
                    f.select();
                    selecting = !selecting;
                }
            }
        }
    },
    drag_end: function (e) {
        if (e) {
            var box = Browse.drag_box;
            if (box.width + box.height < 10) {
                Browse.deselect_all();
            }
        }

        Browse.dragging = false;
        Event.stopObserving(document, "mousemove", Browse.drag_watch);
        Event.stopObserving(document, "mouseup", Browse.drag_end_watch);
        Browse.drag_watch = null;
        $('browse-selection').hide();
    },
    draw_box: function (box) {
        var s = $('browse-selection').style;
        s.top = box.top + "px";
        s.left = box.left + "px";
        s.width = box.width + "px";
        s.height = box.height + "px";
        s.display = '';
    },
    clone_selected: function (e) {
        Browse.sel_clones = $A();
        Browse.sel_clone_origin = {y: e.clientY, x: e.clientX, st: Util.scrollTop(), sl: Util.scrollLeft()};
        Util.scry('ghost-icons').update('');
        for (var i = 0; i < Browse.selected_files.length; i++) {
            var f = Browse.selected_files[i];

            var div = f.div.cloneNode(true); // deep=true
            div.file = null; // don't need this reference ever
            div.hide();
            div.absolutize();
            f.cachePos();
            var p = f.box;
            var top = p.top + Util.scrollTop();
            var left = p.left + Util.scrollLeft();
            div.style.top = top + "px";
            div.style.left = left + "px";
            div.style.width = p.width + "px";
            div.style.height = p.height + "px";

            div.removeClassName("file-select");
            div.removeClassName("file-highlight");
            div.removeClassName("file-selected-highlight");
            div.origin = {'top': top, 'left': left};
            div.setOpacity(0.5);

            Util.scry('ghost-icons').insert(div);
            Browse.sel_clones.push(div);

        }
    },
    draw_clones: function (e) {
        if (!this.sel_clone_origin) {
	        return;
	    }
        var dx = e.clientX -  this.sel_clone_origin.x;
        var dy = e.clientY -  this.sel_clone_origin.y;
        var ds  = Util.scrollTop() - this.sel_clone_origin.st;
        var dl  = Util.scrollLeft() - this.sel_clone_origin.sl;

        for (var i = 0; i < Browse.sel_clones.length; i += 1) {
            var div = Browse.sel_clones[i];
            div.style.display = '';
            div.style.top = (div.origin.top + dy + ds).toString() + "px";
            div.style.left = (div.origin.left + dx + dl).toString() + "px";
        }
    },
    kill_clones: function () {
        if (this.sel_clones) {
            this.sel_clones.map(Util.yank);
            this.sel_clones = null;
            this.sel_clone_origin = null;
        }
    },
    find_drop_target: function (e) {
        if (!Browse.sel_clone_origin) {
            return;
        }

        var x = e.clientX + Util.scrollLeft() - Browse.sel_clone_origin.sl;
        var y = e.clientY + Util.scrollTop() - Browse.sel_clone_origin.st;

        var i = Util.bsearch(Browse.drop_targets, 0,
            function (f) {
                var c = Util.cmpBox(x, y, f.box);
                // this next line's weird
                // 1) -c because we want box relative to point
                // 2) fail with -1 if it's selected
                return (c !== 0) ? -c : (f.selected ? -1 : 0);
            }
        );

        if (i != -1) {
            return Browse.drop_targets[i];
        }

        return false;
    },
    can_drop: function (dest) {
        var selected_profile = Browse.profile_files(Browse.selected_files);

        if (dest.is_share && selected_profile.shared_folders > 0) {
            return "You're not allowed to nest shared folders.";

        } else if (dest.where == "/Public" && selected_profile.shared_folders > 0) {
            return "You're not allowed to move shared folders to your Public folder.";

        } else if (selected_profile.public_folder > 0) {
            return "You're not allowed to move your Public folder.";

        } else if (selected_profile.photos_folder > 0) {
            return "You're not allowed to move your Photos folder.";

        } else if (selected_profile.deleted > 0) {
            return "Moving deleted folders or files is not allowed.";
        }
        return true;
    },
    remove_drop_target: function (t) {
        var targets = Browse.drop_targets;
        var l = targets.length;
        var out = [];

        for (var i = 0; i < l; i++) {
            var test = targets[i];
            if (t != test) {
                out.push(test);
            }
        }

        Browse.drop_targets = out;
    },
    highlight_drop_target: function (e) {
        var t = Browse.find_drop_target(e);

        if (Browse.last_drop_target && Browse.last_drop_target != t) {
            Browse.last_drop_target.drop_lowlight();
        }
        Browse.last_drop_target = t;

        if (t) {
            if (Browse.can_drop(t) === true) {
                t.drop_highlight();
            } else {
                t.drop_highlight_bad();
            }
        }
    },
    show_copy_move: function (t) {
        if (Browse.copy_move_over == t) {
            return;
        }
        Browse.copy_move_over = t;

        var fg = Browse.copy_move_texts();
        var bg = Browse.copy_move_overlays();
        var all = bg.concat(fg);

        var left = t.box.left + Browse.sel_clone_origin.sl;
        var top = t.box.top + Browse.sel_clone_origin.st;

        if (t.box.height > t.box.width) {
            // tile vertically
            all.each(function (x) {
                var s = x.style;
                s.width = this.box.width + "px";
                s.height = Math.round(this.box.height / 2) + "px";
                s.left = left + "px";
                s.display = '';
                s.lineHeight = s.height;
            }, t);
            bg[0].style.top = top + "px";
            bg[1].style.top = (top + Math.round(t.box.height / 2)) + "px";
            fg[0].style.top = top + "px";
            fg[1].style.top = (top + Math.round(t.box.height / 2)) + "px";
        } else {
            // tile horizontally
            all.each(function (x) {
                var s = x.style;
                s.height = this.box.height + "px";
                s.width = Math.round(this.box.width / 2) + "px";
                s.top = top + "px";
                s.display = '';
                s.lineHeight = s.height;
            }, t);
            bg[0].style.left = left + "px";
            bg[1].style.left = (left + Math.round(t.box.width / 2)) + "px";
            fg[0].style.left = left + "px";
            fg[1].style.left = (left + Math.round(t.box.width / 2)) + "px";
        }
        fg[0].box = Util.getBox(fg[0]);
        fg[1].box = Util.getBox(fg[1]);
    },
    bold_copy_move: function (e) {
        var opts = Browse.copy_move_texts();
        var highlight = 0;

        if (e.clientY > opts[1].box.top) {
            highlight = 1;
        }

        if (opts[0].last_highlight == highlight) {
            return;
        }
        opts[0].last_highlight = highlight;

        opts[highlight].addClassName('copy-move-bold');
        opts[1 - highlight].removeClassName('copy-move-bold');
    },
    hide_copy_move: function () {
        Browse.copy_move_over = false;
        Browse.copy_move_overlays().invoke('hide');
        Browse.copy_move_texts().invoke('hide');
    },
    over_copy_option: function (e) {
        return Util.pointOnBox(e.clientX, e.clientY, Util.getBox('copy-text'));
    },
    cmos: [],
    copy_move_overlays: function () {
        if (!Browse.cmos.length) {
            Browse.cmos = $$('.copy-move-overlay');
        }

        return Browse.cmos;
    },
    cmts: [],
    copy_move_texts: function () {
        if (!Browse.cmts.length) {
            Browse.cmts = $$('.copy-move-text');
        }

        return Browse.cmts;
    },
    find_file: function (where) {
        return Browse.files.find(function (x) {
            return x.where == where;
        });
    },
    pull_file: function (where) {
        var files = Browse.files;
        var l = files.length;
        var out = [];

        var file = false;
        for (var i = 0; i < l; i++) {
            var f = files[i];
            if (f.where == where) {
                file = f;
            } else {
                out.push(f);
            }
        }

        Browse.files = out;
        return file;
    },
    reset_state: function () {
        // drop dom references for the gc's sake

        Browse.msg = false;
        Browse.dragging = false;
        Browse.files = [];
        Browse.selected_files = [];
        Browse.checked_files = [];
        Browse.lact_checked = null;
        Browse.sel_clones = [];
        Browse.sel_clone_origin = null;
        Browse.drop_targets = [];
        Browse.last_drop_target = null;
        Browse.selection = [];
        Browse.file_div_cache = null;
        Browse.has_parent_link = false;
        Browse.parent_link = null;
        Browse.in_placer = null;

        BrowseActions.kill_dropdowns();
    },
    update: function (content) {
        $("browse-files").update(content);
    },
    reload: function (path, force) {
        if (Browse.reloading) { // one reload at a time
            return;
        }
        if (Util.normPath(path) == Util.normPath(Browse.current_path) && !force) { // don't reload same path
            return;
        }

        if (!path) {
            path = Browse.current_path;
        }
        Browse.reloading = true;

        Browse.current_path = path;

        if (Browse.first_load) {
            Browse.first_load = false;
            BrowseURL.set_path_url(path);
        }

        Feed.showLoading(false, 'browse-files', true); // just_icon=true

        if (Util.ie) { // @HACK => Prototype doesn't handle calculating the left offset of nested offset elements properly.
            $("feed-loading").style.left = $("browse-box").viewportOffset()[0] + "px";
        }

        var del = Browse.deleted_shown ? "&show_deleted=yah" : "";
        var dbr = new Ajax.DBRequest("/browse2" + path + "?ajax=yes" + del, {
            parameters: {d: Browse.root_depth, mini: Browse.minimode},
            onSuccess: function (req) {
                Browse.reset_state();
                Browse.update(req.responseText);

                if (Browse.select_file) {
                    (function () {
                        var file_path = Util.normPath(path) + "/" + Util.urlquote(Browse.select_file);
                        var file = Browse.find_file(file_path);
                        if (file) {
                            file.select();
                            file.show_dropdown();
                            Browse.select_file = false;
                        }
                    }).defer();
                }
                var gc = Browse.global_checkbox();

                if (gc.selected) {
                    DBCheckbox.deselect(gc);
                }

                if (!Browse.msg) {
                    Browse.resort();
                }
                Browse.listen();
            },
            cleanUp: function () {
                Feed.hideLoading();
                Browse.reloading = false;
                if (document.activeElement && document.activeElement != document.body) {
                    document.activeElement.blur();
                }
                Util.childElementCache = {};
            },
            no_feed_reload: true
        });
        FileSearch.invalidate_cache();
        BrowseKeys.clear_highlight();
        return true;
    },
    unload: function () {
        Browse.reset_state();
        var dd = $('dropdown');
        if (dd) {
            dd = Util.yank(dd);
        }
    },
    breadcrumb: function (path) {
        path = Util.normPath(path);
        var parts = path.split("/");
        var bc = "";

        function bcrumb_link(name, path, ajax, last, first, icon) {
            name = name.snippet(40);
            if (!(first || last)) {
                icon = ""; // Only the first and last elements get icons
            } else {
                icon = "<img src=\"/static/images/icons/icon_spacer.gif\" class=\"sprite s_" + icon + " link-img\"  alt=\"\"/>";
            }

            var link;
            if (!last) {
                link = "<a href='" +  BrowseURL.get_path_url(path) + "'";

                if (ajax) {
                    link += " onclick='return Browse.reload(\""  + path + "\");'";
                }
                link += ">";
                link += icon + name;
                link += "</a>";

                link =  link + " &#187; ";

                return link;
            } else {
                return icon + name + (Browse.is_share ? " <small>(<a href='/share" + path + "'>Sharing info</a>)</small>" : "");
            }
        }

        for (var i = 0 ; i < parts.length; i++) {
            var name = new Emstring(decodeURIComponent(parts[i] || "My Dropbox").escapeHTML()).toString();
            var tpath = parts.slice(0, i + 1).join("/") || "/";
            var ajax = Browse.home === "" || (Browse.home_depth <= i);
            var last = i + 1 === parts.length;
            var first = i === 0;

            var icon = first && name.toString() == "My Dropbox" ? "dropbox" : FileOps.folder_to_icon(tpath, Browse.is_share);

            bc += bcrumb_link(name, tpath, ajax, last, first, icon);
        }
        $('browse-location').update(bc);
    },
    viewportOffset: function () {
        if (!Browse.files.length) {
            return;
        }

        if (!Browse.div_parent) {
            var op = Browse.files[0].div.offsetParent;
            if (!op) {
                return;
            }

            Browse._viewportOffset = {};
            Browse.div_parent = $(op);
            Browse._cumulativeOffset = Browse.div_parent.cumulativeOffset();
        }

        var l = Util.scrollLeft(Browse.div_parent);
        var t = Util.scrollTop(Browse.div_parent);

        if (!Browse.scrollTop || !Browse.scrollLeft || Browse.scrollTop != t  || Browse.scrollLeft != l) {
            Browse._viewportOffset.top = Browse._cumulativeOffset.top - t;
            Browse._viewportOffset.left = Browse._cumulativeOffset.left - l;
            Browse.scrollLeft = l;
            Browse.scrollTop = t;
        }

        return Browse._viewportOffset;
    },
    updateOffset: function () {
        if (!Browse.div_parent) {
            return;
        }

        Browse._cumulativeOffset = Browse.div_parent.cumulativeOffset();
        Browse.viewportOffset();
    },
    selectable: function () {
        Util.enableSelection(Util.scry('browse-files'));
    },
    unselectable: function () {
        Util.disableSelection(Util.scry('browse-files'));
    },
    profile_files: function (file_list) {
        var profile = { files:              0,
                        folders:            0,
                        shared_folders:     0,
                        deleted:            0,
                        public_folder:      0,
                        photos_folder:      0,
                        rejoinables:        0 };

        for (var i = 0; i < file_list.length; i += 1) {
            var file = file_list[i];

            if (file.dir) {
                profile.folders += 1;
            } else {
                profile.files += 1;
            }

            if (file.is_share) {
                profile.shared_folders += 1;
            }

            if (file.bytes == -1) {
                profile.deleted += 1;
            }

            if (file.where == "/Public") {
                profile.public_folder = 1;
            } else if (file.where == "/Photos") {
                profile.photos_folder = 1;
            }

            if (file.main_actions.indexOf("rejoin") >= 0) {
                profile.rejoinables += 1;
            }
        }
        return profile;
    },

    profile_summary: function (profile) {
        var file_string = "";

        if (profile.files) {
            file_string += Util.plural(profile.files, "file");
        }
        if (profile.files && profile.folders) {
            file_string += " and ";
        }
        if (profile.folders) {
            file_string += Util.plural(profile.folders, "folder");
        }

        return file_string;
    },

    global_checkbox: function () {
		return Util.childElement(document.getElementById("select-all-sorter"), 0);
    },
    onContext: function (e) {
        if (e) {
            Event.stop(e);
        }
        return false;
    }
};

/*global FileOps*/
var BrowseFile = Class.create({

    initialize: function (icon, where, href, caption, filename, size, bytes, ago, ts, actions, hash, is_dir, drop_target, need_new) {
        this.icon = icon;
        this.caption = caption;
        this.filename = filename;
        this.where = where;
        this.hash = hash;
        this.href = href;
        this.size = size != 'None' ? size : "";
        this.bytes = bytes;
        this.ago = ago;
        this.ts = ts;
        this.selected = false;
        this.drop_target = drop_target;
        this.dir = is_dir ? 1 : 0;
        this.is_share = icon == "folder_user";

        this.main_actions = actions.strip().replace("  ", " ").split(" ");

        Browse.add_file(this, need_new);
    },
    drag_dist: function (e) {
        return Math.abs(this.drag_startPos.x - e.clientX) +
            Math.abs(this.drag_startPos.y - e.clientY);
    },
    render: function (new_file, new_link) {
        var div;
        if (new_file) {
            div = new Element("div", {'class': Browse.details ? "browse-file-box-details" : "browse-file-box-iconic"});
            this.div = div;

            var del_class = this.bytes != '-1' ? "" : " deleted_file_line";
            var mini_class = Browse.minimode ? " details-filename-mini" : "";
            var link = new_link ? new_link : "#";
            this.div.update(" <div style='position:relative;'><div class='details-check'><img class='sprite s_checkbox checkbox' src='/static/images/icons/icon_spacer.gif' align='absbottom'></div> " +
                            "<div class='details-icon'><a><img class='sprite s_" + this.icon + "' src='/static/images/icons/icon_spacer.gif' align='absbottom'></a></div>" +
                            "<div class='details-filename" + del_class + mini_class + "'><a href='" + link + "'>" + this.caption + "</a></div> " +
                            "<div class='details-size'>" + (this.size || '&nbsp;') + "</div> " +
                            "<div class='details-modified'>" + this.ago + "</div> " +
                            "<a class='dropdown-arrow' style='visibility: hidden;' href='#'><img src='/static/images/big-dropdown.gif'></a>" +
                            "<br class='clear'/><div class='miniscule-text' style='line-height: 1px'>&nbsp;</div></div>");

            (function () {
                DBCheckbox.register(this.div.down("img"));
            }).bind(this).defer();

        } else {
            if (!Browse.file_div_cache) {
                Browse.file_div_cache = Browse.file_div.childElements();
            }
            div = Browse.file_div_cache[Browse.files.length - (Browse.has_parent_link ?  0 : 1)];
            this.div = div;
        }

        div.file = this;
        return div;
    },
    tooltip: function () {
        if (this.caption.unescapeHTML() != this.filename && this.filename.length) {
            Tooltip.show(this.a, this.filename);
        }
    },
    rename: function (where, is_folder, hash) {
        var new_name = FileOps.filename(encodeURIComponent(where));
        this.caption = new_name.snippet();
        this.filename = new_name;
        this.where = Util.urlquote(where);
        this.hash = hash;
        this.ago = is_folder ? "" : "just now";

        if (is_folder) {
            this.href = BrowseURL.get_path_url(this.where);
        } else {
            var parts = this.href.split("/");
            parts[parts.length - 1] = Util.urlquote(new_name) + "?w=" + hash;
            this.href = parts.join("/");
        }

        var a = this.div.down(".details-filename").down("a");
        a.update(this.caption);
        a.title = this.filename;
        a.href = this.href;
    },
    move: function (afterFinish) {
        var ef = new Effect.Fade(this.div, {'afterFinish': afterFinish});
    },
    del: function (afterFinish) {
        if (!Browse.deleted_shown) {
            var ef = new Effect.Fade(this.div, {'afterFinish': afterFinish});
            Browse.emptyCheck();
        } else {
            this.size = "None";
            this.bytes = -1;
            this.ago = this.dir ? "" : "just now";
            this.main_actions = this.dir ? ["full_restore"] : "undelete purge".split(" ");

            var a = this.div.down("a");
            a.addClassName("deleted_file_line");

            if (this.dir) {
                Sprite.src(this.div.down('img'), 'folder_gray');
            }
        }
    },
    purge: function (afterFinish) {
        var ef = new Effect.Fade(this.div, {'afterFinish': afterFinish});
        Browse.emptyCheck();
    },
    setOpacity: function () {
        if (this.size == 'None') {
            this.div.down("div").down("img").setOpacity(0.5);
        }
    },
    cachePos: function () {
        if (!this.div) {
            return;
        }

        if (!this.box) {
            this.box = {};
        }

        var offset = Browse.viewportOffset();
        if (!offset) {
            return;
        }

        this.box.left = offset.left + this.div.offsetLeft;
        this.box.top = offset.top + this.div.offsetTop;

        if (!this.box.width) {
            this.box.width = this.div.getWidth();
            this.box.height = this.div.getHeight();
        }
    },
    overlaps: function (box) {
        return Util.boxOnBox(box, this.box);
    },
    over: function () {
        if (Browse.dragging) {
            return;
        }

        if (!this.editing && this.filename && (!this.checked || (this.checked && Browse.checked_files.length == 1))) {
            this.dropdown_arrow(true); // on=true
        }

        if (!this.selected) {
            this.div.addClassName("file-highlight");
        }
    },
    out: function (e) {
        if (!this.div) {
            return;
        }
        if (e.toElement) {
            if (e.toElement.className == 'tooltip' || $(e.toElement) == this.div || $(e.toElement).descendantOf(this.div)) {
                return;
            }
        }

        if (!this.selected) {
            this.div.removeClassName("file-highlight");
            if (this.filename) {
                this.dropdown_arrow(false); // on=false
            }
        } else {
            this.div.removeClassName("file-selected-highlight");
        }
    },
    down: function (e) {
        if (this.editing || !this.filename) {
            return;
        }
        if (e) {
            Event.stop(e);
        }
        this.click_select(e);
        Browse.find_icons();
        BrowseActions.kill_dropdowns();

        this.drag_startPos = {x: e.clientX, y: e.clientY};
        this.drag_watch = this.drag.bindAsEventListener(this);
        this.up_watch = this.up.bindAsEventListener(this);

        Event.observe(document, "mousemove", this.drag_watch);
        Event.observe(document, "mouseup", this.up_watch);
    },
    show_dropdown: function () {
        BrowseActions.kill_dropdowns();
        if (!this.checked || Browse.checked_files.length == 1 && this.checked) {
            this.dropdown_arrow(true);  // on=true
            BrowseActions.dropdown(this, true); // show_all = true
        } else {
            BrowseActions.showMore();
        }
    },
    down_dropdown: function (e) {
        this.click_select(e);
        this.show_dropdown(true); // show_all=true
    },
    up: function (e) {
        if (this.editing || !this.filename) {
            return;
        }
        if (!Browse.dragging) {
            this.deselect();
        }
        Event.stop(e);

        var dropped = this.drag_end(e);

        if (e.target.tagName == "IMG" && $(e.target).hasClassName("checkbox")) {
            this.click_check(e);
        } else if (!dropped && (e.target.tagName != 'A' && e.target.parentNode && e.target.parentNode.tagName != 'A' || Util.is_right_click(e))) {
            this.click_select(e);
            this.show_dropdown();
        }

        Event.stopObserving(document, "mousemove", this.drag_watch);
        Event.stopObserving(document, "mouseup", this.up_watch);
    },
    click_check: function (e) {
        Browse.deselect_all();

		var dd = $("dropdown");
		if (dd) {
			Util.yank(dd);
		}

        var shift = e.shiftKey;
        if (shift && Browse.last_checked) {
            if (this.checked) {
                Browse.uncheck_range(Browse.files.indexOf(Browse.last_checked), Browse.files.indexOf(this));
            } else {
                Browse.check_range(Browse.files.indexOf(Browse.last_checked), Browse.files.indexOf(this));
            }
        } else {
            if (!this.checked) {
                this.check();
            } else if (this.checked) {
                this.decheck();
            }
        }

        Browse.last_checked = this;
    },
    click_select: function (e) {
        Event.stop(e);

        // no multiple select yet
        if (false && e.shiftKey && e.ctrlKey) {
            Browse.select_range_to(this, true); // additive=true
        } else if (false && e.shiftKey) {
            Browse.select_range_to(this);
        } else if (false && e.ctrlKey) {
            this.toggle();
            this.over();
        } else if (!this.selected) {
            Browse.deselect_all();
            this.select();
        }

        if (false && (!e.shiftKey || e.ctrlKey)) {
            Browse.shift_start = this;
        }
    },
    drag: function (e) {
        Event.stop(e);
        var drag_dist = this.drag_dist(e);

        if (!Browse.dragging && drag_dist >= 10) {
            this.drag_start(e);
        }

        if (drag_dist < 10) {
            return;
        }

        Browse.dragging = true;
        Browse.draw_clones(e);
        Browse.highlight_drop_target(e);
    },

    drag_start: function (e) {
        Browse.dragging = true;
        if (Browse.checked_files.length > 0 && Browse.checked_files.indexOf(this) == -1) {
            Browse.hide_checked();
        } else if (Browse.checked_files.indexOf(this) > -1) {
            for (var i = 0; i < Browse.checked_files.length; i += 1) {
                Browse.checked_files[i].select();
            }
        }
        this.select();
        Browse.clone_selected(e);
        Util.noHorizScroll();
    },
    drag_end: function (e) {
        if (!Browse.dragging) {
            return false;
        }

        Browse.dragging = false;
        var f = Browse.find_drop_target(e);
        Util.allowHorizScroll(); // weird, but you can't turn this back on before calculating the drop target...

        var dropped = false;
        if (f) {
            f.drop_lowlight();

            var can_drop = Browse.can_drop(f);
            if (can_drop !== true) {
                Notify.ServerError(can_drop);
            } else if (f != Browse.down_file) {
                assert(Browse.selected_files.length > 0, "Tried to move 0 files by dragging");
                var folder = f;
                FileOps.show_move_confirm(Browse.selected_files.slice(), decodeURIComponent(folder.where)); // is_folder=file.dir
                BrowseActions.kill_dropdowns();

                dropped = true;
            }
        }

        if (Browse.hidden_checked_files) {
            Browse.restore_checked();
        }

        Browse.deselect_all();
        Browse.kill_clones(e);

        return dropped;
    },
    highlight: function  () {
        var viewportHeight = document.viewport.getHeight();
        var offset = this.div.viewportOffset();
        if (offset.top < 0) {
            this.div.scrollIntoView(true);
        } else if (offset.top + this.div.getHeight() > viewportHeight) {
            this.div.scrollIntoView(false);
        }
        var arrow = $("highlight-arrow");
        arrow.show();
        arrow.clonePosition(this.div, {setWidth: false, setHeight: false, offsetLeft: -17, offsetTop: 6 });
    },
    dehighlight: function () {
        $("highlight-arrow").hide();
    },
    check: function (current_index) {
        if (this.editing) {
            return false;
        }

		var div = this.div;

        assert(!this.checked, "Tried to check a file that was already checked.");
        this.checked = true;

        this.checkbox = this.checkbox || Util.childElementByIndexPath(div, [0, 0, 0]);
        DBCheckbox.select(this.checkbox);
		div.addClassName("file-select");

        Browse.checked_files.push(this);

		current_index = current_index || Browse.files.indexOf(this);

		var files = Browse.files;
        var prev = files[current_index - 1];
        var next = files[current_index + 1];

        if (prev && prev.checked) {
			div.style.borderWidth = "0 1px 1px 1px";
			div.style.paddingTop = "4px";
        }

        if (next && next.checked) {
            next.div.style.borderWidth = "0 1px 1px 1px";
            next.div.style.paddingTop = "4px";
        }
    },

    decheck: function (checked_index, all) {
        if (this.editing) {
            return false;
        }
        assert(this.checked, "Tried to decheck a file that was not checked.");
        this.checked = false;

		var gcb = Browse.global_checkbox();
        if (gcb.selected) {
            DBCheckbox.deselect(gcb);
        }

        DBCheckbox.deselect(this.checkbox);

		if (!all) {
			checked_index = checked_index || Browse.checked_files.indexOf(this);
        	Browse.checked_files.remove(checked_index);
    	}

		var div = this.div;
		Util.removeClassName(this.div, "file-select");
		Util.removeClassName(this.div, "file-highlight");

        this.deselect();

		div.style.borderWidth = "1px";
		div.style.paddingTop = "";

    	if (!all) {
	        var current_index = Browse.files.indexOf(this);
	        var next = Browse.files[current_index + 1];

	        if (next && next.checked) {
	            next.div.style.borderWidth = "1px";
	            next.div.style.paddingTop = "";
	        }
		}
    },
    select: function () {
        if (!this.selected) {
            this.selected = true;
            this.div.addClassName("file-select");
            Browse.selected_files.push(this);
        }
    },
    deselect: function () {
        if (this.selected) {
            Browse.selected_files.remove(Browse.selected_files.indexOf(this));
            this.selected = false;
			var div = this.div;

            if (!this.checked) {
                this.div.removeClassName("file-select");
                this.div.removeClassName("file-highlight");
                this.div.removeClassName("file-selected-highlight");
                this.dropdown_arrow(false); // show=false
            }
        }
    },
    toggle: function (old_selected) {
        if (old_selected === undefined) {
            old_selected = this.selected;
        }

        if (old_selected) {
            return this.deselect();
        } else {
            return this.select();
        }
    },
    set: function (select) { // opposite of toggle
        return this.toggle(!select);
    },
    drop_highlight: function () {
        this.div.addClassName("drop-highlight");
    },
    drop_highlight_bad: function () {
        this.div.addClassName("drop-highlight-bad");
    },
    drop_lowlight: function () {
        this.div.removeClassName("drop-highlight");
        this.div.removeClassName("drop-highlight-bad");
    },
    dropdown_arrow: function (show) {
        if (this.div) {
            if (show) {
                if (this.div.select('.dropdown-arrow').length) {
                    this.div.select('.dropdown-arrow')[0].style.visibility = "visible";
                }
            } else {
                if (this.div.select('.dropdown-arrow').length) {
                    this.div.select('.dropdown-arrow')[0].style.visibility = "hidden";
                }
            }
        }
    },
    edit: function (new_folder) {
        if (Browse.in_placer) {
            Browse.in_placer.file.editing = false;
            Browse.in_placer.editor.dispose();
            Browse.in_placer.name.innerHTML = Browse.in_placer.name_old_innerHTML;
            if (Browse.in_placer.new_folder) {
                Browse.in_placer.file.del(Browse.show_message);
            }
            Browse.in_placer = null;
        }

        Browse.selectable();
        this.editing = true;
        var name = this.div.down(".details-filename").down("a");
        var orig_innerHTML = name.innerHTML;
        name.innerHTML = this.filename.escapeHTML();
        var action = new_folder ? "new" : "rename";
        var action_text = new_folder ? "Create" : "Rename";
        var is_folder = this.dir ? "yes" : "";
        var editor = new Ajax.InPlaceEditor(name, "/cmd/" + action + Util.normalize(this.where) + "?long_running", {
            okControl: 'link',
            cancelControl: 'link',
            htmlResponse: false,

            highlightColor: '#ddf0ff',
            highlightEndColor: '#fafdff',

            okText: action_text,
            cancelText: 'Cancel',
            clickToEditText: '',

            cols: Browse.minimode ? 15 : 25,

            callback: function (f, val) {
                val = val.gsub("/", ":");
                return {to_path: val, 't': Constants.TOKEN, 'folder': is_folder};
            },
            ajaxOptions: {'method': 'post'},
            onComplete: (function (req) {
                if (req) {
                    var status_code_start = req.status.toString().charAt(0);
                    if (status_code_start == '5' || status_code_start == '4') {
                        return;
                    }
                }
                Browse.unselectable();
                editor.dispose();
                this.editing = false;
                Browse.in_placer = null;
                if (!req) {
                    if (new_folder) {
                        this.purge(Browse.show_message); // afterwards=Browse.show_message
                    } else {
                        name.innerHTML = orig_innerHTML;
                    }
                    return;
                }
                if (req.responseText.indexOf('err:') === 0) {
                    Notify.ServerError(req.responseText.substr(4));
                    editor.dispose();
                    this.editing = false;
                    Browse.in_placer = null;
                    Browse.unselectable();
                    if (new_folder) {
                        this.del(Browse.show_message); // afterwards=Browse.show_message
                    } else {
                        this.rename(decodeURIComponent(this.where), is_folder, this.hash);
                    }
                } else {
                    var parts = req.responseText.split(":");
                    var new_where = parts.slice(0, -1).join(":");
                    var new_hash = parts.last();

                    if (new_folder) {
                        // try to find an old folder named the same thing to not show
                        var f = Browse.find_file(Util.urlquote(new_where));
                        if (f && f.bytes.toString() == "-1") {
                            f.purge();
                        }
                    }
                    this.rename(new_where, is_folder, new_hash);
                    Browse.resort();
                    this.div.scrollTo();
                    if (action == "new") {
                        Notify.ServerSuccess("The folder '" + name.innerHTML.escapeHTML() + "' was created successfully.");
                    } else if (action == "rename") {
                        Notify.ServerSuccess((is_folder ? "Folder": "File") + " renamed to '" + name.innerHTML.escapeHTML() + "'.");
                    }

                    TreeView.reset(); //refresh the folder list
                }
            }).bind(this),
            onFailure: (function () {
                editor.dispose();
                this.editing = false;
                Browse.in_placer = null;
                Browse.unselectable();
                if (new_folder) {
                    this.del(Browse.show_message); // afterwards=Browse.show_message
                } else {
                    name.innerHTML = orig_innerHTML;
                }
                Notify.ServerError();
            }).bind(this)
        });

        editor.enterEditMode();
        Browse.in_placer = {file: this, editor: editor, new_folder: new_folder, name: name, name_old_innerHTML: orig_innerHTML};
    }
});

/*global HoverIconSwap, HotButton, swfobject*/
var BrowseActions = {
    option_dict: {
        'share_here': {
            "icon": 'folder_user',
            "text": 'Share this folder',
            "onclick": function (e) {
                Sharing.show_share_existing_modal(Browse.current_path);
                Event.stop(e);
            }
        },
        'share_new': {
            "icon": 'folder_user',
            "text": 'Share a folder',
            "onclick": function (e) {
                Sharing.start_wizard(e);
                Event.stop(e);
            }
        },
        'share': {
            "icon": 'folder_user',
            "text": 'Share this folder',
            "onclick": function (e) {
                Sharing.show_share_existing_modal(this.where);
                Event.stop(e);
            }
        },
        'share_invite_more': {
            "icon": 'group',
            "text": 'Invite more people',
            "onclick": function (e) {
                Sharing.show_invite_more_modal(this.where);
                Event.stop(e);
            }
        },
        'share_leave': {
            "icon": 'folder_user_delete',
            "text": 'Leave shared folder',
            "onclick": function (e) {
                Sharing.show_leave_modal(this.where);
                Event.stop(e);
            }
        },
        'share_unshare': {
            "icon": 'link_break',
            "text": 'Unshare this folder',
            "onclick": function (e) {
                Sharing.show_unshare_modal(this.where);
                Event.stop(e);
            }
        },
        'share_opts_here': {"verb": 'share', "icon": 'user_add', "text": 'Sharing info'},
        'share_opts': {"verb": 'share', "icon": 'user_add', "text": 'Sharing info'},
        'revisions': {"verb": 'revisions', "icon": 'time_back', "text": 'Previous versions'},

        'undelete': {
            "icon": "basket_remove",
            "text": "Undelete",
            "onclick": function (e) {
                Event.stop(e);
                var file = Browse.find_file(this.where);
                FileOps.show_undelete(file);
            }
        },

        'copy_url': {
            "icon": "world_link",
            "text": 'Copy public link',
            "public_href": function () {
                return "http://" + Constants.PUBSERVER + '/u/' + Constants.uid + this.where.substring(7);
            },
            "onclick": function (e) {
                BrowseActions.showCopyPublicUrlModal("http://" + Constants.PUBSERVER + '/u/' + Constants.uid + this.where.substring(7));
                Event.stop(e);
            }
        },

        'download': {
            "href": function () {
                return Constants.protocol + "://" + Constants.block + '/get' + this.where + this.hash + "&dl=1";
            },
            "icon": "download_arrow",
            "text": "Download file"
        },

        'view': {
            "href": function () {
                return Constants.protocol + "://" + Constants.block + '/get' + this.where + this.hash;
            },
            "icon": "page_white_magnify",
            "text": "View file"
        },

        'zipped_dl': {
            "icon": "page_white_compressed",
            "text": "Download folder",
            "onclick": function (e) {
                var file = Browse.find_file(this.where);
                FileOps.do_bulk_download([file]);
                Event.stop(e);
            }
        },

        'xattr_dl': {
            "icon": "download_arrow",
            "text": "Download file",
            "onclick": function (e) {
                var file = Browse.find_file(this.where);
                FileOps.do_bulk_download([file]);
                Event.stop(e);
            }
        },

        'photos': {
            "href": function () {
                return '/photos' + this.where.substring(7);
            },
            "icon": "pictures",
            "text": "Gallery view"
        },

        'a_photo': {
            "href": function () {
                return '/photoshow' + this.where.substring(7);
            },
            "icon": "pictures",
            "text": "Gallery view"
        },

        'rejoin': {"verb": "rejoin", "icon": "folder_user", "text": "Rejoin share"},

        'restore': {
            "href": function () {
                return '/restore' + this.where + "?prev=" + encodeURIComponent(location.href);
            },
            "icon": "time_go",
            "text": "Restore folder"
        },

        'full_restore': {
            "href": function () {
                return '/restore' + this.where + "?prev=" + encodeURIComponent(location.href);
            },
            "icon": "time_back",
            "text": "Restore folder"
        },

        'show_del': {
            "onclick": function (e) {
                window.location.href = BrowseURL.get_del_url(true);
                Event.stop(e);
            },
            "icon": "show_del",
            "text": "Show deleted files"
        },

        'hide_del': {
            "onclick": function (e) {
                window.location.href = BrowseURL.get_del_url(false);
                Event.stop(e);
            },
            "icon": "hide_del",
            "text": "Hide deleted files"
        },

        'copy': {
            "icon": "page_white_copy",
            "text": 'Copy file to...',
            "onclick": function (e) {
                FileOps.show_copy(this.where);
                Event.stop(e);
            }
        },

        'copy_folder': {
            "icon": "folder_page",
            "text": 'Copy folder to...',
            "onclick": function (e) {
                FileOps.show_copy(this.where, true); // is_folder=true
                Event.stop(e);
            }
        },

        'copy_package': {
            "icon": "package_add",
            "text": 'Copy package to...',
            "onclick": function (e) {
                FileOps.show_copy(this.where, true); // is_folder=true
                Event.stop(e);
            }
        },

        'move': {
            "icon": "page_white_go",
            "text": 'Move...',
            "onclick": function (e) {
                FileOps.show_move(this.where);
                Event.stop(e);
            }
        },

        'move_folder': {
            "icon": "folder_go",
            "text": 'Move...',
            "onclick": function (e) {
                FileOps.show_move(this.where, true); // is_folder=true
                Event.stop(e);
            }
        },

        'move_package': {
            "icon": "package_go",
            "text": 'Move...',
            "onclick": function (e) {
                FileOps.show_move(this.where, true); // is_folder=true
                Event.stop(e);
            }
        },

        'rename': {
            "icon": "page_white_edit",
            "text": 'Rename...',
            "onclick": function (e) {
                var file = Browse.find_file(this.where);
                if (file) {
                    file.edit();
                }
                Event.stop(e);
            }
        },

        'rename_folder': {
            "icon": "folder_edit",
            "text": 'Rename...',
            "onclick": function (e) {
                var file = Browse.find_file(this.where);
                if (file) {
                    file.edit();
                }
                Event.stop(e);
            }
        },

        'rename_package': {
            "icon": "package",
            "text": 'Rename...',
            "onclick": function (e) {
                var file = Browse.find_file(this.where);
                if (file) {
                    file.edit();
                }
                Event.stop(e);
            }
        },

        'delete': {
            "icon": "cancel",
            "text": 'Delete...',
            "onclick": function (e) {
                FileOps.show_delete(this.where);
                Event.stop(e);
            }
        },

        'delete_folder': {
            "icon": "cancel",
            "text": 'Delete...',
            "onclick": function (e) {
                FileOps.show_delete(this.where, true); // is_folder=true
                Event.stop(e);
            }
        },

        'delete_package': {
            "icon": "cancel",
            "text": 'Delete...',
            "onclick": function (e) {
                FileOps.show_delete(this.where, true); // is_folder=true
                Event.stop(e);
            }
        },

        'purge': {
            "icon": "purge",
            "text": "Permanently delete",
            "onclick": function (e) {
                FileOps.show_purge(this.where);
                Event.stop(e);
            }
        },

        'purge_folder': {
            "icon": "purge",
            "text": "Permanently delete",
            "onclick": function (e) {
                FileOps.show_purge(this.where, true); // is_folder=true
                Event.stop(e);
            }
        },

        'new_folder': {
            "icon": "folder_add",
            "text": 'Create folder',
            "onclick": function (e) {
                FileOps.inplace_new_folder(this.where);
                Event.stop(e);
                return false;
            }
        },

        'upload': {
            "icon": "page_white_get",
            "text": "Upload",
            "onclick": function (e) {
                FileOps.show_upload(this.where);
                Event.stop(e);
            }
        },

        'app_info': {
            "icon": "application_double",
            "text": "Application info",
            "href": function () {
                return "/applications";
            }
        },

        'more_actions': {
            "icon": "wand",
            "text": "More actions <img src='/static/images/icons/icon_spacer.gif' class='sprite s_big-dropdown icon_no_hover' style='margin-right:-4px;' alt=''/><img style='margin-right:-4px;' src='/static/images/icons/icon_spacer.gif' class='sprite s_big-dropdown_blue icon_hover' alt=''/>",
            "onclick": function (e) {
                Event.stop(e, true);
                BrowseActions.showMore();
            },
            "width": "140px"
        },

        'move_bulk': {
            'icon': 'folder_go',
            'text': function () {
                return 'Move ' + Browse.checked_files.length + ' items';
            },
            "onclick": function (e) {
                FileOps.show_move_bulk(Browse.checked_files);
                Event.stop(e);
            }
        },

        'copy_bulk': {
            'icon': 'folder_page',
            'text': function () {
                return 'Copy ' + Browse.checked_files.length + ' items';
            },
            "onclick": function (e) {
                FileOps.show_copy_bulk(Browse.checked_files);
                Event.stop(e);
            }
        },

        'delete_bulk': {
            'icon': 'cancel',
            'text': function () {
                return 'Delete ' + Browse.checked_files.length + ' items';
            },
            "onclick": function (e) {
                FileOps.show_bulk_delete(Browse.checked_files);
                Event.stop(e);
            }
        },

        'download_bulk': {
            'icon': 'page_white_compressed',
            'text': function () {
                return 'Download items';
            },
            "onclick": function (e) {
                FileOps.do_bulk_download(Browse.checked_files);
                Event.stop(e);
            }
        },

        'purge_bulk': {
            'icon': 'purge',
            'text': 'Permanently delete',
            "onclick": function (e) {
                FileOps.show_bulk_purge(Browse.checked_files);
                Event.stop(e);
            }
        },

        'restore_bulk': {
            'icon': 'time_go',
            'text': function () {
                return 'Undelete ' + Browse.checked_files.length + ' items';
            },
            "onclick": function (e) {
                FileOps.show_bulk_restore(Browse.checked_files);
                Event.stop(e);
            }
        }
    },
    generate_li: function (where, what, hash, isActionBar) {
        assert(BrowseActions.option_dict[what], "Couldn't find " + what + " " + where);

        var obj = Object.clone(BrowseActions.option_dict[what]);
        obj.where = where;
        obj.hash = hash ? "?w=" + hash : "";
        var li = new Element("li");

        var img = Sprite.make(obj.icon, {'class': 'icon_no_hover'});
        var img2 = Sprite.make(obj.icon + "_blue", {'class': 'icon_hover'});

        var a;

        if (typeof(obj.text) == "function") {
            obj.text = obj.text();
        }

        if (isActionBar) {
            var t = new Element('span').update(img).insert(img2).insert(obj.text);
            a = HotButton.make(t);
        } else {
            a = new Element('a').update(img).insert(img2).insert(obj.text);
            a.addClassName("background-icon");
            HoverIconSwap.register(a);
        }

        a.target = "_top";
        if (!obj.onclick) {
            a.href = obj.verb ? ("/" + obj.verb + where) : obj.href();
        }

        if (obj.width) {
            a.style.width = obj.width;
        }

        if (obj.onclick) {
            a.observe('mouseup', (function (e) {
                obj.onclick(e);
                if (what != "more_actions") {
                    BrowseActions.kill_dropdowns();
                }
            }).bindAsEventListener(obj));
        }

        li.update(a);

        return li;
    },
    availMoreActions: function () {
        if (Browse.checked_files.length === 0) {
            return [];
        } else if (Browse.checked_files.length === 1) {
            return Browse.checked_files[0].main_actions;
        }


        // Move, Copy, Delete, Purge, Restore, Download ZIP
        var options = ["move_bulk", "copy_bulk", "delete_bulk", "restore_bulk", "purge_bulk", "download_bulk"];
        var profile = Browse.profile_files(Browse.checked_files);

        if (profile.deleted > 0 || profile.public_folder > 0 || profile.photos_folder > 0) {
            options.removeItem("move_bulk");
            options.removeItem("copy_bulk");
            options.removeItem("delete_bulk");
        }

        if (profile.shared_folders > 0) {
            options.removeItem("copy_bulk");
        }

        if (profile.deleted > 0) {
            options.removeItem("delete_bulk");
            options.removeItem("download_bulk");
        }

        if (profile.deleted != Browse.checked_files.length) {
            options.removeItem("restore_bulk");
            options.removeItem("purge_bulk");
        }

        return options;
    },

    showMore: function () {
        BrowseActions.kill_dropdowns();

        var old_one = $("show-more-dropdown");
        if (old_one) {
            Util.yank(old_one);
        }

        var ul = new Element("ul", {"id": "show-more-dropdown"});
        ul.addClassName("dropdown dropdown-lite");
        var actions = BrowseActions.availMoreActions();

        if (!actions.length) {
            ul.insert(new Element("li").update(new Element("p", {style: "padding: 5px 4px; margin: 0; text-align: center;"}).update(Browse.checked_files.length === 0 ? "Select one or more files using the checkboxes." : "No actions available for these files")));
        } else {
            actions.each(function (action) {
                ul.insert(BrowseActions.generate_li(Browse.checked_files[0].where, action, Browse.checked_files[0].hash, false));
            });
        }

        $("browse-files").insert(ul);
        var show_more_button = $$("#browse-root-actions li").last();

        ul.clonePosition(show_more_button);
        ul.style.height = "auto";
        ul.style.top = parseInt(ul.style.top, 10) + 1 + show_more_button.getHeight() + "px";
        ul.style.width = parseInt(ul.style.width, 10) - 1 + "px";

        if (ul.viewportOffset().top < 0) {
            $("browse-location").scrollIntoView();
        }
        Event.observe(document, "click", BrowseActions.hideMore);
    },

    hideMore: function (e) {
        var show_more_button = $$("#browse-root-actions li").last();

        if (e && e.target && $(e.target).descendantOf(show_more_button)) {
            return;
        }

        var show_more = $("show-more-dropdown");
        if (show_more) {
            Util.yank(show_more);
        }

        Event.stopObserving(document, "click", BrowseActions.hideMore);
    },

    fillActionUL: function (where, actions, ul, add_separator, hash) {
        ul.update(add_separator ? "<li class='action-separator'>|</li>" : "");
        actions.push("more_actions");
        if (actions && actions.length) {
            ul.show();
            actions.each(
                function (action) {
                    ul.insert(BrowseActions.generate_li(where, action, hash, true));
                }
            );
        } else {
            ul.hide();
        }
    },
    clear: function (file) {
        $('browse-filename').update();
        $('browse-filesize').update();
        $('browse-filemodified').update();
        $('browse-file-actions').down("ul").update();
        $('more-file-actions').update();
    },
    kill_dropdowns: function () {
        BrowseActions.hideMore();

        var dd = $('dropdown');
        if (dd) {
            for (var i = 0; i < Browse.checked_files.length; i += 1) {
                Browse.checked_files[i].dropdown_arrow(false);
            }
            dd.hide();
        }
    },
    dropdown: function (file, show_all, e) {
        var elt = file.div;
        if (e) {
            Event.stop(e);
        }
        if (!elt || !file.selected) {
            return;
        }

        var where = file.where;
        var hash = file.hash;
        var options = file.main_actions;

        if (!options.length) {
            return;
        }

        var dd = $("dropdown");
        if (dd) {
            Event.stopObserving(document, "click", dd.listener);
            dd = Util.yank(dd);

            if (Browse.more_link) {
                Event.stopObserving(Browse.more_link, 'click', Browse.more_link_action);
                Browse.more_link = null;
                Browse.more_link_action = null;
            }
        }

        elt = $(elt);
        var div = new Element("div", {id: "dropdown"});
        var menu = new Element("ul", {"class": "dropdown dropdown-lite note"});
        $A(options).each(function (option) {
            menu.insert(BrowseActions.generate_li(where, option, hash));
        });

        var more_link = null;
        div.insert(menu);

        menu.listener =
            function (e) {
                var click_target = $(e.target);
                if (click_target.descendantOf(file.div)) {
                    return;
                }

                Event.stopObserving(document, "click", menu.listener);

                if (Browse.more_link) {
                    Event.stopObserving(Browse.more_link, 'click', Browse.more_link_action);
                    Browse.more_link = null;
                    Browse.more_link_action = null;
                }
                if (e.target.parentNode.tagName != 'A' && e.target.tagName != 'A' || (e.target.href && e.target.href.length <= 2)) {
                    Event.stop(e);
                }

                var dd_elt = $("dropdown");
                if (dd_elt) {
                    dd_elt = Util.yank(dd_elt);
                    if (file.checked) {
                        file.dropdown_arrow(false);
                    }
                }
            };

        Event.observe(document, "click", menu.listener);

        $(elt.offsetParent).insert(div);

        var pos = elt.positionedOffset();

        var dim = elt.getDimensions();
        var dimen = div.getDimensions();
        div.style.left = (pos.left - dimen.width + dim.width) + "px";
        div.style.top = (pos.top + dim.height - 1) + "px";

        if (!Util.ie) {
            var vpos_top = div.cumulativeOffset().top - Util.scrollTop();
            if (dimen.height + vpos_top > (window.innerHeight || document.documentElement.clientHeight)) {
                setTimeout(function () {
                    div.scrollIntoView(false);
                    div = null;
                }, 100);
            }
        }
        window.focus();

        return false;
    },
    showCopyPublicUrlModal: function (url) {
        Modal.icon_show(BrowseActions.getIcon("copy_url"), "Copy Public Link", DomUtil.fromElm('copy-public-url'));
        BrowseActions.addCopyUrlFlash(url);

        $('public_url').setValue(url);
        $('public_url').select();
    },
    clipboard_copy_done: function () {
        $('copy_success').update(Sprite.make("tick", {'style': 'vertical-align:middle;'}));
        $('copy_success').insert('&nbsp;Copied!');
    },

    getIcon: function (name) {
        return BrowseActions.option_dict[name].icon;
    },

    shortenPublicLink: function () {
        Util.shorten_url($F('public_url'), BrowseActions.updatePublicLink);
        var img = new Element("img", {id: 'publink_loading', src: '/static/images/icons/ajax-loading-small.gif'});
        img.addClassName("right");
        $("modal-content").down("a").update(img);
    },

    updatePublicLink: function (url) {
        $('public_url').setValue(url);
        $('public_url').select();
        $('publink_loading').remove();
        BrowseActions.addCopyUrlFlash(url);
    },

    addCopyUrlFlash: function (url) {
        var params = { 'wmode'    : 'transparent',
                       'flashVars': "copy_text=" + Util.urlquote(url) + "&callback=BrowseActions.clipboard_copy_done()" };

        swfobject.embedSWF('/static/swf/copy_to_clipboard.swf', 'copy_button', "100%", "100%", '6.0.65', false, false, params);
        $('copy_button').absolutize();
        $('copy_button').clonePosition($('real_copy'));
    }
};

var BrowseKeys = {
    init: function () {
        document.observe("keypress", BrowseKeys.pressed);
        document.observe("keydown", BrowseKeys.keydown);
    },

    getKey: function (e) {
        var key = e.keyCode || e.which || e.charCode;
        return key;
    },

    focus_in_input: function (e) {
        return document.activeElement && ["INPUT", "TEXTAREA", "SELECT"].indexOf(document.activeElement.tagName) != -1;
    },

	keydown: function (e) {
        var keyCode = BrowseKeys.getKey(e);

		if (BrowseKeys.focus_in_input()) {
			if (keyCode == 27) { // esc
				document.activeElement.blur();
			}
			return;
		} else if (!document.activeElement) {
			return;
		}

		if (keyCode == 27) { // 'esc' key
            BrowseKeys.hide_chart();
            Modal.hide();
		}
	},

    pressed_dict: {
        'search': {
            'title':    "Search your files",
            'key':      '/',
            'onPress':  function (e) {
                $("filesearch").focus();
                $(document.body).scrollTo();
            }
        },

        'move': {
            'title':    "Move checked files",
            'key':      'm',
            'onPress':  function (e, actions) {
                if (actions.indexOf("move") > -1) {
                    BrowseActions.option_dict.move_bulk.onclick(e);
                }
            }
        },

        'check_all': {
            'title':    "Check all files",
            'key':      'a',
            'onPress':  function () {
                Browse.check_all();
            }
        },
        'check_none': {
            'title':    "Uncheck all files",
            'key':      'n',
            'onPress':  function () {
                Browse.decheck_all();
            }
        },
        'show_del': {
            'title':    "Show/hide deleted files",
            'key':      'd',
            'onPress':  function () {
                window.location.href = BrowseURL.get_del_url(BrowseURL.get_del() != 1);
            }
        },
        'help': {
            'title':    "Show keyboard shorcuts",
            'key':      '?',
            'shift':    true,
            'onPress':  function () {
                BrowseKeys.toggle_chart();
            }
        },
        'copy': {
            'title':    "Copy checked files",
            'key':      "c",
            'onPress':  function (e, actions) {
                if (actions.indexOf("copy") > -1) {
                    BrowseActions.option_dict.copy_bulk.onclick(e);
                }
            }
        },
        'up_dir': {
            'title':    "Up a directory",
            'key':      'u',
            'onPress':  function () {
                if (!Browse.reloading) {
                    var parent_dir = Util.parentDir(Util.normPath(Browse.current_path));

                    if (Browse.current_path != parent_dir) {
                        BrowseURL.set_path_url(parent_dir);
                    }
                }
            }
        },

        'highlight_up': {
            'title':    "Highlight previous file",
            'key':      'k',
            'onPress': function () {
                BrowseKeys.highlight_up();
            }
        },

        'highlight_top': {
            'title':    "Highlight first file",
            'key':      'k',
            'shift':    true,
            'onPress':  function () {
                if (Browse.highlight_index >= 0) {
                    Browse.files[Browse.highlight_index].dehighlight();
                }
                Browse.highlight_index = 0;
                Browse.files[0].highlight();
                $("header").scrollTo();
            }
        },
        'highlight_down': {
            'title':    "Highlight next file",
            'key':      'j',
            'onPress':  function () {
                BrowseKeys.highlight_down();
            }
        },
        'highlight_bottom': {
            'title':    "Highlight last file",
            'key':      'j',
            'shift':    true,
            'onPress':  function () {
                if (Browse.highlight_index >= 0) {
                    Browse.files[Browse.highlight_index].dehighlight();
                }
                Browse.highlight_index = Browse.files.length - 1;
                Browse.files[Browse.highlight_index].highlight();
                $("footer").scrollTo();
            }
        },
        'check_file': {
            'title':    "Check highlighted file",
            'key':      ' ',
            'shift':    'optional',
            'stop_event': false,
            'onPress':  function (e) {
                if (Browse.highlight_index >= 0) {
                    var file = Browse.files[Browse.highlight_index];
                    file.click_check(e);
                    Event.stop(e);
                }
            }
        },
        'open_file': {
            'title':    "Open highlighted file",
            'key':      'o',
            'onPress':  function () {
                if (Browse.highlight_index >= 0 && !Browse.reloading) {
                    var file = Browse.files[Browse.highlight_index];
                    var url;
                    if (file.dir) {
                        url = "#" + file.where;
                    } else {
                        url = file.a.href;
                    }
                    window.location = url;
                }
            }
        }
    },

    pressed: function (e) {
        var keyCode = BrowseKeys.getKey(e);
        var key = String.fromCharCode(keyCode).toLowerCase();
        var shift = e.shiftKey;

        // Make sure an input doesn't have focus
        if (!document.activeElement || BrowseKeys.focus_in_input()) {
            return;
        }
        var actions = BrowseActions.availMoreActions().join(" ");

        var k;
        for (k in BrowseKeys.pressed_dict) {
            var key_action = BrowseKeys.pressed_dict[k];
            if (key_action.key == key && ((key_action.shift || false) == shift || key_action.shift == "optional")) {
                key_action.onPress(e, actions);
                if (key_action.stop_event !== false) {
                    Event.stop(e);
                }
                break;
            }
        }
    },

    clear_highlight: function () {
        if (Browse.highlight_index >= 0 && Browse.highlight_index < Browse.files.length) {
            Browse.files[Browse.highlight_index].dehighlight();
        }

        Browse.highlight_index = -1;
    },

    highlight_up: function () {
        if (!Browse.files.length) {
            return;
        }

        if (Browse.highlight_index > 0 && Browse.highlight_index < Browse.files.length) {
            Browse.files[Browse.highlight_index].dehighlight();
        }

        if (Browse.highlight_index < 0) {
            Browse.highlight_index = Browse.files.length - 1;
        } else if (Browse.highlight_index === 0) {
            return;
        } else {
            Browse.highlight_index = Browse.highlight_index - 1;
        }

        Browse.files[Browse.highlight_index].highlight();
    },

    highlight_down: function () {
        if (!Browse.files.length) {
            return;
        }

        if (Browse.highlight_index >= 0 && Browse.highlight_index < Browse.files.length) {
            Browse.files[Browse.highlight_index].dehighlight();
        }

        if (Browse.highlight_index < 0) {
            Browse.highlight_index = 0;
        } else if (Browse.highlight_index == Browse.files.length - 1) {
            Browse.highlight_index = Browse.files.length - 1;
        } else {
            Browse.highlight_index = Browse.highlight_index + 1;
        }

        Browse.files[Browse.highlight_index].highlight();
    },

    toggle_chart: function () {
        if ($("keys-chart").style.display == "none") {
            BrowseKeys.show_chart();
        } else {
            BrowseKeys.hide_chart();
        }
    },
    show_chart: function () {
        var chart = $("keys-chart");
        chart.absolutize();
        chart.clonePosition($("browse-files"));
        var offsettop = $("browse-files").viewportOffset()[1];
        if (offsettop < 0) {
            chart.style.top = parseInt(chart.style.top, 10) - $("browse-files").viewportOffset()[1] + 10 + "px";
        }
        chart.setOpacity(0.85);
        chart.show();
    },
    hide_chart: function () {
        $("keys-chart").hide();
    }
};

var FileOps = {
    folder_to_icon: function (fq_path, is_share) {
        assert(fq_path, "folder_to_icon was not given a path.");

        fq_path = Util.normalize(fq_path.toLowerCase());

        if (is_share) {
            return "folder_user";
        } else if (fq_path == "/photos") {
            return "folder_photos";
        } else if (fq_path == "/public") {
            return "folder_public";
        } else {
            return "folder";
        }

    },
    filename_to_icon: function (filename) {
        var file_ext = FileOps.file_extension(filename).toLowerCase();
        var ext_map = { 'exe' : 'page_white_gear',
                        'dll' : 'page_white_gear',
                        'xls' : 'page_white_excel',
                        'xlsx' : 'page_white_excel',
                        'ods' : 'page_white_tux',
                        'c' : 'page_white_c',
                        'h' : 'page_white_c',
                        'php' : 'page_white_php',
                        'mp3' : 'page_white_sound',
                        'wav' : 'page_white_sound',
                        'm4a' : 'page_white_sound',
                        'wma' : 'page_white_sound',
                        'aiff' : 'page_white_sound',
                        'au' : 'page_white_sound',
                        'ogg' : 'page_white_sound',
                        'doc' : 'page_white_word',
                        'docx' : 'page_white_word',
                        'odt' : 'page_white_tux',
                        'ppt' : 'page_white_powerpoint',
                        'pptx' : 'page_white_powerpoint',
                        'odp' : 'page_white_tux',
                        'txt' : 'page_white_text',
                        'rtf' : 'page_white_text',
                        'sln' : 'page_white_visualstudio',
                        'vcproj' : 'page_white_visualstudio',
                        'html' : 'page_white_code',
                        'htm' : 'page_white_code',
                        'psd' : 'page_white_paint',
                        'pdf' : 'page_white_acrobat',
                        'fla' : 'page_white_actionscript',
                        'swf' : 'page_white_flash',
                        'gif' : 'page_white_picture',
                        'png' : 'page_white_picture',
                        'jpg' : 'page_white_picture',
                        'jpeg' : 'page_white_picture',
                        'tiff' : 'page_white_picture',
                        'tif' : 'page_white_picture',
                        'bmp' : 'page_white_picture',
                        'odg' : 'page_white_picture',
                        'py' : 'page_white_code',
                        'gz' : 'page_white_compressed',
                        'tar' : 'page_white_compressed',
                        'rar' : 'page_white_compressed',
                        'zip' : 'page_white_compressed',
                        'iso' : 'page_white_dvd',
                        'css' : 'page_white_code',
                        'xml' : 'page_white_code',
                        'tgz' : 'page_white_compressed',
                        'bz2' : 'page_white_compressed',
                        'rb' : 'page_white_ruby',
                        'cpp' : 'page_white_cplusplus',
                        'java' : 'page_white_cup',
                        'cs' : 'page_white_csharp',
                        'ai' : 'page_white_vector' };
        return ext_map[file_ext] || "page_white";
    },
    file_extension: function (filename) {
        return filename.split(".").last();
    },
    raw_filename: function (path) {
        // Asumes path is not url encoded
        return FileOps.filename(encodeURIComponent(path));
    },
    filename: function (path) {
        // Assumes path is always url encoded
        var decoded_path = decodeURIComponent(path);

        path = Util.normPath(decoded_path);
        path = path.split("/");
        var filename = path.pop();

        if (filename === "") { // looks like top level Dropbox
            return "My Dropbox";
        }

        return filename;
    },
    dir_handler: function (path, obj) {
        if (typeof(obj) == 'string') {
            obj = $(obj);
        }

        var highlighted = $$(".treeview .highlight")[0];
        if (highlighted) {
            highlighted.removeClassName("highlight");
            var old_icon = highlighted.down(".link-img");
            if (old_icon) {
                old_icon.className = old_icon.className.replace("_blue ", " ");
            }
        }

        var mydiv = obj.up('div');
        mydiv.addClassName("highlight");

        var icon = mydiv.down(".link-img");

        if (icon) {
            var classes = icon.className.split(" ");
            for (var i = 0; i < classes.length; i += 1) {
                if (classes[i].startsWith("s_") && !classes[i].match(/_blue$/)) {
                    classes[i] = classes[i] + "_blue";
                }
            }
            icon.className = classes.join(" ");
        }

        Modal.selected_div = mydiv;

        if (Modal.shown()) {
            obj.blur();
        }
        document.fire("db:dir_click", {path: path});
        Modal.vars.selected_path = encodeURIComponent(path);
    },
    show_folder_pick: function (title, file, action, for_folder) {
        DomUtil.fillVal(FileOps.filename(file).escapeHTML(), 'folder-pick-file');

        TreeView.move('copy-move-treeview', 'folder-pick-treeview');
        TreeView.enable_shared('copy-move-treeview');

        var icon = title.startsWith("Move") ? BrowseActions.getIcon('move_bulk') : BrowseActions.getIcon('copy_bulk');
        Modal.icon_show(icon, title, DomUtil.fromElm('folder-pick'), {'where': file, 'action': action, 'folder': for_folder});

        // prep by selecting the first
        var first_link = $('first-treeview-link');
        if (!Util.ie) {
            first_link.onclick();
        }
    },
    show_bulk_folder_pick: function (title, action_name, files, action) {
        var profile = Browse.profile_files(files);
        var file_string = Browse.profile_summary(profile);

        DomUtil.fillVal(action_name, 'bulk-folder-pick-action');
        DomUtil.fillVal(action_name.capitalize(), 'bulk-folder-pick-action-text');
        DomUtil.fillVal(file_string, 'bulk-folder-pick-file');

        TreeView.move('copy-move-treeview', 'bulk-folder-pick-treeview');
        TreeView.enable_shared('copy-move-treeview');

        document.observe("db:dir_click", function (event) {
            var button = $$("#modal-content .bulk-folder-pick-action-text").first();
            if (button) {
                button.setValue(action_name.capitalize() + " " + files.length + " items to " + FileOps.raw_filename(event.memo.path));
            }
        });

        var icon = action_name == "move" ? BrowseActions.getIcon('move_bulk') : BrowseActions.getIcon('copy_bulk');
        Modal.icon_show(icon, title, DomUtil.fromElm('bulk-folder-pick'), {'files': files, 'action': action});

        // prep by selecting the first
        var first_link = $('first-treeview-link');
        if (!Util.ie) {
            first_link.onclick();
        }

    },
    show_copy: function (file_path, is_folder) {
        var f = is_folder ? "Folder" : "File";
        DomUtil.fillVal("copy", 'folder-pick-action');
        DomUtil.fillVal("Copy " + f, 'folder-pick-action-text');
        DomUtil.fillVal(f, 'folder-pick-file-folder');
        FileOps.show_folder_pick("Copy " + f + " to...", file_path, FileOps.do_copy, is_folder);
    },
    show_copy_bulk: function (files) {
        FileOps.show_bulk_folder_pick("Copy " + files.length + " Items to...", "copy", files, FileOps.do_bulk_copy);
    },
    show_move_bulk: function (files) {
        FileOps.show_bulk_folder_pick("Move " + files.length + " Items to...", "move", files, FileOps.do_bulk_move);
    },
    show_move: function (file_path, is_folder) {
        var f = is_folder ? "Folder" : "File";
        DomUtil.fillVal("move", 'folder-pick-action');
        DomUtil.fillVal("Move " + f.escapeHTML(), 'folder-pick-action-text');
        DomUtil.fillVal(f, 'folder-pick-file-folder');
        FileOps.show_folder_pick("Move " + f.escapeHTML() + " to...", file_path, FileOps.do_move, is_folder);
    },
    show_move_confirm: function (files, to) {
        var profile = Browse.profile_files(files);
        var file_summary = Browse.profile_summary(profile);

        var desc = files.length == 1 ? "'" + files[0].filename + "'": file_summary;

        DomUtil.fillVal(desc, 'move-confirm-filename');
        DomUtil.fillVal(FileOps.raw_filename(to), 'move-confirm-dest');
        DomUtil.fillVal("Move " + file_summary, 'move-confirm-action-text');
        Modal.icon_show(BrowseActions.getIcon("move"), "Move " + desc + "?", DomUtil.fromElm('move-confirm'), {'files': files, 'to': encodeURIComponent(to)});
    },
    show_rename: function (file_path) {
        DomUtil.fillVal(FileOps.filename(file_path), 'rename-filename');
        Modal.icon_show(BrowseActions.getIcon("rename"), "Rename File", DomUtil.fromElm('rename-file'), {'where': file_path, 'action': FileOps.do_rename});
    },
    show_delete: function (file_path, is_folder) {
        var f = is_folder ? "Folder" : "File";
        DomUtil.fillVal(FileOps.filename(file_path).escapeHTML(), 'delete-filename');
        DomUtil.fillVal(f, 'delete-file-folder');
        DomUtil.fillVal("the", "delete-pronoun");

        var icon = is_folder ? BrowseActions.getIcon('delete_folder') : BrowseActions.getIcon('delete');
        Modal.icon_show(icon, "Delete " + f + "?", DomUtil.fromElm('delete-file'), {'where': file_path, 'action': FileOps.do_delete, 'folder': is_folder});
    },

    show_bulk_delete: function (files) {
        var profile = Browse.profile_files(files);
        var file_summary = Browse.profile_summary(profile);

        DomUtil.fillVal("", "delete-pronoun");
        DomUtil.fillVal("", 'delete-file-folder');

        DomUtil.fillVal(FileOps.filename(file_summary), 'delete-filename');
        Modal.icon_show(BrowseActions.getIcon('delete'), "Delete " + file_summary + "?", DomUtil.fromElm('delete-file'), {'files': files, 'action': FileOps.do_bulk_delete});
    },

    show_purge: function (file_path, is_folder) {
        var filename = FileOps.filename(file_path).escapeHTML();
        var f = is_folder ? "Folder" : "File";
        DomUtil.fillVal("\"" + filename + "\"", 'purge-filename');
        DomUtil.fillVal(f, 'purge-file-folder');
        DomUtil.fillVal("Permanently Delete " + f, 'purge-action-text');

        Modal.icon_show(BrowseActions.getIcon('purge'), "Permanently Delete the " + f + " \"" +  filename + "\"?", DomUtil.fromElm('purge-file'), {'where': file_path, 'action': FileOps.do_purge, 'folder': is_folder});
    },

    show_bulk_purge: function (files) {
        var profile = Browse.profile_files(files);
        var file_summary = Browse.profile_summary(profile);

        DomUtil.fillVal(file_summary, 'purge-filename');
        DomUtil.fillVal("Permanently Delete " + file_summary, 'purge-action-text');

        Modal.icon_show(BrowseActions.getIcon('purge_bulk'), "Permanently Delete " + files.length + " items...", DomUtil.fromElm('purge-file'), {'files': files, 'action': FileOps.do_bulk_purge});
    },

    show_bulk_restore: function (files) {
        var profile = Browse.profile_files(files);
        var file_summary = Browse.profile_summary(profile);

        DomUtil.fillVal(file_summary, 'restore-filename');
        DomUtil.fillVal("Restore " + file_summary, 'restore-action-text');

        Modal.icon_show(BrowseActions.getIcon("restore_bulk"), "Restore " + files.length + " Items...", DomUtil.fromElm('restore-file'), {'files': files, 'action': FileOps.do_bulk_restore});
    },

    show_new_folder: function (file_path) {
        Modal.show("Create Folder", DomUtil.fromElm('new-folder'), {'where': file_path, 'action': FileOps.do_new_folder});
    },
    show_upload: function (folder_path, force_reload) {
        folder_path = folder_path || Browse.current_path;
        var folder_name = FileOps.filename(folder_path);
        DomUtil.fillVal(folder_name, 'upload-dest');
        Modal.icon_show("page_white_get", "Upload to '" + folder_name + "'", DomUtil.fromElm('advanced-upload-modal'), {'where': folder_path, 'action': FileOps.do_upload}, false, 500, FileQueue.numShown() && !force_reload); // item_focus=false, modal_width=600
        if (FileQueue.empty()) {
            Upload.set_dest(Util.normPath(decodeURIComponent(folder_path)));
        }
        if (!FileQueue.numShown() || force_reload) {
            Upload.init(true); // late_game=true
        }
        InlineUploadStatus.hide();
    },
    show_basic_upload: function (folder_path) {
        DomUtil.fillVal(FileOps.filename(folder_path), 'upload-dest');

        Modal.icon_show(BrowseActions.getIcon("upload"), "Upload", $('basic-upload-modal'), {}, false, 600);
    },
    show_undelete: function (file) {
        DomUtil.fillVal(file.filename.escapeHTML(), "undelete-filename");
        DomUtil.fillVal(file.ago, "undelete-when");

        var icon = Sprite.make(file.icon, {});
        icon.addClassName("link-img");
        icon.style.backgroundColor = "transparent";

        var link = "/revisions" + file.where + "?undelete=1";
        $$(".undelete-icon").invoke("update", icon);
        $$(".undelete-other-versions")[0].href = link;
        $$(".undelete-link")[0].href = link;

        $("undelete-form").action = "/revisions" + file.where;

        Modal.icon_show(BrowseActions.getIcon("undelete"), "Undelete the File \"" + file.filename.escapeHTML() + "\"?", $("undelete-modal"), {'file': file});
    },

    submit_undelete: function (e) {
        var form = $("undelete-form");
        Forms.ajax_submit(form, false,
            function () {
                Modal.hide();
                Notify.ServerSuccess("'" + Modal.vars.file.filename + "' restored successfully.");
                Browse.reload(Browse.current_path, true);
            },
            function () {
                Notify.ServerError("Unable to restore " + Modal.vars.file.filename);
            },
            e.target);
        return false;
    },
    do_copy: function (from, to) {
        from = from || Modal.vars.where;
        to = to || Modal.vars.selected_path;

        var file = Browse.find_file(Modal.vars.where);
        assert(file, "Trying to copy a file we couldn't find.");
        FileOps.do_bulk_copy([file], to);
    },

    do_bulk_copy: function (files, to) {

        files = files || Modal.vars.files;
        assert(files.length > 0, "Tried to copy 0 files");

        to = to || Modal.vars.selected_path;
        to = decodeURIComponent(to);

        if (!to) {
            Notify.ServerError("You should select a destination for the file.");
            return;
        }

        var copying_to_here = false;
        var got_dir = false;
        for (var i = 0; i < files.length; i += 1) {
            if (files[i].dir && Util.normDir(decodeURIComponent(to)).indexOf(Util.normDir(decodeURIComponent(files[i].where))) === 0) {
                Notify.ServerError("You cannot copy a folder into itself.");
                return;
            }

            got_dir = got_dir || files[i].dir;

            if (decodeURIComponent(files[i].where) == Util.normPath(to) + "/" + FileOps.filename(files[i].where)) {
                copying_to_here = true;
            }
        }

        var file_paths = files.collect(function (file) {
            return decodeURIComponent(file.where);
        });

        var dbr = new Ajax.DBRequest("/cmd/copy", {
            parameters: {files: file_paths, to_path: to },
            job: true,
            progress_text: "Copying...",
            onSuccess: function (req) {
                if (copying_to_here) {
                    Browse.reload(Browse.current_path, true);
                }

                Notify.ServerSuccess("Copied " + Util.plural(file_paths.length, "item") + " successfully.");

                if (got_dir) {
                    TreeView.reset();
                }
            },
            cleanUp: function (req) {
            }
        });
    },
    do_move: function (from, to) {
        from = from || Modal.vars.where;
        to = to || Modal.vars.selected_path;

        var file = Browse.find_file(Modal.vars.where);
        assert(file, "Trying to move a file we couldn't find.");
        FileOps.do_bulk_move([file], to);
    },

    do_bulk_move: function (files, to) {

        files = files || Modal.vars.files;
        if (!files) {
            return;
        }
        assert(files.length > 0, "Tried to move 0 files");

        to = to || Modal.vars.selected_path;
        to = decodeURIComponent(to);

        if (!to) {
            Notify.ServerError("You should select a destination for the file.");
            return;
        }

        var copying_to_here = false;
        for (var i = 0; i < files.length; i += 1) {
            if (files[i].dir && Util.normDir(decodeURIComponent(to)).indexOf(Util.normDir(decodeURIComponent(files[i].where))) === 0) {
                Notify.ServerError("You cannot move a folder into itself.");
                return;
            }
        }

        var file_paths = files.collect(function (file) {
            return decodeURIComponent(file.where);
        });

        var dbr = new Ajax.DBRequest("/cmd/move", {
            parameters: {files: file_paths, to_path: to},
            job: true,
            progress_text: "Moving...",
            onSuccess: function (req) {
                Notify.ServerSuccess("Moved " + Util.plural(file_paths.length, "item") + " successfully.");
            },
            cleanUp: function (req) {
                Browse.reload(Browse.current_path, true);
            }
        });
    },
    do_rename: function (to_path) {
        var from = Modal.vars.where;
        var to = encodeURIComponent(to_path);
        var dbr = new Ajax.DBRequest("/cmd/rename" + from + "?to_path=" + to, {
            onSuccess: function (req) {
                var parts = req.responseText.split(":");
                var new_where = parts[0];
                var new_hash = parts[1];
                var new_name = FileOps.filename(new_where);
                Notify.ServerSuccess("Renamed '" + FileOps.filename(from).snippet() + "' to '" + new_name.snippet() + "' successfully.");
                var file = Browse.find_file(from);
                file.rename(new_where, file.dir, new_hash);
            },
            cleanUp: function (req) {
            }
        });
    },

    do_bulk_delete: function (files) {
        files = files || Modal.vars.files;

        assert(files.length > 0, "Tried to delete 0 files");

        var file_paths = files.collect(function (file) {
            return decodeURIComponent(file.where);
        });

        var from = Modal.vars.where;
        var folder_delete = Modal.vars.folder;
        var dbr = new Ajax.DBRequest("/cmd/delete", {
            parameters: {files: file_paths},
            job: true,
            progress_text: "Deleting...",
            onSuccess: function (req) {
                Notify.ServerSuccess("Deleted " + Util.plural(file_paths.length, "item") + " successfully.");
                TreeView.reset();
            },
            cleanUp: function (req) {
                Browse.reload(Browse.current_path, true);
            }
        });
    },

    do_delete: function () {
        var file = Browse.find_file(Modal.vars.where);
        assert(file, "Trying to delete a file we couldn't find.");

        FileOps.do_bulk_delete([file]);
    },

    do_purge: function () {
        var file = Browse.find_file(Modal.vars.where);
        assert(file, "Trying to purge a file we couldn't find.");

        FileOps.do_bulk_purge([file]);
    },

    do_bulk_purge: function (files) {
        files = files || Modal.vars.files;
        assert(files.length > 0, "Tried to purge 0 files");

        var file_paths = files.collect(function (file) {
            return decodeURIComponent(file.where);
        });

        var from = Modal.vars.where;
        var folder_delete = Modal.vars.folder;
        var dbr = new Ajax.DBRequest("/cmd/purge", {
            parameters: {files: file_paths},
            job: true,
            progress_text: "Deleting...",
            onSuccess: function (req) {
                Notify.ServerSuccess("Permanently deleted " + Util.plural(file_paths.length, "item") + " successfully.");
                Browse.reload(Browse.current_path, true);
                TreeView.reset();
            },
            cleanUp: function (req) {
            }
        });
    },

    do_bulk_restore: function (files) {
        files = files || Modal.vars.files;
        assert(files.length > 0, "Tried to restore 0 files");

        var file_paths = files.collect(function (file) {
            return decodeURIComponent(file.where);
        });

        var from = Modal.vars.where;
        var folder_delete = Modal.vars.folder;
        var dbr = new Ajax.DBRequest("/cmd/restore", {
            parameters: {files: file_paths},
            job: true,
            progress_text: "Restoring...",
            onSuccess: function (req) {
                Notify.ServerSuccess("Restored " + Util.plural(file_paths.length, "item") + " successfully.");
                Browse.reload(Browse.current_path, true);
                TreeView.reset();
            },
            cleanUp: function (req) {
            }
        });
    },

    do_upload: function () {

        $('dest-folder').value = decodeURIComponent(Modal.vars.where);

        // submit the form, the loaded page will do the right thing
        $('upload-form').submit();

        frames['upload-frame'].onload =
            function (e) {
                var text = e.target.documentElement.textContent;
                if (text == 'winner!') {
                    Browse.reload();
                    Notify.ServerSuccess("Uploaded file successfully");
                } else {
                    Notify.ServerError();
                }
            };

    },

    do_bulk_download: function (files) {
        var f = new Element("form", {'action': "https://" + Constants.BLOCK_CLUSTER + "/zip_batch", 'method': 'post'});

        for (var i = 0; i < files.length; i += 1) {
            Forms.add_vars(f, {'files': decodeURIComponent(files[i].where) });
        }

        Forms.add_vars(f, {'parent_path': decodeURIComponent(Browse.current_path), 'w': Browse.current_path_hash, 'user_id': Constants.uid });

        $(document.body).insert(f);
        f.submit();

    },
    inplace_new_folder: function (where) {
        if (Browse.in_placer && Browse.in_placer.new_folder) {
            return;
        }

        var name = "New Folder";
        if (where.charAt(where.length - 1) != "/") {
            where += "/";
        }

        // make a placeholder
        Browse.hide_message();

        var f = new BrowseFile('folder', where, "/browse2" + where + name, name, name, false, 0, "", Util.ts(), "share zipped_dl upload copy_folder move_folder rename_folder delete_folder", "", true, true, true);

        Browse.resort();

        f.div.scrollTo();
        f.edit(true); // new_folder=true
    }
};

var Forms = {
    submitOnlyOnce: function () {
        var ret = Forms.submitted !== true;
        Forms.submitted = true;

        return ret;
    },
    disable: function (me) {
    	if (me) {
    	    setTimeout(function () {
                me.disabled = true;
            }, 0);
    	}
    },
    enable: function (me) {
        if (me) {
    	    setTimeout(function () {
                me.disabled = false;
            }, 0);
    	}
    },
    clearInput: function (elm, value) {
        elm = $(elm);
        if (elm.value == value) {
            elm.value = "";
            elm.style.color = '#444444';
        }
    },
    add_vars: function (form, vars) {
        form = $(form);
        for (var k in vars) {
            form.insert(new Element('input', {'type': 'hidden', 'name': k, 'value': vars[k]}));
        }
    },
    mirror: function (elm1, elm2) {
        elm1 = $(elm1);
        elm2 = $(elm2);

        function mirror_it(first, second) {
            second.setValue($F(first));
            second.fire('db:value_change');
        }

        if (elm1 && elm2) {
            elm1.observe('keyup', function () {
                mirror_it(elm1, elm2);
            });
            elm1.observe('db:autocompleted', function () {
                mirror_it(elm1, elm2);
            });

            elm2.observe('keyup', function () {
                mirror_it(elm2, elm1);
            });
            elm2.observe('db:autocompleted', function () {
                mirror_it(elm2, elm1);
            });
        }
    },
    collect_form_vars: function (form) {
        form = form || $(document.body);

        var elms = form.select("input").concat(form.select("textarea")).concat(form.select("select"));

        var out = {};
        for (var i = 0; i < elms.length; i++) {
            var elm = elms[i];

            if (elm.name && elm.name != 't') { // ignore any token vars in the form -- js has its own
                var value = elm.getValue();
                if (value) {
                    if (typeof(value) != "string") {
                        // must be an array, turn into a comma-separated string
                        value = value.join(",");
                    }
                    out[elm.name] = value;
                }
            }
        }

        return out;
    },
    ajax_submit: function (form, url, success_callback, fail_callback, button, more_vars) {
        // one submit at a time
        if (form.ajax_submitted) {
            return false;
        }
        form.ajax_submitted = true;

        var inputs = form.select(".suggestion-input").each(function (elm) {
            SuggestionInput.blank(elm.identify())();
        });

        if (button) {
            button = $(button);
            var img = new Element("img", {src: '/static/images/icons/ajax-loading-small.gif'});
            img.addClassName("text-img ajax_submit_loading");
            button.insert({before: img});
        }

        var params = Forms.collect_form_vars(form);
        if (more_vars) {
            params = $H(params).update(more_vars).toObject();
        }

        var dbr = new Ajax.DBRequest(url || form.action, {
            noAutonotify: true,
            parameters: params,
            onSuccess: function (req) {
                if (success_callback && typeof(success_callback) == "function") {
                    success_callback(req);
                }
            },
            onFailure: function (req) {
                if (req) {
                    if (req.responseText.indexOf("err:") === 0) {
                        var error = req.responseText.substr(4);

                        if (error.indexOf("{") === 0) {
                            var error_dict = error.evalJSON(true); //sanitize=true
                            Forms.fill_errors(form, error_dict);
                        } else {
                            Notify.ServerError(error);
                        }
                    } else {
                        Notify.ServerError();
                    }

                    if (fail_callback && typeof(fail_callback) == "function") {
                        fail_callback(req);
                    }
                }
            },
            onComplete: function (req) {
                form.ajax_submitted = false;
                $$(".ajax_submit_loading").each(function (elm) {
                    Util.yank(elm);
                });
            }
        });

        return false;// false;
    },
    clear_errors: function (form) {
        form = form || $(document.body);
        form.select('.error-removable').invoke('remove');
    },
    fill_errors: function (form, error_dict) {
        error_dict = error_dict || {};
        form = form || $(document.body);

        // we're inserting an error span + br right before each element
        Forms.clear_errors(form);
        for (var field in error_dict) {
            var elm = form.down("input[name='" + field + "']") || form.down("textarea[name='" + field + "']") || form.down("select[name='" + field + "']");
            if (elm) {
                var br = new Element('br', {'class': 'error-removable'});
                var error = new Element('span', {'class': 'error-message error-removable'});
                error.update(error_dict[field]);

                elm.insert({before: error});
                elm.insert({before: br});
            }
        }
    },
    value: function (name) {
        var inputs = $$('input[name="' + name + '"]');
        var val = null;

        var l = inputs.length;
        for (var i = 0; i < l; i++) {
            val = $(inputs[i]).getValue() || val;
        }

        return val;
    }
};

var Upgrade = {
    card_toggle: function (chosen) {
        return function (type) {
            var type_elm = $(type);

            if (type == chosen || !chosen) {
                type_elm.removeClassName("cc-icon-off");
            }
            else {
                type_elm.addClassName("cc-icon-off");
            }
        };
    },
    highlightCardtype: function () {
        var ccn = $('ccn');

        if (Upgrade.last_val == ccn.value) {
            return;
        }

        Upgrade.last_val = ccn.value;

        var ccnum = ccn.value;
        var first_two = ccnum.substr(0, 2);
        var all = $A(['visa', 'mastercard', 'amex']);
        var choice = null;

        if (ccnum.charAt(0) == '4') {
            choice = 'visa';
        }
        else if (first_two == '34' || first_two == '37') {
            choice = 'amex';
        }
        else if (parseInt(first_two, 10) >= 51 && parseInt(first_two, 10) <= 55) {
            choice = 'mastercard';
        }

        all.each(Upgrade.card_toggle(choice));
    },
    runCardHighlighter: function () {
        setInterval(Upgrade.highlightCardtype, 200);
    },
    highlightPlan: function () {
        var plans = $A(['fifty-plan', '100-plan', '250-plan', 'free-plan']);
        var checked = plans.map(Util.scry).find(function (x) {
            if (x) {
                return x.checked;
            }
        });

        if (Upgrade.last_checked == checked) {
            return;
        }

        if (Upgrade.last_checked) {
            Util.scry(Upgrade.last_checked.id + "-div").removeClassName("payment-option-selected");
        }

        if (checked) {
            Util.scry(checked.id + "-div").addClassName("payment-option-selected");
        }

        Upgrade.last_checked = checked;
    },
    enableNext: function () {
        var elm = $("next-button");
        elm.enable();
        elm.removeClassName("disabled-button");
    },
    disableNext: function () {
        var elm = $("next-button");
        elm.disable();
        elm.addClassName("disabled-button");
    },
    runPlanHighlighter: function () {
        setInterval(Upgrade.highlightPlan, 100);
    },
    showPlanInfo: function (plan) {
        var info = { "50-plan": "It's 50 GB", "100-plan": "It's 100 GB", "250-plan": "It's 250 GB", "free-plan": "It's free"};
        Util.scry("plan-specific").update(info[plan.id]);
    }
};

var Home = {
    showScreencast: function (container_id, auto_play, width) {
        width = width || 532;
        var height = (360 / 640) * width;
        height = parseInt(height, 10);

        var params = { 'allowfullscreen': 'true',
                       'wmode'          : 'transparent' };

        var flashvars = { 'file'           : 'http://scast.s3.amazonaws.com/cc/dropbox_intro.flv',
                          'skin'           : '/static/swf/bekle.swf',
                          'controlbar'     : 'over',
                          'image'          : '/static/images/cc_endframe.jpg' };
        if (auto_play) {
            flashvars.autostart = 'true';
        }

        var div = new Element("div", {'id': 'commoncraft-embed', 'style': 'display: inline-block; border:1px solid #adcfea;background:#fff;'});

        $(container_id).update(div);
        swfobject.embedSWF('/static/swf/player-licensed.swf', 'commoncraft-embed', width.toString(), height.toString(), '9', false, flashvars, params);

        /* (12/9/09 albert) I'll probably refactor this once it becomes clear
         * that we need it in multiple places
         */
        var req = new Ajax.Request('/ajax_mc_log/commoncraft_views');

    },
    showFeedback: function (e) {
        if (e) {
            Event.stop(e);
        }

        Modal.icon_show("comments", "Tell Us What You Think", $('feedback-div'), {icon: 'information'}, $('feedback_textarea'));
        return false;
    },
    hide: function (elm, value) {
        $(elm).up('div').hide();
        var request = new Ajax.DBRequest("/hide/" + value);
    }
};

var Install = {
    pingForLinkedHost: function (share_path) {
        var request = new Ajax.Request("/host_linked",
            {method: 'get',
            onSuccess: function () {
                location.href = "/share" + share_path;
            },
            onFailure: function () {
                setTimeout(Install.pingForLinkedHost.curry(share_path), 3000);
            }
        });
    }
};

var Tour = {
    pages: {},
    loading: false,
    register: function () {
        var i = 1;
        $$(".tour-page a").each(function (elm) {
            elm.href = "#" + i;
            i += 1;
        });

        var button = $$(".abutton")[0];
        if (button) {
            button.href = "#" + (Tour.current_page + 1);
        }

        Tour.interval = setInterval(Tour.check_url, 100);
    },
    load: function (page_num, event) {
        if (Tour.loading) {
            return;
        }

        page_num = page_num <= Tour.page_count && page_num > 0 ? page_num : 1;
        Tour.current_page = page_num;

        Tour.select_tab(page_num);
        Tour.loading = true;

        if (Tour.pages[page_num]) {
            Tour.show_page(page_num);
        } else {
            Feed.showLoading(false, 'tour-content', true); // just_icon=true
            var db_pro_suffix = Tour.db_pro ? "?db_pro" : "";
            var request = new Ajax.Request("/tour/" + page_num + db_pro_suffix, {
                method: 'get',
                onSuccess: function (req) {
                    Tour.pages[page_num] = req.responseText;
                    Tour.show_page(page_num);
                    Feed.hideLoading();
                }
            });
        }
    },
    select_tab: function (page_num) {
        var img;
        $$("a.selected").each(function (elm) {
            elm.removeClassName("selected");
            img = elm.down("img.tour-tab-rounded").remove();
        });

        var button = $$("#tour-tabs ul li a")[page_num - 1];
        button.blur();

        button.addClassName("selected");
        button.insert(img);
    },
    show_page: function (page_num) {
        $("tour-content").update(Tour.pages[page_num]);

        if (page_num < Tour.page_count) {
            var div = new Element("div", {'style': 'text-align: right;'});
            var a = new Element("a", {'href': '#' + (page_num + 1)});
            a.update("Next &raquo;");
            a.addClassName("abutton");
            div.update(a);
            $("tour-content").insert(div);
        }
        Tour.loading = false;
    },
    check_url: function () {

        var hash = Util.url_hash();
        if (!hash || Tour.loading) {
            return;
        }

        hash = parseInt(hash, 10);
        if (hash != Tour.current_page) {
            Tour.load(hash);
        }
    }
};

var Dropdown = {
    init: function () {
        $$("#tabs-container > ul > li").each(
            function (elm) {
                elm.observe("mouseenter", Dropdown.over);
                elm.observe("mouseleave", Dropdown.out);
            }
        );
    },

    over: function (event) {
        clearTimeout(Dropdown.timeout);
        $$("#tabs-container > ul > li.hover").invoke("removeClassName", "hover");
        var elm = $(event.target);
        if (!elm.match("#tabs-container > ul > li")) {
            elm = elm.up("#tabs-container > ul > li");
        }
        elm.addClassName("hover");
    },

    out: function (event) {
        var elm = $(event.target);
        if (!elm.match("#tabs-container > ul > li")) {
            elm = elm.up("#tabs-container > ul > li");
        }
        Dropdown.timeout = setTimeout(function () {
                elm.removeClassName("hover");
            }, 300);

    }
};


var LiveSearch = {
    search: function (search_string, update_elm, action, callbacks, shortened) {
        search_string = search_string.strip();

        if (search_string.length < 3) {
            $(update_elm).update("");
            if (callbacks.onEmpty) {
                callbacks.onEmpty(search_string);
            }
        }

        else {
            var params = {'search_string': search_string,
                          'short': shortened ? 1 : '' };

            var request = new Ajax.Request(action, {
                parameters: params,
                method: 'get',
                onSuccess: function (req) {
                    var text = req.responseText.strip();

                    if (!text) {
                        if (callbacks.onEmpty) {
                            callbacks.onEmpty(search_string);
                        }
                        return;
                    }

                    $(update_elm).update(req.responseText);
                    LiveSearch.highlight(update_elm, search_string);
                    if (callbacks.onComplete) {
                        callbacks.onComplete(search_string);
                    }
                }
            });
        }
    },

    highlight: function (elm, search) {
        var search_parts = search.split(" ");

        search_parts.each(
            function (search_string) {
                if (search_string.length < 4) {
                    return;
                }
                var regx = new RegExp(RegExp.escape(search_string), "i");
                elm = $(elm);
                $$('.livesearch_result_a').each(
                    function (elm) {
                        elm.innerHTML = elm.innerHTML.gsub(regx, function (match) {
                            return '<span class=\'highlight\'>' + match[0] + '</span>';
                        });
                    }
                );
                $$('.livesearch_result_p').each(
                    function (elm) {
                        elm.innerHTML = elm.innerHTML.stripTags().gsub(regx, function (match) {
                            return '<span class=\'highlight\'>' + match[0] + '</span>';
                        });
                    }
                );
            }
        );
    },
    MAX_RESULTS: 10
};


var Email = {
    mailto: function (link, name, domain, body) {
        if (!domain) {
            domain = "dropbox.com";
        }

        link.href = "mailto:" + name + "@" + domain;

        if (body) {
            link.href += "?body=" + body;
        }

        link.onMouseover = null;
    }
};

var SuggestionInput = {
    register: function (elm) {
        elm = $(elm);
        var rel_form = elm.up('form');

        var init_val = elm.getValue();
        if (init_val === '' || init_val === elm.title) {
            elm.setValue(elm.title); // title holds the default value
            elm.defaulted = true;
        }
        else {
            elm.defaulted = false;
            elm.addClassName('suggestion-input-unfaded');
        }

        elm.observe('blur', SuggestionInput.blur);
        elm.observe('focus', SuggestionInput.focus);
        elm.observe('db:value_change', SuggestionInput.focus);

        if (rel_form) {
            if (!elm.id) {
                elm.id = "r_elm_id_" + Math.random().toString();
            }

            rel_form.observe('submit', SuggestionInput.blank(elm.id));
        }
    },
    register_all: function () {
        $$('.suggestion-input').each(SuggestionInput.register);
    },
    blank: function (elm_id) {
        return function () {
            var elm = $(elm_id);
            if (!elm) {
                return;
            }

            if (elm && elm.defaulted) {
                elm.setValue('');
            }
        };
    },
    do_blank: function (elm_id) {
        SuggestionInput.blank(elm_id)();
    },
    clear: function (elm_id) {
        var fake_event = {'target': elm_id};
        SuggestionInput.focus(fake_event);
    },
    focus: function (e) {
        var elm = $(e.target);
        if (!elm) {
            return;
        }

        if (elm.defaulted) {
            elm.addClassName('suggestion-input-unfaded');
            elm.setValue('');
            elm.defaulted = false;
        }
    },
    blur: function (e) {
        var elm = $(e.target);
        if (!elm) {
            return;
        }

        if (elm.getValue() === '') {
            elm.removeClassName('suggestion-input-unfaded');
            elm.setValue(elm.title);
            elm.defaulted = true;
        }
    },
    reset: function (id) {
        var elm = $(id);
        if (!elm) {
            return;
        }
        elm.removeClassName('suggestion-input-unfaded');
        elm.setValue(elm.title);
        elm.defaulted = true;
    }
};
document.observe('dom:loaded', SuggestionInput.register_all);

var HoverIconSwap = {
    register_all: function () {
        $$('.background-icon').each(HoverIconSwap.register);
    },
    register: function (elm) {
        elm = $(elm);

        elm.db_observe('mouseenter', HoverIconSwap.mouseenter);
        elm.db_observe('mouseleave', HoverIconSwap.mouseleave);
    },
    mouseenter: function (e, elm) {
        elm.addClassName("hover_swap");
    },
    mouseleave: function (e, elm) {
        elm.removeClassName("hover_swap");
    },
    getFileName: function (elm) {
        var fileName = elm.src.split("/");
        return fileName[fileName.length - 1];
    }
};
document.observe("dom:loaded", HoverIconSwap.register_all);


var BrowseStyleRows = {
    register_all: function () {
        $$('.bs-row').each(BrowseStyleRows.register);
        Event.observe(document, 'click', BrowseStyleRows.kill_current);
    },
    register: function (elm) {
        elm = $(elm);

        elm.db_observe('mouseover', BrowseStyleRows.mouseover);
        elm.db_observe('mouseout', BrowseStyleRows.mouseout);
        elm.db_observe('click', BrowseStyleRows.click);

    },
    mouseover: function (e, source) {
        source.addClassName("hover");
    },
    mouseout: function (e, source) {
        source.removeClassName("hover");
    },
    click: function (e, source) {
        if (e.target.tagName == 'A') {
            // links should be clickable without opening the row
            return;
        }

        Event.stop(e);
        BrowseStyleRows.kill_current(false);
        var target = $(e.target);
        if (!target.match(".bs-actions-list *")) {
            source.addClassName("selected");
        }

        var parent = target.hasClassName("bs-row") ? target : target.up(".bs-row");
        if (Util.ie6) {
            parent.down(".bs-actions-list").style.position = "absolute";
        }
        parent.style.zIndex = 9;
    },
    kill_current: function (e) {
        $$(".bs-row.selected").each(function (elm) {
            elm.removeClassName("selected");
            elm.style.zIndex = "";
        });
    }
};

var EventBubble = {
    make: function (content) {
        var template = '<table class="ebubble"><tr><td class="tl"></td><td class="t"></td><td class="tr"></td></tr><tr><td class="l"></td><td class="c">#{content}</td><td class="r"></td></tr><tr><td class="bl"></td><td class="b"><img src="/static/images/events_bubble_tail.gif" alt="" class="events_bubble_tail"/></td><td class="br"></td></tr></table>';
        return template.interpolate({'content': content});
    }
};

var HotButton = {
    make: function (content) {
        var anchor = new Element("a");
        anchor.addClassName("hotbutton");

        var wrapper = new Element("span");
        wrapper.addClassName("hotbutton-content");
        wrapper.update(content);

        anchor.update(wrapper);
        return HotButton.register(anchor);
    },

    register: function (anchor) {

        anchor.observe("mouseenter", function () {
            HotButton.mouseenter(anchor);
        });

        anchor.observe("mouseleave", function () {
            HotButton.mouseleave(anchor);
        });

        anchor.observe("mousedown", function (e) {
            HotButton.mousedown(e, anchor);
        });

        anchor.observe("mouseup", function () {
            HotButton.mouseup(anchor);
        });

        Util.disableSelection(anchor);
        return anchor;
    },

    mouseenter: function (anchor) {
        anchor.addClassName("hover");
        anchor.style.zIndex = 1;
    },

    mouseleave: function (anchor) {
        anchor.removeClassName("hover");
        anchor.removeClassName("down");
        anchor.removeClassName("hover_swap");
        anchor.style.zIndex = 0;
    },

    mousedown: function (e, anchor) {
        anchor.addClassName("down");
        anchor.addClassName("hover_swap");
        Event.stop(e);
    },

    mouseup: function (anchor) {
        anchor.removeClassName("down");
        anchor.removeClassName("hover_swap");
    }
};

var ActAsBlock = {
    elm_list: ["margin-left", "margin-right", "padding-left", "padding-right", "border-left-width", "border-right-width"],
    parent_list: ["padding-left", "padding-right", "border-left-width", "border-right-width"],

    register: function (e, elm) {
        elm = elm || document.body;
        var elms = $(elm).getElementsByClassName("act_as_block");

        for (var i = 0; i < elms.length; i = i + 1) {
            ActAsBlock.resize(elms[i]);
        }

    },

    resize: function (elm) {
        elm = $(elm);

        var parent = elm.up();
        var elm_total = Util.sumStyles(elm, ActAsBlock.elm_list);
        var parent_total = Util.sumStyles(parent, ActAsBlock.parent_list);

        elm.style.width = "1px";

        var width = (parent.getWidth() - elm_total - parent_total);

        if (width > 0) {
            elm.style.width = width + "px";
        }
    }
};
Event.observe(window, 'load', ActAsBlock.register);

var Inbox = {
    overQuotaModal: function (button, folder_name, used, free) {
        Modal.show('Quota Warning', $('overquota-modal'));

        var modal_content = $('modal-content');

        $$('.shared-folder-name').invoke("update", folder_name);
        $$('.shared-folder-size').invoke("update", used);

        var submit = modal_content.getElementsByClassName('joinbutton');
        submit[0].onclick = function () {
            button.onclick = null;
            button.click();
        };
        submit[0].value = "Join " + folder_name;

        return false;
    }
};

var Downloading = {
    registerAll: function () {
        if (Prototype.Browser.IE) {
            $$('.downloading-link').each(Downloading.register);
        }
    },

    register: function (elm) {
        elm = $(elm);
        elm.observe("click", Downloading.clicked);
    },

    clicked: function (e) {
        Event.stop(e);
        var elm = $(e.target);
        if (elm.nodeName === "SPAN") {
            elm = elm.up("a");
        }
        var args = elm.href.split("?").last();
        window.location = "/download?" + args;
        setTimeout(function () {
            window.location = "/downloading?" + args;
        }, 4000);
    }
};
document.observe("dom:loaded", Downloading.registerAll);

/*global Template, Form*/
var Referral = {
    select_all: 1,
    show_login_modal: function (vars) {
        Modal.show("Invite Contacts From Your Email Address Book", $("cli-login"), vars || {});
    },

    get_selected_emails: function () {
        var emails = [];
        $$("#contact-list input").each(function (elm) {
                if (elm.checked) {
                    emails.push(elm.value);
                }
            }
        );
        return emails.join(", ");
    },
    send_invites: function () {
        var emails = Referral.get_selected_emails();
        Invitations.do_send(emails);
        Modal.hide();
    },
    show_contact_info_modal: function () {
        Modal.show("Invite Contacts From Your Email Account", DomUtil.fromElm('contact-info-modal'),
            {action: Referral.fetch_contacts});
        $("email-prefix").focus();
        return false;
    },
    show_error: function (error) {
        Referral.hide_captcha();
        $('contact-info-error').update(error);
        $('contact-info-error').show();
    },
    show_captcha: function (info) {
        Referral.hide_captcha();
        info = info.evalJSON(true); // sanitize=true
        $('captcha-row').hide();

        $('contact-info-captcha-image').src = info.image.replace("http://", "https://");
        $('contact-info-captcha-image').hide();
        Element.observe('contact-info-captcha-image', 'load', function () {
            $('contact-info-captcha-image').show();
        });

        $('contact-info-captcha-id').value = info.id;
        $('contact-info-captcha-answer').value = '';

        $('captcha-row').show();
        $('captcha-answer-row').show();
        $('contact-info-error').update('Captcha required');
        $('contact-info-error').show();
    },
    hide_captcha: function () {
        $('contact-info-captcha-id').value = '';
        $('contact-info-captcha-answer').value = '';
        $('captcha-row').hide();
        $('captcha-answer-row').hide();
    },
    parse_contacts: function (cstr) {
        cstr = cstr.substr(9); // drop the leading "contacts:"
        return cstr;
    },

    fetch_contacts: function (e) {
        Event.stop(e);
        var username = $F('username');
        var provider = "";
        if (username.indexOf("@") > 0) {
            var split = username.split("@");
            username = split.first();
            provider = split.last();
        }
        Referral.fetch_and_show_contacts(e, username, provider, $F('email-password'),
                                         $F('contact-info-captcha-id'), $F('contact-info-captcha-answer'));
    },
    fetch_and_show_contacts: function (e, email, provider, password, captcha_id, captcha_answer) {
        if (e) {
            Event.stop(e);
        }
        $('contact-info-error').hide();
        Referral.show_loading_modal(provider.split(".")[0]);

        email = provider !== "" ? email + "@" + provider : email;
        var params = {email: email, password: password, select_all: Referral.select_all ? 1 : 0};
        if (captcha_id && captcha_answer) {
            Object.extend(params, {'captcha_id': captcha_id, 'captcha_answer': captcha_answer});
        }

        var dbr = new Ajax.DBRequest("/import_contacts", {
            noAutonotify: true,
            parameters: params,
            onSuccess: function (req) {
                contacts = Referral.parse_contacts(req.responseText);
                Referral.show_select_contacts(contacts);
            },
            onFailure: function (req) {
                if (req.responseText.indexOf('err:') === 0) {
                    var err = req.responseText.substr(4);

                    if (err.indexOf('captcha:') !== 0) {
                        if (Referral.hide_on_error) {
                            Modal.hide();
                        } else {
                            Referral.show_login_modal();
                        }
                        Referral.show_error(err);
                    } else {
                        if (Referral.hide_on_error) {
                            Modal.hide();
                        } else {
                            Referral.show_login_modal();
                        }
                        Referral.show_captcha(err.substr(8));
                    }
                } else {
                    Referral.show_error("Unexpected server error.");
                    if (Referral.hide_on_error) {
                        Modal.hide();
                    } else {
                        Referral.show_login_modal();
                    }
                }
            },
            cleanUp: function () {
                $("modal-title").show();
            }
        });
    },
    show_loading_modal: function (provider) {
        var providers_with_images = ["gmail", "yahoo", "aol", "hotmail", "live", "msn"];
        if (providers_with_images.indexOf(provider) > -1) {
            $("email-provider-img").src = "/static/images/referrals_" + provider + ".png";
            $("email-provider-img").show();
        } else {
            $("email-provider-img").hide();
        }

        $("modal-title").hide();
        Modal.show("Loading Contacts", $("loading-contacts-modal"), {}, "", 490);

    },
    show_select_contacts: function (contacts) {
        var max_snip_len = 70;
        if (contacts.length) {

            $('contact-list').innerHTML = contacts;
            SuggestionInput.reset("contact-filter");

            var dropbox_users = $$("#contact-list img").length;

            if (dropbox_users === 0) {
                $("dropbox-users-text").style.visibility = "hidden";
            }

            DomUtil.fillVal($$(".contact-row").length, "num-contacts");
            Referral.contact_container = document.getElementById("contact-list");
            Referral.contact_rows = Referral.contact_container.childNodes;



            for (var i = 0; i < Referral.contact_rows.length; i += 1) {
                var row = Referral.contact_rows[i];
                row.search_text = row.childNodes[1].firstChild.innerHTML + row.childNodes[2].firstChild.innerHTML;
                var checkbox = $(row.firstChild.firstChild);
                checkbox.observe("click", Referral.checkbox_clicked);
            }
            Referral.fresh = true;
            Referral.update_invite_count();


        }
        var modal = !contacts.length ? 'no-contacts-modal' : 'select-contacts-modal';
        var title = !contacts.length ? "Oops! No Contacts Here." : "Choose Contacts";
        Modal.show(title, $(modal), {action: Referral.action }, null, 600);
        Referral.filter_observer = new Form.Element.Observer('contact-filter', 0.5, function (element, value) {
        	Referral.filter(value);
        });
    },

    checkbox_clicked: function (event) {
        Referral.fresh = false;
        Referral.update_invite_count();
    },

    update_invite_count: function (count) {
        if (!count && Referral.contact_rows) {
            count = 0;
            for (var i = 0; i < Referral.contact_rows.length; i += 1) {
                if (Referral.contact_rows[i].firstChild.firstChild.checked) {
                    count += 1;
                }
            }
        }

        $("select-contacts-modal").down("input[type=button]").setValue("Invite " + count + " friend" + (count == 1 ? '' : 's'));
    },

    select_all_contacts: function () {
        $$(".contact-check input").each(function (x) {
            x.checked = true;
        });
        Referral.update_invite_count();
        return false;
    },
    select_no_contacts: function () {
        $$(".contact-check input").each(function (x) {
            x.checked = false;
        });
        Referral.update_invite_count(0);
        return false;
    },
    insert_contacts: function () {
        var emails = [];
        $$(".contact-check").each(function (x) {
            if (x.checked) {
                emails.push(x.value);
            }
        });

        if (emails.length) {
            SuggestionInput.clear('invite-recip');
            var prev = $F('invite-recip');
            if (prev) {
                prev += ', ';
            }
            $('invite-recip').setValue(prev + emails.join(", "));
        }
        Modal.hide();
    },
    filter: function (search_string) {
        if (search_string === Referral.last_search || search_string === "Search" || (Referral.last_search === undefined && search_string === "")) {
            return;
        }

        if (Referral.fresh) {
            Referral.fresh = false;
            Referral.select_no_contacts();
        }

        Referral.last_search = search_string;
        var rows_shown = 0;
        var regx = new RegExp(RegExp.escape(search_string.strip()).split(/[;,\s]+/).join(".*"), "i");
        Referral.contact_container.style.display = "none";

        var i = Referral.contact_rows.length;
        while (i--) {
            var row = Referral.contact_rows[i];
            var style = row.style;

            if (regx.test(row.search_text)) {
                if (rows_shown % 2 === 0) {
                    style.background = "#ffffff";
                } else {
                    style.background = "#f4faff";
                }

                style.display = "";
                rows_shown += 1;
            } else {
                style.display = "none";
            }
        }
        Referral.update_invite_count();
        Referral.contact_container.style.display = "";
    },

    do_submit: function () {
        assert(Referral.action && typeof(Referral.action) == "function", "Finished with contact list importer but have no callback");
        Referral.action();
    },

    do_cancel: function () {
        assert(Referral.cancel_action && typeof(Referral.cancel_action) == "function", "Finished with contact list importer but have no cancel callback");
        Referral.cancel_action();
    },

    hide_warning: function (elm, referral_id) {
        var callback = function () {
            Referral.hide(elm, referral_id);
        };
        Modal.icon_show("group_add", "Remove Referral?", $("referral_warning"), { action: callback });
    },
    hide: function (elm, referral_id) {
        elm = $(elm);
        assert(elm, "Referral elm doesn't exist");
        assert(Util.isNumber(referral_id), "Referral id is not a number");

        Modal.hide();
        var dbr = new Ajax.DBRequest("/account/hide_referral", {
            parameters: { 'referral_id': referral_id },
            onSuccess: function (req) {
                var row = elm.up("tr");
                var anim = new Effect.Fade(row);
            }
        });
    }
};

var Account = {
    referralPages: {},
    referralCurrentPage: -1,
    referralTabClick: function () {
        if (Account.referralCurrentPage != -1) {
            return;
        }
        Account.getReferralsPage(0);
    },
    getReferralsPage: function (num) {
        Account.referralCurrentPage = num;
        if (Account.referralPages[num]) {
            Account.showReferrals(num);
        } else {
            Feed.showLoading(false, $('referrals-container'));
            var dbr = new Ajax.DBRequest("/account/referralspage/" + (num).toString(), {
                onSuccess: function (req) {
                    Account.referralPages[num] = req.responseText;
                    Account.showReferrals(num);
                }
            });
        }
        return false;
    },

    showReferrals: function (num) {
        Feed.hideLoading();
        $("referrals-container").update(Account.referralPages[num]);
    }
};


var LP = {
    interval: 10000,
    slideshow: function () {
        if (LP.slideshow_index === undefined) {
            LP.slideshow_index = 0;
        } else {
            var tablist = $$(".subtab");
            if (tablist.length - 1 <= LP.slideshow_index) {
                LP.slideshow_index = 0;
            } else {
                LP.slideshow_index += 1;
            }
            var tab = tablist[LP.slideshow_index];
            Tabs.fadeShow(tab.down("a"), tab.id.split("-").first());
        }
        LP.timeout = setTimeout(LP.slideshow, LP.interval);
    },
    stop_slideshow: function () {
        clearTimeout(LP.timeout);
    }
};

var Help = {
    toggle_more_help: false,
    search_complete: function (search_string) {
        $('hide_on_search').hide();

        if (Help.toggle_more_help) {
            $('morehelp').show();
        }

        $("search-results-title").update("Search results for '" + search_string.escapeHTML() + "'");
        $("search-results-container").show();
    },
    search_empty: function () {
        if (Help.toggle_more_help) {
            $('morehelp').hide();
        }
        $('hide_on_search').show();
        $("search-results-container").hide();
    },
    show_os: function (e, elm, os) {
        elm = $(elm);

        $$(".os-filter").invoke("removeClassName", "selected");
        elm.addClassName("selected");
        $$(".help-os-section").invoke("hide");
        $$(".help-os-" + os).invoke("show");


        Event.stop(e);
    },

    vote: function (article_id, value) {
        var dbr = new Ajax.DBRequest("/help/" + article_id + "/vote/" + value);
        var ef = new Effect.Fade("help-vote-cont");
        Notify.ServerSuccess("Thanks for your feedback!");
    }
};
var DBCheckbox = {
    register_all: function () {
        var checkboxes = $$(".checkbox");
        for (var i = 0; i < checkboxes.length; i += 1) {
            DBCheckbox.register(checkboxes[i]);
        }
    },
    register_browse: function () {
        var files = Browse.files;
        for (var i = 0, len = files.length; i < len; i += 1) {
            files[i].checkbox.selected = false;
        }
    },
    register: function (img) {
        img.addClassName("s_checkbox sprite");
        img.selected = false;
        return img;
    },

    select: function (img) {
        Sprite.replace(img, "checkbox", "checkbox_checked");
        img.selected = true;
    },

    deselect: function (img) {
        Sprite.replace(img,  "checkbox_checked", "checkbox");
        img.selected = false;
    }

};
document.observe("dom:loaded", DBCheckbox.register_all);

var TabList = Class.create({
    initialize: function (lists, tabs, tab_map) {
        this.lists = lists;
        this.initialize_lists(lists);

        if (tabs) {
            this.tabs = tabs;
            this.tab_map = tab_map;
            this.initialize_tabs();
        }

    },

    initialize_lists: function () {
        var that = this;
        for (var i = 0; i < this.lists.length; i += 1) {
            var anchors = $(this.lists[i]).select("a");

            for (var j = 0; j < anchors.length; j += 1) {
                var elm = anchors[j];

                var click_func = (function (that) {
                    return function (e, source) {
                        that.list_click(that, e, source);
                    };
                }(that));

                elm.db_observe("click", click_func);
            }
        }
    },

    initialize_tabs: function () {
        var that = this;

        for (var i = 0; i < this.tabs.length; i += 1) {
            var elm = $(this.tabs[i]);
            if (!elm) {
                continue;
            }
            (function () {
                var j = i;
                elm.db_observe("click", function (e, source) {
                    that.tab_click(that, e, source, j);
                });
            }());
        }
    },

    list_click: function (that, event, source) {
        for (var i = 0; i < that.lists.length; i += 1) {
            $(that.lists[i]).select("a").invoke("removeClassName", "selected");
        }
        source.addClassName("selected");
    },

    tab_click: function (that, event, source, show_this) {
        for (var i = 0; i < that.tabs.length; i += 1) {
            $(that.tabs[i]).removeClassName("selected");
        }

        source.addClassName("selected");

        for (var j = 0; j < that.lists.length; j += 1) {
            $(that.lists[j]).hide();
        }

        $(that.tab_map[show_this]).show();
    }
});

var SharedFolderInvites = {
    pages: {},
    contents: {},
    register_all: function () {
        $$(".expand-invite").each(function (elm) {
            SharedFolderInvites.register(elm);
        });
    },

    register: function (elm) {
        elm.db_observe("click", SharedFolderInvites.expand);
    },

    expand: function (e, source, ns_id) {
        Event.stop(e);
        source = $(source);

        if (SharedFolderInvites.animating) {
            return;
        }

        var invite = source.up(".invite");
        var effects = [];

        SharedFolderInvites.get_sf_contents(invite, ns_id);

        if (invite.hasClassName("active")) {
            effects.push(SharedFolderInvites.hide(invite));
        } else {
            effects.push(SharedFolderInvites.show(invite));
        }

        $$("div.invite.active").each(function (elm) {
            if (elm != invite) {
                effects.push(SharedFolderInvites.hide(elm));
            }
        });

        SharedFolderInvites.animating = true;
        var ef = new Effect.Parallel(effects, {duration: 0.5, afterFinish: function () {
            SharedFolderInvites.animating = false;
        }});
    },

    show: function (invite) {
        invite.addClassName("active");
        var details = invite.down(".invite-details");
        var toggle = invite.down(".toggler");
        Sprite.replace(toggle, "plus", "minus");
        return new Effect.BlindDown(details, {sync: true, afterFinish: function () {
            details.style.height = "auto";
        }});
    },

    hide: function (invite) {
        invite.removeClassName("active");
        var details = invite.down(".invite-details");
        var toggle = invite.down(".toggler");
        Sprite.replace(toggle, "minus", "plus");
        return new Effect.BlindUp(details, {sync: true});
    },

    show_page: function (page) {
        Feed.hideLoading();
        $("invites-container").update(SharedFolderInvites.pages[page]);
        SharedFolderInvites.register_all();
    },

    get_page: function (page) {

        if (!SharedFolderInvites.pages[0] && page == 1) {
            SharedFolderInvites.pages[0] = $("invites-container").innerHTML;
        }

        if (SharedFolderInvites.pages[page]) {
            SharedFolderInvites.show_page(page);
        } else {
            Feed.showLoading(false, $('invites-container'));

            var dbr = new Ajax.DBRequest("/share_ajax/invitation_page?page=" + page, {
                onSuccess: function (req) {
                    SharedFolderInvites.pages[page] = req.responseText;
                    SharedFolderInvites.show_page(page);
                }
            });
        }

        return false;
    },

    get_sf_contents: function (invite, ns_id) {

        if (SharedFolderInvites.contents[ns_id]) {
            SharedFolderInvites.show_sf_contents(invite, ns_id);
        } else {
            var dbr = new Ajax.DBRequest("/share_ajax/sf_contents?ns_id=" + ns_id, {
                onSuccess: function (req) {
                    SharedFolderInvites.contents[ns_id] = req.responseText;
                    SharedFolderInvites.show_sf_contents(invite, ns_id);
                }
            });
        }
    },

    show_sf_contents: function (invite, ns_id) {
        invite.down(".folder-contents").update(SharedFolderInvites.contents[ns_id]);
    },

    mailto: function (e, email) {
        Event.stop(e);
        window.location = "mailto:" + email;
    }
};

var AccountExtras = {
    prices: {},
    watch_id: 0,
    show_detail: function (name, group_name, icon) {
        Modal.icon_show(icon, 'What is ' + name + '?', $(group_name + "-modal"), {}, false);
        return false;
    },
    register_price_watch: function (group_name, monthly, yearly) {
        AccountExtras.prices[group_name] = [monthly, yearly];

        if (!AccountExtras.watch_id) {
            AccountExtras.watch_id = setInterval(AccountExtras.update_prices, 200);
        }
    },
    update_prices: function () {
        var yearly = $('yearly').checked;
        var period = yearly ? 1 : 0;
        var period_name = yearly ? "year" : "month";
        for (var group_name in AccountExtras.prices) {
            $(group_name + "-price").update(AccountExtras.prices[group_name][period]);
            $(group_name + "-priceperiod").update(period_name);
        }
    }
};

var DowngradeReasons = {
    reasons: {},
    addReason: function (identifier, reason) {
        DowngradeReasons.reasons[identifier] = reason;
    },

    change: function (identifier, container) {
        identifier = parseInt(identifier, 10);
        var cont = $(container);
        assert(cont, "Couldn't find container for DowngradeReason");

        if (DowngradeReasons.reasons[identifier]) {
            cont.show();
            var img = Sprite.make("information", {});
            img.addClassName("text-img");
            cont.update(img);
            cont.insert(DowngradeReasons.reasons[identifier]);
        } else {
            cont.hide();
        }
    }
};

var Restore = {
    next: function (event, elm) {
        elm = $(elm);
        var selected_list = $$("ul.selected")[0];
        var new_selected_list = selected_list.next("ul");

        new_selected_list.addClassName("selected");
        selected_list.removeClassName("selected");

        if (new_selected_list.next("ul")) {
            Restore.show_next_link();
        } else {
            Restore.hide_next_link();
        }

        Restore.show_prev_link();
        Restore.inc_page(1);
    },

    prev: function (event, elm) {
        elm = $(elm);
        var selected_list = $$("ul.selected")[0];
        var new_selected_list = selected_list.previous("ul");

        new_selected_list.addClassName("selected");
        selected_list.removeClassName("selected");

        if (new_selected_list.previous("ul")) {
            Restore.show_prev_link();
        } else {
            Restore.hide_prev_link();
        }
        Restore.show_next_link();
        Restore.inc_page(-1);
    },

    inc_page: function (inc) {
        var current_page = parseInt($("page-num").innerHTML, 10);
        var new_page = current_page + inc;

        $("page-num").update(new_page);
    },

    hide_next_link: function () {
        $("next-page").update();
    },
    show_next_link: function () {
        var a = new Element("a", { href: "#", onclick: "Restore.next(event, this);"});
        a.update("Next &raquo;");
        $("next-page").update(a);
    },

    show_prev_link: function () {
        var a = new Element("a", { href: "#", onclick: "Restore.prev(event, this);"});
        a.update("&laquo; Prev");
        $("prev-page").update(a);
    },
    hide_prev_link: function () {
        $("prev-page").update();
    }
};

var Votebox = {
    page: '0',
    view: "popular",
    add_comment: function (e) {
        if (e) {
            Event.stop(e);
        }

        var feature_id = $F('feature_id');
        var comment = $F('comment');

        var req = new Ajax.DBRequest("/votebox/add_comment", {
            parameters: { feature_id: feature_id, comment: comment },
            onSuccess: function (req) {
                var comments = $("feature-comments");
                comments.innerHTML = req.responseText + comments.innerHTML;
                $("comment").setValue("");
                comments.scrollTo();
            }
        });
    },

    edit_comment: function (e, elm, comment_id) {
        Event.stop(e);
        elm = $(elm);

        var comment_elm = elm.up().next(".feature-comment-text");
        if (comment_elm.down("textarea")) {
            return;
        }

        comment_elm.old_comment = comment_elm.innerHTML;

        var html = '<p><textarea class="textarea act_as_block" id="comment_edit_#{comment_id}" rows="5" cols="4" >#{comment_content}</textarea></p><p style="text-align:right; margin-bottom:0;"><input type="button" id="comment_save_#{comment_id}" value="Save" class="button"/> <input type="button" id="comment_cancel_#{comment_id}" class="button grayed" value="Cancel"/></p>';
        html = html.interpolate({ 'comment_id'      : comment_id,
                                  'comment_content' : comment_elm.old_comment.strip().replace(/<br\/>|<br>/g, "\n") });

        comment_elm.update(html);

        $("comment_save_" + comment_id).observe("click", function (e) {
            Votebox.save_comment(e, comment_id);
        });

        $("comment_cancel_" + comment_id).observe("click", function (e) {
            Votebox.cancel_comment(e, comment_id);
        });

        ActAsBlock.register(comment_elm);
    },
    delete_comment: function (e, elm, comment_id) {
        Event.stop(e);
        elm = $(elm);

        var comment_elm = elm.up(".feature-comment");

        var req = new Ajax.DBRequest("/votebox/delete_comment", {
            parameters: { comment_id: comment_id }
        });

        comment_elm.remove();
    },

    cancel_comment: function (e, comment_id) {
        Event.stop(e);

        var comment_elm = $("comment_edit_" + comment_id).up(".feature-comment-text");
        comment_elm.update(comment_elm.old_comment);
    },

    save_comment: function (e, comment_id) {
        Event.stop(e);
        var comment_text = $("comment_edit_" + comment_id).getValue();

        var req = new Ajax.DBRequest("/votebox/edit_comment", {
            parameters: { comment_id: comment_id, comment_text: comment_text }
        });

        comment_text = comment_text.escapeHTML().replace(/\n/g, "<br/>");
        $("comment_edit_" + comment_id).up(".feature-comment-text").update(comment_text);
    },

    submit_feature: function (e) {
        Event.stop(e);

        var form = $("add-feature-request");
        Forms.ajax_submit(form, false,
            function (req) {
                window.location = req.responseText;
            }, false, e.target);
    },

    how_voting_works: function () {
        Modal.icon_show("comments", "How Voting Works", $("howvotingworks"));
    },

    votes_left: function () {
        return parseInt($("votes-left").innerHTML, 10);
    },
    vote: function (elm, e) {
        if (e) {
            Event.stop(e);
        }

        elm = $(elm);
        var feature_id = elm.id.slice(4);
        var votes_left = Votebox.votes_left();

        if (votes_left <= 0) {
            Votebox.show_more_votes_modal();
            return;
        }

        var req = new Ajax.DBRequest("/votebox/vote", {
            parameters: { feature_id: feature_id },
            onSuccess: function () {
                if (votes_left == 1) {
                    window.location.reload();
                }
            },
            onFailure: function (req) {
                Votebox.adjust_votes_left(1);
                Votebox.adjust_votes_total(elm, -1);
                Votebox.adjust_votes_bubble(elm, -1);
            }
        });

        Votebox.adjust_votes_left(-1);
        Votebox.adjust_votes_total(elm, 1);
        Votebox.adjust_votes_bubble(elm, 1);
    },

    tab_click: function (e, elm, view) {
        Event.stop(e);
        Votebox.list_set_url({'view': view});
        Votebox.tab(elm);
    },
    tab: function (elm) {
        elm = $(elm);
        elm.up("ul").select(".selected").invoke("removeClassName", "selected");
        elm.up().addClassName("selected");
    },

    list_set_url: function (options) {
        clearTimeout(Tabs.check_interval);
        var page = options.page || Votebox.page || 0;
        var view = options.view || Votebox.view || 'popular';

        if (view != Votebox.view) {
            page = '0';
        }

        var hash = ["votebox", view, page].join(":");
        window.location.href = "#" + hash;

        Votebox.list_hash_update(view, page);
    },

    list_hash_update: function (view, page) {
        var changed = view != Votebox.view || page != Votebox.page;
        if (!changed) {
            return;
        }

        view = view || "popular";
        page = page || 0;

        Votebox.view = view;
        Votebox.get_features(page);
        Votebox.tab($(view + "-tab").down());
    },

    comment_set_url: function (page) {
        var hash = ["votebox", page].join(":");
        window.location.href = "#" + hash;
        Votebox.comment_hash_update(page);
    },

    comment_hash_update: function (page) {
        if (page != Votebox.page) {
            page = page || 0;
            Votebox.get_comments(page);
        }
    },

    adjust_votes_left: function (value) {
        var votes_left = Votebox.votes_left();
        votes_left += value;
        $("votes-left").update(votes_left);
    },

    adjust_votes_total: function (elm, value) {
        var total_elm = elm.previous(".votecount").down("span");

        if (Util.isNumber(total_elm.innerHTML)) {
            var vote_count = parseInt(total_elm.innerHTML, 10);
            total_elm.update(vote_count + value);
        }
    },

    adjust_votes_bubble: function (elm, value) {
        var votecount_elm = elm.up(".votebox");
        var bubble_elm = votecount_elm.down(".ebubble");

        if (bubble_elm) {
            var count_elm = bubble_elm.down(".c");

            var current_count = parseInt(count_elm.innerHTML, 10);
            current_count += value;

            if (current_count === 0) {
                bubble_elm.remove();
            } else {
                count_elm.update("+" + current_count);
            }
        } else {
            votecount_elm.insert(EventBubble.make("+1"));
        }
    },

    show_more_votes_modal: function () {
        Modal.icon_show("comments", "Out of Votes", $("outofvotes"));
    },

    features_cache: {},

    features_key: function (page) {
        return Votebox.view + "_" + Votebox.category + "_" + page;
    },

    get_features: function (page) {
        var key = Votebox.features_key(page);
        Votebox.page = page;
        assert(Util.isNumber(page), "Feature page is not a number: " + page);
        if (Votebox.features_cache[key]) {
            Votebox.show_features(page);

        } else {
            var params = {};
            params.page = page;
            if (Votebox.view) {
                params.view = Votebox.view;
            }
            if (Votebox.category) {
                params.category = Votebox.category;
            }

            Feed.showLoading(false, $('features'));
            var req = new Ajax.DBRequest("/votebox/more_features", {
                parameters: params,
                onSuccess: function (req) {
                    Votebox.features_cache[key] = req.responseText;
                    Votebox.show_features(page);
                },
                onComplete: function () {
                    Feed.hideLoading();
                }
            });
        }
    },

    show_features: function (page) {
        var key = Votebox.features_key(page);
        $("features").update(Votebox.features_cache[key]);
    },

    comments_cache: {},
    get_comments: function (page) {
        Votebox.page = page;

        assert(Util.isNumber(page), "Comment page is not a number" + page);
        if (Votebox.comments_cache[page]) {
            Votebox.show_comments(page);

        } else {
            Feed.showLoading(false, $('feature-comments'));
            var req = new Ajax.DBRequest("/votebox/more_comments", {
                parameters: { feature_id: Votebox.feature_id, page: page },
                onSuccess: function (req) {
                    Votebox.comments_cache[page] = req.responseText;
                    Votebox.show_comments(page);
                },
                onComplete: function () {
                    Feed.hideLoading();
                }
            });
        }
    },

    show_comments: function (page) {
        $("feature-comments").update(Votebox.comments_cache[page]);
    },

    search: function (search_string) {
        Votebox.last_search = search_string;

        if ($("feature-search").title == search_string || search_string === "") {
            if (search_string === "") {
                $("hideme").show();
                $("searchresults").hide();
                $("add-feature").hide();
            }
            return;
        }


        var req = new Ajax.DBRequest("/votebox/search", {
            parameters: { search_string: search_string },
            onSuccess: function (req) {
                $("hideme").hide();
                $("searchresults").show();
                $("searchresults").update(req.responseText);
                $("add-feature").show();
                ActAsBlock.register(false, $("add-feature"));
            }
        });
    }
};

var Bubble = {
    make: function (content, position) {
        var table = new Element("table");
        table.addClassName("bubble");

        var tbody = new Element("tbody");

        var tr1 = new Element("tr");
        var tl = new Element("td");
        tl.addClassName("tl");
        var t = new Element("td");
        t.addClassName("t");
        var tr = new Element("td");
        tr.addClassName("tr");

        tr1.insert(tl);
        tr1.insert(t);
        tr1.insert(tr);

        tbody.insert(tr1);

        var tr2 = new Element("tr");
        var l = new Element("td");
        l.addClassName("l");

        if (position == "left") {
            var arrow = new Element("img", {'src': "/static/images/bubble_arrow.png"});
            arrow.addClassName("arrow");
            l.insert(arrow);
        }
        var c = new Element("td");
        c.addClassName("c");
        c.update(content);

        var r = new Element("td");
        r.addClassName("r");

        if (position == "right") {
            var rarrow = new Element("img", {'src': "/static/images/bubble_arrow_right.png"});
            rarrow.addClassName("rarrow");
            r.insert(rarrow);
        }

        tr2.insert(l);
        tr2.insert(c);
        tr2.insert(r);

        tbody.insert(tr2);

        var tr3 = new Element("tr");
        var bl = new Element("td");
        bl.addClassName("bl");

        var b = new Element("td");
        b.addClassName("b");

        if (position == "bottom") {
            var barrow = new Element("img", {'src': "/static/images/bubble_arrow_bottom.png"});
            barrow.addClassName("barrow");
            b.insert(barrow);
        }

        var br = new Element("td");
        br.addClassName("br");

        tr3.insert(bl);
        tr3.insert(b);
        tr3.insert(br);

        tbody.insert(tr3);
        table.insert(tbody);
        return table;
    }
};

var Timezone = {
    check_timezone: function () {
        if (!Constants.uid) {
            return;
        }
        var offset = Timezone.get_current_timezone();
        if (Constants.auto_timezone_offset === undefined || Constants.auto_timezone_offset != offset) {
            Timezone.update(offset);
        }
    },

    get_current_timezone: function () {
        // http://www.onlineaspect.com/2007/06/08/auto-detect-a-time-zone-with-javascript/
        var now = new Date();
        now.setSeconds(0);
        now.setMilliseconds(0);

        var temp = now.toGMTString();
        var gmt = new Date(temp.substring(0, temp.lastIndexOf(" ") - 1));
        var std_time_offset = (now - gmt) / (1000 * 60 * 60);
        return std_time_offset;
    },

    update: function (offset) {
        assert(typeof(offset) == "number", "Timezone offset was not a number: " + offset);
        var req = new Ajax.DBRequest("/set_timezone", {
            parameters: { 'offset': offset },
            noAutonotify: true
        });
    },

    change: function () {
        var tz_area = $("timezone_area");
        var tz_location = $("timezone_location");
        var new_area = $F(tz_area);
        var new_locations = Timezone.timezones_by_area[new_area];

        if (new_locations.length === 0) {
            tz_location.hide();
        } else {
            tz_location.show();
        }
        tz_location.update();

        for (var i = 0, length = new_locations.length; i < length; i += 1) {
            var e = new Element("option", {'value': new_locations[i]});
            e.innerHTML = new_locations[i];
            tz_location.insert(e);
        }
    },

    auto: function () {
        var value = $F("timezone_auto");
        var tz_area = $("timezone_area");
        var tz_location = $("timezone_location");

        if (!value) {
            tz_area.disabled = false;
            tz_location.disabled = false;
        } else {
            tz_area.disabled = true;
            tz_location.disabled = true;
        }

        Timezone.change();
    }
};
document.observe("dom:loaded", Timezone.check_timezone);

/*global Hash*/
var Profiler = {
	stack: [],
	start_time: new Hash(),
	total_time: new Hash(),

	start: function (label) {
		assert(label.indexOf("_") === -1, "Profiler label is not allowed to contain _");

		Profiler.stack.push(label);
		var key = Profiler.get_key();
		assert(Profiler.start_time.get(key) === undefined, "Profiler for " + label + " already started.");

		Profiler.start_time.set(key, new Date().getTime());
	},

	stop: function () {
		var key = Profiler.get_key();
		var start_time = Profiler.start_time.get(key);

		Profiler.start_time.set(key, undefined);

		var diff = new Date().getTime() - start_time;

		var total = Profiler.total_time.get(key);

		if (total) {
			Profiler.total_time.set(key, total + diff);
		} else {
			Profiler.total_time.set(key, diff);
		}

		var removed_label = Profiler.stack.pop();

		if (Profiler.stack.length === 0) {
			Profiler.print();
		}
	},

	get_key: function () {
		return Profiler.stack.join("_");
	},

	reset: function () {
		Profiler.start_time = new Hash();
		Profiler.total_time = new Hash();
	},

	print: function () {
		var keys = Profiler.total_time.keys();
		keys.sort(function (a, b) {
			var a_depth = a.split("_").length;
			var b_depth = b.split("_").length;

			if (a_depth < b_depth) {
				return -1;
			} else if (a_depth === b_depth) {
				return 0;
			} else {
				return 1;
			}
		});

		var print_key = function (key, depth) {
			var split_key = key.split("_");
			var label = split_key.last();

			var spacing = "&nbsp;";
			for (var j = 0; j < depth; j += 1) {
				spacing += "&nbsp;&nbsp;";
			}

			var time = Profiler.total_time.get(key);
			Util.log(spacing, label, ":", time, "ms");

			for (var i = 0, length = keys.length; i < length; i += 1) {
				var k = keys[i];
				var d = k.split("_").length - 1;

				if (k.startsWith(key + "_") && d == depth + 1) {
					print_key(k, d);
				}
			}
		};

		Util.log("-------------");
		print_key(keys[0], 0);

		Profiler.reset();
	}
};

var DBCalendar = Class.create({
    initialize: function (container_id, options) {
        this.options = options || {};
        this.container = $(container_id);
        assert(this.container, "Couldn't find the element");

        this.today = new Date();

        if (this.options.disable_future) {
            this.options.last_day = this.options.last_day || this.today;
        }

        this.current_day = Util.start_of_day(this.options.selected_day || new Date(this.today.getFullYear(), this.today.getMonth(), 1));
        this.selected_day = Util.start_of_day(this.options.selected_day || new Date(this.today.getFullYear(), this.today.getMonth(), this.today.getDate()));
        this.render();
    },

    change_month: function (e, month) {
        Event.stop(e);
        this.current_day.setMonth(month);
        this.render();

        if (this.options.onMonthChange) {
            this.options.onMonthChange(this.current_day);
        }

    },

    change_day: function (e, day) {
        Event.stop(e);
        this.container.select(".selected").invoke("removeClassName", "selected");
        $(e.target).addClassName("selected");

        this.selected_day.setMonth(this.current_day.getMonth());
        this.selected_day.setYear(this.current_day.getFullYear());
        this.selected_day.setDate(day);

        if (this.options.onDateChange) {
            this.options.onDateChange(this.selected_day);
        }
    },

    render: function () {
        var days = this.render_days();

        this._next_month = (function (e) {
            this.change_month(e, this.current_day.getMonth() + 1);
        }).bind(this);

        this._prev_month = (function (e) {
            this.change_month(e, this.current_day.getMonth() - 1);
        }).bind(this);

        var next = new Element("a");
        next.addClassName("changemonth next");
        next.update(Sprite.make("arrowright", {}));
        Event.observe(next, "click", this._next_month);

        var prev = new Element("a");
        prev.addClassName("changemonth prev");
        prev.update(Sprite.make("arrowleft", {}));
        Event.observe(prev, "click", this._prev_month);

        var calendar = new Element("div");
        calendar.addClassName("calendar clearfix");

        var label = new Element("h5");

        label.update(" " + Util.months[this.current_day.getMonth()] + " " + this.current_day.getFullYear() + " ");

        calendar.insert(next);
        calendar.insert(prev);
        calendar.insert(label);

        calendar.insert(days);

        this.container.update(calendar);
    },

    render_days: function () {
        var first_day = new Date(this.current_day.getFullYear(), this.current_day.getMonth(), 1);
        var previous_month_days_count = first_day.getDay();

        // Render previous month days
        var days = new Element("div");
        days.addClassName("days");

        for (var i = previous_month_days_count; i > 0; i -= 1) {
            var prev_date = new Date(first_day.getFullYear(), first_day.getMonth(), first_day.getDate());
            prev_date.setDate(prev_date.getDate() - i);
            days.insert(this.render_day(prev_date, true));
        }

        // Render current month days
        var iter_day = new Date(this.current_day.getFullYear(), this.current_day.getMonth(), 1);
        while (iter_day.getMonth() == this.current_day.getMonth()) {
            days.insert(this.render_day(iter_day));
            iter_day = new Date(this.current_day.getFullYear(), this.current_day.getMonth(), iter_day.getDate() + 1);
        }

        // Render next month days
        var last_day = new Date(this.current_day.getFullYear(), this.current_day.getMonth() + 1, 0);
        while (last_day.getDay() != 6) {
            last_day = new Date(last_day.getFullYear(), last_day.getMonth(), last_day.getDate() + 1);
            days.insert(this.render_day(last_day, true));
        }

        return days;
    },

    render_day: function (day, inactive) {

        if (this.options.last_day) {
            inactive = inactive || day > this.options.last_day;
        }
        if (this.options.first_day) {
            inactive = inactive || day < this.options.first_day;
        }
        var a;
        if (inactive) {
            a = new Element("span");
        } else {
            a = new Element("a");
        }

        a.update(day.getDate());
        a.addClassName("date");

        if (this.selected_day.getDate() == day.getDate() && this.selected_day.getMonth() == day.getMonth() && this.selected_day.getFullYear() == day.getFullYear()) {
            a.addClassName("selected");
        }

        if (inactive) {
            a.addClassName("inactive");
        } else {
            this._change_day = (function (e) {
                this.change_day(e, day.getDate());
            }).bind(this);
            Event.observe(a, "click", this._change_day);
        }

        Util.disableSelection(a);
        return a;
    }
});

var EventDatePicker = {
    show_calendar: function (e) {
        if (EventDatePicker.shown) {
            return;
        }

        Event.stop(e);

        if (!EventDatePicker.calendar) {
            var cal_container = new Element("div", {'id': 'cal_container'});
            cal_container.observe("click", function (e) {
                Event.stop(e);
            });
            $(document.body).insert(cal_container);

            EventDatePicker.calendar = new DBCalendar("cal_container", { 'onDateChange': EventDatePicker.change_date, 'disable_future': true, 'first_day': EventDatePicker.first_event });
            cal_container.absolutize();

            var cal_date = $("cal_date");

            cal_container.clonePosition(cal_date, {'setWidth': false, 'setHeight': false, 'offsetTop': cal_date.getHeight() - 1, 'offsetLeft': cal_date.getWidth() - cal_container.down().getWidth()});
        }

        $("cal_container").show();
        $(document.body).observe("click", EventDatePicker.hide_calendar);
        EventDatePicker.shown = true;
    },

    hide_calendar: function (e) {
        Event.stop(e);
        $("cal_container").hide();
        $(document.body).stopObserving("click", EventDatePicker.hide_calendar);
        EventDatePicker.shown = false;
    },

    change_date: function (date) {
        var cur_date_text = $("cur_date_text");
        cur_date_text.update(Util.months[date.getMonth()] + " " + date.getDate() + ", " + date.getFullYear());
        Feed.set_url({'date': Util.niceDate(date)});
    }
};

var TextInputDatePicker = Class.create(
{
    initialize: function (input_id, options) {
        this.options = {
            'include_seconds': true,
            'choose_eod':      false // force the time selection to the end of the day
        };
        Object.extend(this.options, options || {});

        this.input = $(input_id);
        assert(this.input, "Couldn't find the element " + input_id.toString());

        // set the calendar's day if there's already something in the text box
        var now = new Date();
        var selected_day = this.input.value ? Util.from_mysql_date(this.input.value) : false;
        var last_day = new Date(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());

        this.cal_icon = Sprite.make('calendar_view_month', {'align': 'absmiddle'});
        this.cal_container  = new Element("div", {'id': 'cal_container_' + input_id, 'style': 'display: none; position: absolute; z-index: 1'});
        this.calendar = new DBCalendar(this.cal_container, {'onDateChange': this.onDateChange.bind(this), 'last_day': last_day, 'selected_day': selected_day});

        // put the icon to the right of the text input
        //this.cal_icon.clonePosition(this.input, {'setWidth': false, 'setHeight': false, 'offsetLeft': this.input.getWidth() + 2);
        this.input.insert({'after': this.cal_icon});
        this.cal_icon.observe('click', this.toggle_cal.bindAsEventListener(this));

        // put the calendar under the input
        this.cal_container.clonePosition(this.input, {'setWidth': false, 'setHeight': false, 'offsetTop': this.input.getHeight()});
        this.cal_icon.insert({'after': this.cal_container});
    },
    toggle_cal: function (e) {
        if (e) {
            Event.stop(e);
        }
        this.cal_container.toggle();
    },
    hide_cal: function (e) {
        if (e) {
            Event.stop(e);
        }
        this.cal_container.hide();
    },
    onDateChange: function (date) {
        if (this.options.choose_eod) {
            date.setTime(Util.start_of_day(date).getTime() + 86399999); // 1 millisecond before the next day, ignoring leap seconds/daylight savings
        }
        this.input.value = Util.to_mysql_date(date, true); // include_seconds=true
        this.hide_cal();
    }
});

var HashKeeper = {
    last_hash: null,
    check_hash: function () {
        var iframe_hash = HashKeeper.get_iframe_hash();
        var current_hash = Util.url_hash();

        if (iframe_hash != HashKeeper.last_hash) {
            HashKeeper.last_hash = iframe_hash;
            window.location = "#" + iframe_hash;
            return;
        }

        if (HashKeeper.last_hash != current_hash) {
            HashKeeper.set_iframe_hash(current_hash);
        }
    },

    get_iframe_hash: function () {
        if (!document.frames.hashkeeper || !document.frames.hashkeeper.location) {
            return;
        }
        var hash = document.frames.hashkeeper.location.href.split("#")[1];
        return hash;
    },

    set_iframe_hash: function (hash) {
        if (!document.frames.hashkeeper || !document.frames.hashkeeper.location) {
            return;
        }
        HashKeeper.last_hash = hash;

        var ifrm = document.getElementById('hashkeeper');
        ifrm = (ifrm.contentWindow) ? ifrm.contentWindow : (ifrm.contentDocument.document) ? ifrm.contentDocument.document : ifrm.contentDocument;

        ifrm.document.open();
        ifrm.document.write(new Date().getTime());
        ifrm.document.close();
        document.frames.hashkeeper.location = "#" + hash;
    }
};

var HashRouter = {
    watch_timer: null,
    callback_map: {},
    last_hash: "",
    last_prefix: "",
    watch: function (prefix, callback) {
        HashRouter.callback_map[prefix] = callback;

        if (!HashRouter.watch_timer) {
            HashRouter.watch_timer = setInterval(HashRouter.check_hash, 300);
        }
    },

    check_hash: function () {
        var hash = Util.url_hash();

        if (HashRouter.last_hash == hash) {
            return;
        }
        HashRouter.last_hash = hash;

        if (HashRouter.last_prefix && hash === "") {
            hash = HashRouter.last_prefix + ":";
        } else if (!hash || hash.indexOf(":") == -1) {
            return;
        }

        var split_hash = hash.split(":");
        var prefix     = split_hash.first();
        HashRouter.last_prefix = prefix;

        var func = HashRouter.callback_map[prefix];
        if (func) {
            func.apply(func, split_hash.slice(1));
        }
    },
    set_hash: function () {
        var filtered_args = $A(arguments).map(Util.falsy_to_empty);
        var hash = filtered_args.map(encodeURIComponent).join(":");

        HashRouter.last_hash = hash;
        window.location.hash = hash;
    }
};

if (Util.ie) {
    document.observe("dom:loaded", function () {
        HashKeeper.hash_checker = setInterval(HashKeeper.check_hash, 300);
    });
}

var DBObserver = {
    watch: function (elm_id, callback) {
        setInterval(function () {
            var elm = $(elm_id);
            assert(elm, "Couldn't find watch element");

            var new_search = elm.getValue().strip();
            if (new_search != elm.last_search && new_search != elm.title) {
                elm.last_search = new_search;
                callback(new_search);
            }
        }, 300);
    }
};

var Team = {
    show_add_modal: function (team_name) {
        Sharing.reset_wizard();
        DomUtil.fillVal("team", "invite-more-wizard-share-type");
        DomUtil.fillVal("Invite to team", "invite-more-wizard-share-button");
        Modal.icon_show("folder_user_add", "Add users to \"" + team_name.escapeHTML() + "\"", $("invite-more-wizard"), {'action': Team.add_users});
    },
    add_users: function (e) {
        Event.stop(e);

        var form = $("invite-more-form");
        assert(form, "Couldn't find the invite more form.");

        Forms.ajax_submit(form, "/account/team/add_users",
            function (req) {
                Modal.hide();
                $("team-member-info").update(req.responseText);
                // responseText has a Notify.ServerSuccess with the user count
            },

            function () {
                Forms.enable(form.down("input[type='submit']"));
            },
            e.target,
            {'team_id': Constants.team_id}
            );
    },
    show_remove_modal: function (button, team_name, user_id, email) {
        DomUtil.fillVal(email, "remove-user-email");
        DomUtil.fillVal(team_name, "remove-user-team");
        Modal.icon_show("delete", "Remove user from \"" + team_name.escapeHTML() + "\"", $("remove-user-modal"), {'user_id': user_id, 'button': button});
    },
    remove_user: function (e) {
        Event.stop(e);

        var user_id = Modal.vars.user_id;
        var dbr = new Ajax.DBRequest("/account/team/remove_user", {
            parameters: {'team_id': Constants.team_id, 'user_id': user_id},
            onSuccess: function (req) {
                var row = Modal.vars.button.up(".bs-row");
                if (row) {
                    row.hide();
                }
                Team.decrement_used_licenses();
                Notify.ServerSuccess("User removed.");
            },
            cleanUp: function () {
                Modal.hide();
            }
        });
    },
    show_reinvite_modal: function (button, team_name, user_id, email) {
        DomUtil.fillVal(email, "reinvite-user-email");
        DomUtil.fillVal(team_name, "reinvite-user-team");
        Modal.icon_show("email", "Resend Invite to \"" + email.escapeHTML() + "\"", $("reinvite-user-modal"), {'user_id': user_id, 'button': button});
    },
    reinvite_user: function (e) {
        Event.stop(e);

        var user_id = Modal.vars.user_id;
        var dbr = new Ajax.DBRequest("/account/team/reinvite_user", {
            parameters: {'team_id': Constants.team_id, 'user_id': user_id},
            onSuccess: function (req) {
                Notify.ServerSuccess("Invite sent.");
                $("team-member-info").update(req.responseText);
            },
            cleanUp: function () {
                Modal.hide();
            }
        });
    },
    show_reset_password_modal: function (button, team_name, user_id, email) {
        DomUtil.fillVal(email, "reset-password-email");
        Modal.icon_show("arrow_refresh", "Reset password for \"" + email.escapeHTML() + "\"", $("reset-password-modal"), {'user_id': user_id, 'button': button});
    },
    reset_password: function (e) {
        Event.stop(e);

        var user_id = Modal.vars.user_id;
        var dbr = new Ajax.DBRequest("/account/team/reset_password", {
            parameters: {'team_id': Constants.team_id, 'user_id': user_id},
            onSuccess: function (req) {
                Notify.ServerSuccess("User's password reset.");
            },
            cleanUp: function () {
                Modal.hide();
            }
        });
    },
    show_admin_status_modal: function (button, team_name, user_id, email, display_name, admin_on) {
        var name = display_name.strip() || email;
        var status_action, button_action, title, icon;

        if (admin_on) {
            status_action = "make " + name.escapeHTML() + " an Admin of \"" + team_name.escapeHTML()  + "\"";
            button_action = "Make admin";
            title = "Make " + name.escapeHTML() + " an Admin";
            icon = 'user_suit';
        } else {
            status_action = "remove admin privileges for " + name.escapeHTML();
            button_action = "Remove admin status";
            title = "Remove Admin Status";
            icon = 'user_suit_minus';
        }

        DomUtil.fillVal(email, "admin-status-email");
        DomUtil.fillVal(status_action, "admin-status-action");
        DomUtil.fillVal(button_action, "admin-status-button-action");
        Modal.icon_show(icon, title, $("admin-status-modal"), {'user_id': user_id, 'button': button, 'admin_on': admin_on});
    },
    set_admin_status: function (e) {
        Event.stop(e);

        var user_id = Modal.vars.user_id;
        var dbr = new Ajax.DBRequest("/account/team/set_admin_status", {
            parameters: {'team_id': Constants.team_id, 'user_id': user_id, 'on': Modal.vars.admin_on ? "yes" : "no"},
            onSuccess: function (req) {
                Notify.ServerSuccess("User's admin status " + (Modal.vars.admin_on ? "granted" : "removed") + ".");
                $("team-member-info").update(req.responseText);
            },
            cleanUp: function () {
                Modal.hide();
            }
        });
    },
    show_team_message_modal: function (team_name) {
        DomUtil.fillVal(team_name, "team-message-team");
        Modal.icon_show("page_white_get", "Send email to members of \"" + team_name.escapeHTML() + "\"", $("team-message-modal"));
        Util.focus.defer("team-message");
    },
    send_team_message: function (e) {
        Event.stop(e);

        var message = $F("team-message").strip();
        if (message) {
            var dbr = new Ajax.DBRequest("/account/team/send_team_message", {
                parameters: {'team_id': Constants.team_id, 'message': message},
                onSuccess: function (req) {
                    Notify.ServerSuccess("Message successfully sent to team.");
                    Modal.hide();
                }
            });
        }
    },
    used_licenses: 0,
    total_licenses: 0,
    set_used_licenses: function (used, total) {
        Team.used_licenses = used;
        Team.total_licenses = total;

        $('team-used-licenses').update(used);
        $('team-avail-licenses').update(total - used);
    },
    decrement_used_licenses: function () {
        Team.set_used_licenses(Team.used_licenses - 1, Team.total_licenses);
    }
};

window.LoadedJsSuccessfully = true;

