function PapirflyObject() {
    var changedElementsStore, saveButton;

    // Resource strings are initialized with default texts
    this.ResourceStrings = {
        ErrorTitle: "Error",
        InfoTitle: "Information",
        ConfirmTitle: "Please confirm",
        OkButton: "Ok",
        CancelButton: "Cancel"
    }

    // Replaces the default texts with provided texts
    this.SetResourceStrings = function(resourceStrings) {
        for (var name in resourceStrings) {
            if (this.ResourceStrings.hasOwnProperty(name))
                this.ResourceStrings[name] = resourceStrings[name];
        }
    }

    function HighlightSaveButton() {
        if (saveButton) {
            saveButton.addClass('Active');
        }
    }

    function HighlightChangedElement(input) {
        if (input) {
            input.addClass('InputChanged');
        }
    }

    function AddChangedElement(input) {
        if (changedElementsStore) {
            var id = input.attr('id');
            if (id && id.length > 0) {
                id = "[" + id + "]";
                var elems = changedElementsStore.attr('value');
                if (elems.indexOf(id) < 0) {
                    if (elems.length == 0)
                        HighlightSaveButton();
                    else
                        elems += ",";
                    elems += id;
                    changedElementsStore.attr('value', elems);
                }
            }
        }
    }

    // Marke element (identified by jQuery object) as changed
    this.MarkAsChanged = function(jq) {
        if (jq.hasClass('NoHighlight')) return;
        HighlightChangedElement(jq);
        AddChangedElement(jq);
    }

    this.ListenForChanges = function(input) {
        function Highlight() {
            HighlightChangedElement(input);
            AddChangedElement(input);
            input.unbind("keydown", OnKeydown);
            input.unbind("keypress", OnKeyPress);
            input.unbind("change", OnChange);
            input.unbind("click", OnChange);
        }

        function OnChange(e) {
            Highlight();
            return true;
        }
        function OnKeyPress(e) {
            if (e.which != 0) {
                Highlight();
            }
            return true;
        }
        function OnKeydown(e) {
            if (e.which == 8 || e.which == 46) {
                Highlight();
            }
            return true;
        }

        input.bind("keydown", OnKeydown);
        input.bind("keypress", OnKeyPress);
        input.bind("change", OnChange);
        if (input.attr('type') == 'checkbox' || input.attr('type') == 'radio')
            input.bind("click", OnChange);
    }

    // Highlight elements changed
    this.HighlightChangedElements = function(changedElementsStoreId) {
        jQuery(function() {
            changedElementsStore = jQuery('#' + changedElementsStoreId);
            saveButton = jQuery('.SaveButton');
            Papirfly().MarkChangedElements();

            function HandleThisElement(input) {
                if (input.hasClass('NoHighlight')) return false;
                if (input.attr('type') == 'checkbox') {
                    var name = input.attr('name');
                    if (name && name.length >= 8 && name.lastIndexOf('selector') == (name.length - 8)) return false;
                }
                return true;
            }

            // listen for changes
            jQuery('input, textarea, select').each(function() {
                var input = jQuery(this);

                if (HandleThisElement(input)) {
                    Papirfly().ListenForChanges(input);
                }
            });
        });
    }

    // Highlight elements changed
    this.MarkChangedElements = function(val) {
        if (changedElementsStore) {
            if (val)
                changedElementsStore.val(val);
            var elems = changedElementsStore.val().split(',');
            var firstTime = true;
            for (var i = 0; i < elems.length; i++) {
                var id = elems[i];
                if (id && id.length > 2) {
                    id = id.substring(1, id.length - 1);
                    if (firstTime) {
                        HighlightSaveButton();
                        firstTime = false;
                    }
                    HighlightChangedElement(jQuery('#' + id));
                }
            }
        }
    }

    // Adjust container heights to take the space available
    this.AdjustContainerHeights = function() {
        jQuery(function() {
            var firstTime = true;
            function AdjustHeights() {
                var maxH = jQuery(".DoMaxHeight")
                var maxW = jQuery(".DoMaxWidth");
                var winWidth = jQuery(window).width();
                var m = jQuery('#NodeMenuContainer');
                var content = jQuery('.Pf_Content');
                var f = jQuery('#Pf_Footer');
                var bTop = jQuery('#Pf_AdminBottom').offset().top;
                var fHeight = f.height();

                if (Papirfly().IsIE6()) bTop += 15;

                if (firstTime && f.children().length > 0)
                    f.show();
                //alert("left: " + left);

                var left = content.offset().left; // prefetch before doing changes
                var top = content.offset().top;

                m.height(bTop - m.offset().top);

                maxH.each(
                    function() {
                        var e = jQuery(this);
                        e.height(bTop - e.offset().top - fHeight);
                    }
                );
                maxW.each(
                    function() {
                        var e = jQuery(this);
                        e.width(winWidth - e.offset().left);
                    }
                );

                content.height(bTop - top - fHeight);
                content.width(winWidth - left);
                firstTime = false;
            }
            if (Papirfly().IsIE6())
                setTimeout(AdjustHeights, 100);
            else
                AdjustHeights();
            jQuery(window).resize(function() {
                AdjustHeights();
            });
        });
    }

    // Set the title of the dialog within the parent window
    this.SetParentWindowDialogTitle = function(title) {
        jQuery(function() {
            if (window.parent)
                window.parent.jQuery("#UrlDialog").dialog('option', 'title', title);
        });
    }

    // Restore and save scroll position of given target element
    this.MaintainScrollPosition = function(targetId, offsetTopId) {
        if (Papirfly().jQueryExist())
            jQuery(function() {
                var target = jQuery('#' + targetId);
                var offset = jQuery('#' + offsetTopId);

                target.scrollTop(offset.attr('value'));

                target.scroll(function() {
                    offset.attr('value', target.scrollTop());
                    return true;
                });
            });
    }

    // Returns true if the flash palyer is installed
    this.IsFlashPlayerInstalled = function() {
        var MM_contentVersion = 8;
        var MM_FlashCanPlay = false;
        var plugin = (navigator.mimeTypes && navigator.mimeTypes["application/x-shockwave-flash"]) ? navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin : 0;
        if (plugin) {
            var MM_PluginVersion;
            var words = navigator.plugins["Shockwave Flash"].description.split(" ");
            for (var i = 0; i < words.length; ++i) {
                if (!isNaN(parseInt(words[i])))
                    MM_PluginVersion = words[i];
            }
            MM_FlashCanPlay = MM_PluginVersion >= MM_contentVersion;
        } else if (navigator.userAgent && navigator.userAgent.indexOf("MSIE") >= 0
									     && (navigator.appVersion.indexOf("Win") != -1)) {
            try {
                var o = new ActiveXObject("ShockwaveFlash.ShockwaveFlash." + MM_contentVersion);
                MM_FlashCanPlay = o != null;
            } catch (e) {
            }
        }
        return MM_FlashCanPlay;
    }

    this.CloseDialog = function() {
        if (window.opener && !window.opener.closed) {
            window.opener.focus();
            window.close();
        } else if (window.parent) {
            window.parent.jQuery("#UrlDialog").dialog('close');
        }
    }

    this.DisableInputWhileProcessing = function(delay, dialogSelector) {
        jQuery(function() {
            // we use a modal dialog to block input, but we should check out the jQuery block plugin ...
            // note that the dialog content must be predefined - it can not be genrated dynamically as it is
            // to be displayed on the form submit event
            function DisableInput() {
                jQuery(dialogSelector).dialog({
                    modal: true,
                    resizable: false,
                    draggable: false,
                    dialogClass: "DisableInputWhileProcessingDialog"
                });
            }

            jQuery("form").each(function() {
                // note: We do not use the jQuery "submit" event because it does not fire for asp (Javascript) postbacks
                var theForm = this;
                var currentSubmitFunction = theForm.onsubmit;

                function SubmitHook() {
                    var ok = true;
                    if (theForm != null) {
                        if (currentSubmitFunction != null && typeof (currentSubmitFunction) == "function") {
                            with (theForm)
                                ok = currentSubmitFunction();
                        }
                        if (ok) {
                            setTimeout(DisableInput, delay);
                            theForm = null; // to avoid possible GC error in IE
                        }
                    }
                    return ok;
                }

                this.onsubmit = SubmitHook;
            }
            );
        });
    }

    // Execute callback and if requested close current window when done.
    // This method both handles interaction between browser windows and ThickBox instances.
    this.ExecuteCallback = function(callback, doClose) {
        if (window.opener && !window.opener.closed) {
            try {
                window.opener.eval(callback);
            } catch (e) {
                alert("Error: Caller does not implement callback " + callback);
            }
        } else if (window.parent) {
            try {
                window.parent.eval(callback);
            } catch (e) {
                alert("Error: Parrent does not implement callback " + callback);
            }
        }
        if (doClose)
            this.CloseDialog();
    }

    // Build url by adding parameters to base urls.
    // The urlParams must be a list of paramater name and value pairs.
    // Eksamles of legal calls:
    //      BuildUrl("page.aspx")
    //      BuildUrl("page.aspx","p1",value1,"p2",value2)

    this.BuildUrl = function(baseUrl, urlParams) {
        var sep = baseUrl.indexOf('?') >= 0 ? '&' : '?';
        for (var i = 1; i < arguments.length; i += 2) {
            baseUrl += sep + arguments[i] + '=' + escape(arguments[i + 1]);
            sep = '&';
        }
        return baseUrl;
    }

    this.jQueryExist = function() {
        return typeof jQuery == "function";
    }

    this.useJQ = function() {
        return this.jQueryExist();
    }

    this.dialogNumber = 0;

    this.ShowDialog = function(diaTitle, diaText, diaClass, diaButtons) {
        var diaId = "DynamicDialog_" + (this.dialogNumber++);
        jQuery("body").append("<div id='" + diaId + "'><div class='" + diaClass + "'>" + diaText + "</div></div>");
        var dia = jQuery("#" + diaId);
        var options =
            {
                bgiframe: true,
                modal: true,
                autoOpen: true,
                closeOnEscape: true,
                resizable: false,
                minWidth: 0,
                minHeight: 0,
                title: diaTitle,
                close: function(event, ui) {
                    dia.dialog('destroy');
                    dia.remove();
                }
            };
        if (diaButtons)
            options.buttons = diaButtons;
        dia.dialog(options);
        return dia;
    }

    this.ShowErrorDialog = function(message) {
        var buttons = {};
        buttons[this.ResourceStrings.OkButton] = function() { jQuery(this).dialog('close'); };
        this.ShowDialog(this.ResourceStrings.ErrorTitle, message, "ErrorDialog", buttons)
    }

    this.ShowConfirmDialog = function(message, okFunction) {
        var buttons = {};
        buttons[this.ResourceStrings.OkButton] = function() { jQuery(this).dialog('close'); if (okFunction) okFunction(); };
        buttons[this.ResourceStrings.CancelButton] = function() { jQuery(this).dialog('close'); };
        this.ShowDialog(this.ResourceStrings.ConfirmTitle, message, "ConfirmDialog", buttons)
    }

    this.ShowInfoDialog = function(message, closeFunction) {
        var buttons = {};
        buttons[this.ResourceStrings.OkButton] = function() { jQuery(this).dialog('close'); if (closeFunction) closeFunction(); };
        this.ShowDialog(this.ResourceStrings.InfoTitle, message, "InfoDialog", buttons)
    }

    this.IsIE6 = function() {
        return jQuery.browser.msie && (jQuery.browser.version == "6.0") && !window.XMLHttpRequest;
    }

    // Open centered dialog window showing the defined url.
    // If the width and/or height parameters are unspecified default sizes are used.
    this.OpenDialog = function(url, width, height, callbackOnClose) {
        if ((!width) || width < 1) width = jQuery(window).width() - 100;
        if ((!height) || height < 1) height = jQuery(window).height() - 100;

        var IsIE6 = this.IsIE6();

        if (!IsIE6) {

            jQuery("body").append("<div id='UrlDialog'></div>");
            var dia = jQuery("#UrlDialog");

            dia.append('<iframe id="UrlDialogIFrame" style="display:none;" marginheight="0" marginwidth="0" frameborder="0" src="' + url + '"></iframe>');

            var theIFrame = jQuery('#UrlDialogIFrame');
            var delayResize = true;  // jQuery.browser.msie;

            function DoResize() {
                theIFrame.show();
                theIFrame.height(dia.height());
                theIFrame.width(dia.width());
            }

            theIFrame.load(function() {
                DoResize();
            });

            var options =
            {
                bgiframe: true,
                modal: true,
                autoOpen: true,
                closeOnEscape: false,
                resizable: true,
                minHeight: 0,
                minWidth: 0,
                height: height + 40,
                width: width + 30,
                resizeStart: function() { theIFrame.hide(); },
                dragStart: function() { theIFrame.hide(); },
                dragStop: function() { theIFrame.show(); },
                close: function() {
                    dia.dialog('destroy');
                    dia.remove();
                    if (callbackOnClose) eval(callbackOnClose);
                }
            };

            if (delayResize) {
                options.resizeStop = DoResize;
            } else {
                options.resize = DoResize;
            }

            dia.dialog(options);

            return window; // hm ..
        } else {
            var left = (screen.width - width) / 2;
            var top = (screen.height - height) / 2;
            var params = 'width=' + width + ', height=' + height;
            params += ', top=' + top + ', left=' + left;
            params += ', directories=no';
            params += ', location=no';
            params += ', menubar=no';
            params += ', resizable=yes';
            params += ', scrollbars=yes';
            params += ', status=no';
            params += ', toolbar=no';
            var newwin = window.open(url, 'dialog', params);

            if (callbackOnClose) {
                var timer = null;
                function DetectClose() {
                    if (newwin.closed) {
                        window.clearInterval(timer);
                        eval(callbackOnClose);
                    }
                }
                timer = window.setInterval(DetectClose, 1000);
            }

            if (newwin.focus)
                newwin.focus();
            return newwin;
        }
    }

    this.DateSelectedTarget = null;

    this.DateSelectedCallback = function(date) {
        Papirfly().DateSelectedTarget.attr('value', date);
        Papirfly().MarkAsChanged(Papirfly().DateSelectedTarget);
    }

    // Build calendard url.
    // If p2 is NOT supplied, p1 must be the ID of the element containing the date to be modified.
    // If p2 is supplied, p1 must contain the currently selected date and p2 a callback method.
    // The date format to use is the format produced bye the C# DateTime.ToShortDateString() method.
    this.BuildCalendarUrl = function(url, p1, p2) {
        var callback;
        var selectedDate;
        if (p2) {
            selectedDate = p1;
            callback = p2;
        } else {
            Papirfly().DateSelectedTarget = jQuery('#' + p1);
            selectedDate = Papirfly().DateSelectedTarget.attr('value');
            callback = "Papirfly().DateSelectedCallback";
        }

        url += "?SelectedDate=" + selectedDate + "&CB=" + callback;
        return url;
    }

    // Open calendar dialog.
    // If p2 is NOT supplied, p1 must be the ID of the element containing the date to be modified.
    // If p2 is supplied, p1 must contain the currently selected date and p2 a callback method.
    // The date format to use is the format produced bye the C# DateTime.ToShortDateString() method.
    this.OpenCalendar = function(url, p1, p2) {
        return this.OpenDialog(this.BuildCalendarUrl(url, p1, p2), 180, 180);
    }

    // Call this function from body onload when you want to ensure that the session lives 
    // even if the user is passive for a longer periode than the session timeout. Don't missuse!
    //
    // The method uses AJAX to regulary poll the server ensuring the session is keept alive.
    // pathPrefix: Path prefix identifying the site root. 
    //
    // Example: If you call this method from a page "mypages/mypage1.aspx", use the following code:
    //    <head>
    //        <script language="JavaScript1.2" type="text/JavaScript" src="../jscript/papirfly.js" ></script>
    //    </head>
    //    <body onload="Papirfly().KeepSessionAlive('..');">
    //    ...
    this.KeepSessionAlive = function(pathPrefix) {
        var ka = new KeepAlive(pathPrefix);
        setTimeout(ka.Run, 5 * 60 * 1000);
    }

    this.KeepSessionAliveCount = 0;

    function KeepAlive(pathPrefix) {
        this.Run = function() {
            window.status = 'KeepSessionAlive #' + (++Papirfly().KeepSessionAliveCount);
            var httpReq = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
            httpReq.onreadystatechange = function() {
                if (httpReq.readyState == 4 && httpReq.status == 200) {
                    Papirfly().KeepSessionAlive(pathPrefix);
                }
            };

            httpReq.open("GET", pathPrefix + "/common/KeepSessionAlive.ashx", true);
            httpReq.send(null);
        };
    }

    function hasClass(obj) {
        var c = obj.getAttributeNode("class");
        return c != null ? c.value : false;
    }

    // Set alternating row background colors for given table
    // The background color is set on each td, but only if the td has no css class and no background style set
    this.Stripe = function(id, evenColor, oddColor) {
        var table = document.getElementById(id);
        if (!table)
            return;

        if (!evenColor)
            evenColor = "#ffffff";
        if (!oddColor)
            oddColor = "#e0e0e0";

        var even = false;
        var trs = table.rows;

        for (var i = 0; i < trs.length; i++) {
            var color = even ? evenColor : oddColor;
            even = !even;
            var mytr = trs[i];

            if (!hasClass(mytr) && !mytr.style.backgroundColor) {
                var tds = mytr.cells;
                for (var j = 0; j < tds.length; j++) {
                    var mytd = tds[j];
                    if (!hasClass(mytd) && !mytd.style.backgroundColor) {
                        mytd.style.backgroundColor = color;
                    }
                }
            }
        }
    }

    // Set all checkboxes with given name to given value
    this.SetCheckboxes = function(checkboxName, checked) {
        var cbs = document.getElementsByName(checkboxName);
        if (!cbs) return;
        for (var i = 0; i < cbs.length; i++) {
            var cb = cbs[i];
            if (!cb.disabled)
                cb.checked = checked;
        }
    }

    // Get the number of checked and enabled checkboxes with given name
    this.GetSelectedCount = function(checkboxName) {
        var count = 0;
        var cbs = document.getElementsByName(checkboxName);
        if (cbs) {
            for (var i = 0; i < cbs.length; i++) {
                var cb = cbs[i];
                if (cb.checked && !cb.disabled) {
                    count++;
                }
            }
        }
        return count;
    }

    // Get the values, as comma separated list, of all checked and enabled checkboxes with given name
    this.GetSelectedValues = function(checkboxName) {
        var result = "";
        var cbs = document.getElementsByName(checkboxName);
        if (cbs) {
            var sep = "";
            for (var i = 0; i < cbs.length; i++) {
                var cb = cbs[i];
                if (cb.checked && !cb.disabled) {
                    result += sep + cb.value;
                    sep = ",";
                }
            }
        }
        return result;
    }

    this.OpenWindow = function(url, width, height) {
        var top = (window.screen.availHeight - height) / 2;
        var left = (window.screen.availWidth - width) / 2;
        var features = "top=" + top + ",left=" + left + ",width=" + width + ",height=" + height +
            ",resizable=1,scrollbars=0,status=0";
        var w = window.open(url, "_blank", features);
        w.focus();
        return w;
    }

    this.ShowDropDownList = function(id) {
        var opener = jQuery('#' + id + '_opener');
        var selector = jQuery('#' + id + '_selector');
        selector.css("top", opener.position().top);
        selector.css("left", opener.position().left);
        //opener.hide();
        selector.slideDown("fast");
        jQuery(document).bind("mousedown.papirfly", function(e) {
            var offset = selector.offset();
            if (e.pageX < offset.left || e.pageX > (offset.left + selector.width()) ||
                e.pageY < offset.top || e.pageY > (offset.top + selector.height()))
                Papirfly().HideDropDownList(id);
            return true;
        });
    }

    this.HideDropDownList = function(id) {
        var opener = jQuery('#' + id + '_opener');
        var selector = jQuery('#' + id + '_selector');
        selector.hide();
        //opener.show();
        jQuery(document).unbind("mousedown.papirfly");
    }

    this.SelectDropDownItem = function(id, value) {
        var opener = jQuery('#' + id + '_opener');
        var dropdown = jQuery('#' + id);
        dropdown.val(value);
        opener.html(dropdown.find(":selected").text());
        this.HideDropDownList(id);
        dropdown.change();
    }
}

var _papirfly = null;

function Papirfly() {
    if (_papirfly == null) {
        _papirfly = new PapirflyObject();
    }
    return _papirfly;
}

