jQuery.noConflict();
(function() {
    var dav;
    var basepath = jQuery("script[src*=textbox.js]").attr("src").replace(/textbox\.js/, "");
    
    jQuery.extend(jQuery.fn, {
        textbox: function(url, options) {
            return this.each(function() {
                // store a reference to the textbox instance in the element data
                jQuery(this).data("textbox", new TextBox(this, url, options));
            });
        }
    });
    
    // similar to prototypes bind
    // usage: jQuery.context(obj, fn, [args]...)
    jQuery.extend({
        context: function(context, fn) {
            return function() { 
                return fn.apply(context, arguments); 
            };
        }
    });
    
    this.TextBox = function(elem, url, options) {
        // closure
        var self = this;
        dav = AXIS.WebDAV;
        
        // extend the base options
        this.options = jQuery.extend({
            async: true,
            image: false,
            versioned: false,
            editable: true,
            controlElement: "button",
            className: "textbox",
            hoverclass: "textbox-hover",
            editHeight: "auto",
            editWidth: "clone"
        }, options);
        
        // add custom events
        jQuery.each(["load", "save", "edit", "cancel", "historystart", "historysave"], function() {
            if (self.options[this]) 
                self.addEvent(this, self.options[this]);
        });
        
        if (this.options.preProcessors) {
            jQuery.extend(this.preProcessors, this.options.preProcessors);
        }
        
        this.source = "";       // source cache
        this.element = jQuery(elem).addClass(this.options.className);
        this.content = jQuery("<div class='textbox-content'>").appendTo(this.element);
        this.editable = this.options.editable;
        this.url = url;
        
        this.createStyleSheet();

        // fetch current-user-privilege-set
        if (this.options.allow) {
            this.allow = this.options.allow;
        }
        else {
            this.allow = function() {
                return false;
            };

            var privs = dav.getCUPS({
                asynch: false,
                url: AXIS.Util.uri.getParent(url)
            });

            if (privs) {
                this.allow = privs.hasPriv;
            };
        }
        this.onTitleUpdate = AXIS.CustomEvents.create();
        this.load();
        this.hover();
    };
    
    this.TextBox.prototype = {
        editing: false,
        css: "css/textbox.css",
        createStyleSheet: function() {
            if (jQuery("#textbox-css")[0]) return; // don't duplicate the css
            css = jQuery("<link id='textbox-css' />").appendTo("head").attr({
                rel:  "stylesheet",
                type: "text/css",
                href: basepath + "../configuration/" + this.css
            });
        },
        setProperty: function(props, fn) {
            var fn    = fn || function() {};
            var url   = this.url;
            dav.PROPPATCH({
                url: url,
                setProperties: props,
                on409: jQuery.context(this, function() {
                    if (this.checkedOut) return;
                    this.checkout(url);
                    this.setProperty(props, fn);
                }),
                onSuccess: fn
            });
        },
        getProperty: function(url, props, errorCallback) {
            var response = dav.getProperty({
                properties: props,
                url: url,
                async: false
            });
            if (response.httpHandle.status > 300) 
                return errorCallback ? errorCallback(response) : null;
            response = response.responseXMLObject().multistatus.response.propstat;
            if (response.constructor !== Array) {
                response = [response];
            }
            return response[0].prop;
        },
        isCheckedOut: function(url) {
            var checked_out;
            try {
                checked_out = this.getProperty(url || this.url, ["checked-out"])["checked-out"];
            }
            catch(e) {
                checked_out = null;
            }

            return (checked_out != null);
        },
        checkout: function(url) {
            if (!this.options.versioned) return;
            if (!this.checkedOut && this.isCheckedOut(url)) return this.checkedOut = true;
            dav.CHECKOUT({
                url: url,
                asynch: false,
                on409: jQuery.context(this, function() {
                    dav.VERSION_CONTROL({url: url, asynch: false});
                    this.checkout(url);
                }),
                onSuccess: jQuery.context(this, function() {
                    this.checkedOut = true;
                })
            });
        },

        save: function() {
            var text = this.editField.val();
            var url  = this.url;
            // checkout the resource
            // if it's not versioned start version control and checkout
            if (this.options.versioned && !this.checkedOut) {
                this.checkout(url);
            }
            var self = this;
            var cnt = 0;
            var put = function(on409) {
                dav.PUT({
                    url: url,
                    body: text,
                    onSuccess: jQuery.context(self, function(res) {
                        this.source = text;
                        this.loadText(this.source, "save");
                        var title = text.replace(/\s*=?=?=?([^=\n\r]*)=?=?=?[\s\S]*/, '$1');
                        this.setProperty([{ 
                            name: 'displayname',
                            value: title
                        }]);

                        this.onTitleUpdate.fire([title]);
                        if (this.checkedOut) {
                            dav.CHECKIN({url: url});
                            this.checkout(url);
                        }
                    }),
                    on403: jQuery.context(self, function() {
                        // needed for the frontpage
                        this.source = text;
                        this.loadText(this.source, "save");
                    }),
                    on409: function() {
                        var folder = url.replace(/\/[^\/]*$/,'/');
                        dav.mkcolParents({url: folder});
                        if (cnt <=1 ) put();
                        cnt++
                    }
                });
            };
            put();
            return false; // prevent default event behavior
        },

        cancel: function() {
            this.editField.val(this.source); // reset
            this.loadText(this.source, "cancel"); // reload the text
            return false; // prevent default event behavior
        },
        load: function(url) {
            var url = url || this.url;
            if (this.options.image) {
                return this.loadText('<img src="' + url + '">', "load");
            }
            if (this.options.body) {
                this.source = this.options.body;
                this.loadText(this.source, "load");
            } else {
                dav.GET({
                    asynch: this.options.async,
                    url: url,
                    onSuccess: jQuery.context(this, function(res) {
                        this.source = res.responseText;
                        this.loadText(this.source, "load");
                    }),
                    on403: jQuery.context(this, function() {
                        this.source = "=Sorry! You don't have read access to this content=";
                        this.loadText(this.source, "load");
                    }),
                    on404: jQuery.context(this, function(res) {
                        // data file not found, create it
                        this.source = "=" + unescape(AXIS.Util.uri.basename(url)) + "=";
                        dav.PUT({
                            asynch: this.options.async,
                            url: url,
                            body: this.source,
                            on401: function() {
                            }
                        });
                        this.loadText(this.source, "load");
                    })
                });
            }
        },
        edit: function() {
            this.editControls = jQuery("<div class='editor-controls'>\
                                            <a href='#' class='editor-save button'>Save</a> \
                                            <a href='#' class='editor-cancel button'>Cancel</a>\
                                        </div>");
            this.editField = jQuery("<textarea class='editor-field' />");
            this.editor = jQuery("<div class='textbox-editor'>").append(this.editControls, this.editField);
            
            // bind event handlers
            this.editControls
                .find("a:eq(0)").bind("click", jQuery.context(this, this.save)).end()
                .find("a:eq(1)").bind("click", jQuery.context(this, this.cancel)).end();
           
            function fix_newlines(s) {
                // convert \r (which Macs add) to \r\n
                s = s.replace(/\r([^\n])/g, "\r\n$1");

                // convert \n (which Firefox adds) to \r\n
                s = s.replace(/([^\r])\n/g, "$1\r\n");

                return s;
            }

            // setup editor field properties
            this.editField.text(fix_newlines(this.source));
            
            // set styles
            var copyableStyles = ["fontFamily", "fontWeight", "fontSize", "color"];
            jQuery.each(copyableStyles, jQuery.context(this, function(index, item) {
                this.editField.css(item, this.content.css(item));
            }));

            this.editField.css("width", this.content.width());

            this.loadEditor(this.editor, "edit"); // insert the editor into the textbox
            
            // auto set size so that content expands without needing a scrollbar
            var setSize = jQuery.context(this, function(direction) {
                var capped  = direction.slice(0, 1).toUpperCase() + direction.slice(1);
                var storage = this.editField.data(direction);
                var option  = this.options["edit" + capped]
                if (option === "auto") {
                    storage = storage || parseInt(this.editField[direction]());
                    var field  = this.editField[0];
                    var scroll = field["scroll" + capped];      // only need to look up the scroll offset once
                    while (field["client" + capped] < scroll) { // check to see if there is a scrollbar
                        storage += 5;
                        this.editField.css(direction, storage + "px");
                    }
                    this.editField.data(direction, storage);
                } else if ( !isNaN(parseInt(option)) ) {    
                    storage = this.editField.data(direction, option) && option;
                    
                } else { // handle the clone case because that's already been taken care of
                    return;
                }
                this.editField.css(direction, storage + "px");
            });
            
            setSize("width");
            setSize("height");
            
            return false; // prevent default event behavior
        },
        history: function() {
            // trigger callback fn
            this.triggerEvent("historystart");
            return false;
        },
        hover: function() {
            var self = this;
            this.element.hover(     // setup hover
                function() {
                    if (!self.editing) jQuery(this).addClass(self.options.hoverclass);
                }, function() {
                    if (!self.editing) jQuery(this).removeClass(self.options.hoverclass);
                }
            );
        },
        loadEditor: function(editor, e) {
            this.element
                .prepend(editor)                        // add it right before the content
                .removeClass(this.options.hoverclass);  // clear the hover class
            this.content.hide(); 
            this.toolbar.remove();
            this.editing = true;
            if (e) this.triggerEvent(e);
        },
        loadText: function(text, e) {
            this.content.html(this.processResult(text));
            this.content.show();
            if (this.editing) {
                this.editor.remove();
            }
            if (this.options.editable && this.allow('write')) {
                this.createToolBar();
            }            
            this.editing = false;
            // trigger any custom event setup
            if (e) this.triggerEvent(e);
        },
        createToolBar: function() {
            this.toolbar = jQuery("<div><a href='#' class='edit button'>Edit</a></div>").attr("class", "textbox-toolbar");

            // link handlers
            this.toolbar.find("a.edit").bind("click", jQuery.context(this, this.edit));

            // append to content
            this.toolbar.prependTo(this.element);        
        },
        triggerEvent: function(type) {
            jQuery(this).trigger(type + ".textbox");
        },
        addEvent: function(type, fn) {
            jQuery(this).bind(type + ".textbox", fn);
        },
        processResult: function(text) {
            var self = this;
            if (this.options.image)
                return text;
            jQuery.each(this.preProcessors, function() {
                text = this.apply(self, [text]);
            });
            return text;
        },
        preProcessors: {
            // Keyword 'this' refers to current textbox instance in these functions below.
            templateTags: function(text) {
                var args    = this.options.templateArgs || {};
                var re      = /\{\%([^\%^\}]+)\%\}/g;
                var trimre  = /\s*/g; // liberal trim since var names shouldn't have spaces
                return text.replace(re, function() {
                    var match = arguments[1].replace(trimre, "");
                    return args[match] || "";
                });
            },
            wiki2HTML: function(s) {
                // cache's the regular expressions
                var transforms = 
                [
                    [ (/(\n|^)===(.*)===/gi),            " <h3> $2 </h3> "],               /* heading 3 */
                    [ (/(\n|^)==(.*)==/gi),              " <h2> $2 </h2> "],               /* heading 2 */
                    [ (/(\n|^)=(.*)=/gi),                " <h1> $2 </h1> "],               /* heading */
                    [ (/'''([^''']*)'''/gi),             " <b> $1 </b> "],                 /* bold */
                    [ (/''([^'']*)''/gi),                " <i> $1 </i> "],                 /* italics */
                    [ (/\[\[([^ ]*)\]\]/gi),             " <a href='$1'> $1 </a> "],       /* link, no title */
                    [ (/\[\[([^ ]*)\ ?([^\[]*)\]\]/gi),  " <a href='$1'> $2 </a> "],       /* link */
                    [ (/(\r?\n{2,})(.*)(\r?\n)/gi),  "\n <p> $2 </p> "]  /* paragraph */
                ];

                // list transform
                var listt = function(s) {
                    var listRegExp      = /(\r?\n\s*\*.*)+/g;
                    var listItemRegExp  = /\*(.*)/g;
                    return s.replace(listRegExp, function(match) {
                        return "\n <ul> " + match.replace(listItemRegExp, " <li> $1 </li> ") + "\n </ul> ";
                    });
                };

                // escape html
                var escapeHTML = function(s) {
                    return s.replace(/</gi, "&lt;").replace(/>/gi, "&gt;");
                };

                // remove the verbatim text and store them in a separate array
                // replace them in the original text with the following token %%n%% where n in the index in the array
                var verbatimMatches = [];
                var verbatimRegExp = /\{\{\{([\s\S]*?)\}\}\}/gi    ;
                var s = s.replace(verbatimRegExp, function($0, $1, index) {
                    var len = verbatimMatches.length;
                    verbatimMatches.push($1);
                    return "%%" + len + "%%";
                });

                s = listt(s);
                jQuery.each(transforms, function() { 
                    s = s.replace(this[0], this[1]);
                });

                // replace the verbatim text
                var placeHolderRegExp = /\%\%(\d+)\%\%/gi;
                return s.replace(placeHolderRegExp, function($0, $1, index) {
                    var text = escapeHTML(verbatimMatches[parseInt($1)]);
                    return "<tt>" + text + "</tt>";
                });
                
            }
        }
    };
    
})();
