/* Globals
 * We've generated the following variables to help us out:
 * var CaseEventEdit_sPluginPrefix
 * var CaseEventEdit_sActionToken
 * var CaseEventEdit_sRawPageURL
 * var CaseEventEdit_sHTMLIconAttach
 * var CaseEventEdit_sHTMLIconTrash
 * var CaseEventEdit_sHTMLIconLoading
 * var CaseEventEdit_fBulkEdit
 */

//Bulk Edit Disabler:
//If I'm bulk-editing, get rid of the edit icons.
//All sorts of crazy stuff can happen, so we must do this continuously. 
//ATM, this must be done in Javascript.
$(document).ready(function() {
    if (typeof(CaseEventEdit_fBulkEdit) != "undefined" && CaseEventEdit_fBulkEdit) {
        setInterval(function() {
            $('[id^=CaseEventEditIcon]', '#bugEventHistory').remove();
            $('[id^=CaseEventEditIcon]', '#BugEvents').remove();
        }, 50);
    }
});

//CaseEventEdit Class:
var CaseEventEdit = CaseEventEdit ? CaseEventEdit : function() {

    //Private members:

    var Historian = ConstructHistorian();
    var Editor = ConstructEditor();
    var hashBEToEdit = {}; //dictionary of bugevent : ixedit.
    var sREQUESTFAIL = 'Server not responding... Refreshing this page might help.';

    //Public accessors:
    var members = {
        MakeEditable: Editor.MakeEditable,
        Edit: Editor.EditCaseEvent,

        ToggleHistory: Historian.ToggleHistory,
        ShowHistory: Historian.ShowHistory,
        HideHistory: Historian.HideHistory,
        RefreshHistoryFromSelectObj: Historian.RefreshHistoryFromSelectObj,

        ScrollTo: ScrollTo,
        hash: hashBEToEdit,
        FixImages : FixImages
    };

    //Utilities:
    function Prefix(s) {
        return '&' + CaseEventEdit_sPluginPrefix + s;
    }

    function ScrollTo(sID) {
        try {
            if (!CaseEventEdit_fBulkEdit) {
                $(window).scrollTop($('#' + sID).position().top);
            }
            else {
                $('#' + sID).parents('.itemBody').scrollTop($('#' + sID).position().top);
            }
        }
        catch (e) {
        }
    }
    function FixImages() {
        try {
            shrinkImagesToFit();
        }
        catch (e) {
        }
    }

    function ParseData(sResponse) {
        var o = $(sResponse);
        if ($.browser.msie) {
            return o.attr('sData'); //IE is happy to read the attributes, but won't read .text() or xml.
        }
        return o.text(); //FF/Chrome are happy to read xml or preserve spaces via .text(), but won't respect spaces in attributes.
    }

    //Editor Constructor
    function ConstructEditor() {

        //Public members:
        var members = {
            MakeEditable: MakeEditable,
            EditCaseEvent: EditCaseEvent,
            GetInsertionPoint: GetInsertionPoint
        };

        //Private members:

        var sSAVING = 'Saving...';
        var hashForceRefresh = {};
        var hashAttach = {}; //dictionary of bugevent : { sFileName : ixFile }
        var hashFileToAttach = {}; //Dictionary of ixFile -> ixAttachment

        //Uploader class
        var Uploader = function() {
            var c = 0;
            var rgFxn = {};
            var cRgFxn = 0;

            return {
                UploadStart: function() { c++; },
                UploadComplete: function() {
                    c--;
                    if (c === 0) {
                        for (var ix = 0; ix < cRgFxn; ix++) {
                            rgFxn[ix]();
                        }
                        cRgFxn = 0;
                        rgFxn = {};
                    }
                },
                OnNotUploading: function(fxn) {
                    if (c === 0) {
                        fxn(); //If all uploads are complete, callback immediately.
                    }
                    else {
                        rgFxn[cRgFxn++] = fxn; //else, enqueue
                    }
                }
            };

        } ();

        //Function with private variable
        var GetUID = function() {
            var cUID = 1; // Start at 1, since 0 == false, which can suck.    
            return function() { return cUID++; };
        } ();

        function MakeEditable(ixBugEvent) {
            //This function will apply markup to a bugevent body so that it becomes editable.

            var body = $('#bugeventBody_' + ixBugEvent);

            //Place an empty span before the body text which we can use as an insertion point.
            body.before('<span id="CaseEventEditInsertionPoint' + ixBugEvent + '"></span>');

            //Initialize its attachment hash.
            hashAttach[ixBugEvent] = {};
        }

        function SetAttachmentButtonText(ixBugEvent, sText) {
            $('#BugEventAttachText' + ixBugEvent).text(sText);
        }

        function RemoveFile(ixFile, ixBugEvent) {
            var divFile = $('#CaseEventEdit_File' + ixFile);
            var sFile = divFile.attr('value');

            var cRemaining = $('#BugEventAttach' + ixBugEvent).children().length;

            divFile.stop().hide('blind', {}, 100, function() { $(this).remove(); });
            hashAttach[ixBugEvent][sFile] = undefined;

            if (cRemaining === 1) {
                //If we just removed the last attachment, revert our attachment text.
                SetAttachmentButtonText(ixBugEvent, 'Attach a file');
            }
        }

        function AddAttachment(ixBugEvent, sFile) {

            var spanAttach = $('#BugEventAttach' + ixBugEvent);
            if (sFile != '') {
                if (hashAttach[ixBugEvent] !== undefined && hashAttach[ixBugEvent][sFile] !== undefined) {
                    //We already have this file.
                    $('#CaseEventEdit_File' + hashAttach[ixBugEvent][sFile]).stop(true, true).effect('highlight', {}, 2000);
                    return false;
                }

                var ixFile = GetUID();

                var aRemove = $('<a href="javascript: void 0;">' + CaseEventEdit_sHTMLIconTrash + '</a>');
                aRemove.click(function() { RemoveFile(ixFile, ixBugEvent); });

                var divFile = $('<div style="margin-top:0.8em; padding-right:0.4em;" value="' + sFile + '" id="CaseEventEdit_File' + ixFile + '"><span id="CaseEventEdit_FileLoading' + ixFile + '">' + CaseEventEdit_sHTMLIconLoading + '</span>' + sFile + ' </div>');
                divFile.append(aRemove);

                spanAttach.append(divFile);

                SetAttachmentButtonText(ixBugEvent, 'Attach another file');

                if (hashAttach[ixBugEvent] === undefined) {
                    hashAttach[ixBugEvent] = {};
                }
                hashAttach[ixBugEvent][sFile] = ixFile;
                Uploader.UploadStart();
                return true;
            }
        }

        function SetEditSaving(ixBugEvent, fSaving) {
            if (fSaving) {
                $('#TextSaveEditEvent' + ixBugEvent).attr('value', sSAVING);
            }
            else {
                $('#TextSaveEditEvent' + ixBugEvent).attr('value', 'OK');
            }
        }
        function IsEditSaving(ixBugEvent) {
            return ($('#TextSaveEditEvent' + ixBugEvent).attr('value') == sSAVING);
        }
        function GetInsertionPoint(ixBugEvent) {
            var ip = $('#CaseEventEditInsertionPoint' + ixBugEvent);
            if (ip.length == 0) {
                MakeEditable(ixBugEvent);
                ip = $('#CaseEventEditInsertionPoint' + ixBugEvent);
            }
            return ip;
        }

        /****************************
        *  Editing Ajaxy functions: *
        ****************************/

        function EditCaseEvent(ixBugEvent) {
            if (CaseEventEdit_fBulkEdit) {
                $('#CaseEventEditIcon' + ixBugEvent).after('<div class="errorLabel">Cannot edit events while Bulk Editing cases</div>').remove();
                return;
            }

            var oInsertionPoint = GetInsertionPoint(ixBugEvent);
            if (oInsertionPoint.attr('editing') == 'true') {
                return;
            }

            //Mark us as editing.
            oInsertionPoint.attr('editing', 'true');
            //Hide the 'edit' icon and show the throbber.
            $('#CaseEventEditIcon' + ixBugEvent).children().toggle();

            var fxn = function(sHTML, status) {
                var sData = ParseData(sHTML);
                var sErr = '';
                var fOK = $(sHTML).attr('fOK') == 1;
                if (!fOK) {
                    //Couldn't load the case.
                    sData = ''; //Don't say 'undefined'.
                    sErr = $(sHTML).attr('sMsg');
                    if (!sErr) { sErr = sREQUESTFAIL; }
                }

                var sPaddingButNotInIE = $.browser.msie ? '' : 'style = "padding-right:0.4em;"';
                var sAttachmentButton = '<div ' + sPaddingButNotInIE + '>';
                sAttachmentButton += '    <span style="padding:2px 0px 2px 2px;" id="BugEventAttachButton' + ixBugEvent + '">';
                sAttachmentButton += '        <a class="dotted" href="javascript: void 0;" id="BugEventAttachText' + ixBugEvent + '">';
                sAttachmentButton += '            Attach a file';
                sAttachmentButton += '        </a>';
                sAttachmentButton += '        <span style="padding-left:4px;" href="javascript: void 0;">';
                sAttachmentButton += '            ' + CaseEventEdit_sHTMLIconAttach;
                sAttachmentButton += '        </span>';
                sAttachmentButton += '    </span>';
                sAttachmentButton += '</div>';

                var dialog = '<div id="BugEventTextNew' + ixBugEvent + '" class="body editable">';
                dialog += '    <div><div class="summary" style="padding:4px; border-bottom:#adaba8 1px solid; background-color:#f2f0ea;" ><div class="action">Edit Case Event</div><span id="BugEventTextError' + ixBugEvent + '" class="errorLabel">' + sErr + '</span></div></div>';
                dialog += '    <div style="padding-right: 4px; margin: -1px 0px; overflow:hidden;"><textarea id="BugEventTextArea' + ixBugEvent + '" style="resize:none; width: 100%; border: 0px none; padding: 2px;" onkeyup="adjustRows(this);" rows="8" cols="85" wrap="virtual" ></textarea></div>';
                dialog += '    <div class="footer">';
                dialog += '        ' + (fOK ? '<input class="actionButton2 dlgButton" id="TextSaveEditEvent' + ixBugEvent + '" type="submit" value="OK"/>' : '') + '';
                dialog += '        <input class="actionButton2 dlgButton" id="TextCancelEditEvent' + ixBugEvent + '"  type="button" value="Cancel"/>';
                dialog += '        <div style="text-align:right; margin-top:0.4em;">';
                dialog += '            ' + sAttachmentButton + '';
                dialog += '            <div id="BugEventAttach' + ixBugEvent + '" />';
                dialog += '        </div>';
                dialog += '    <div class="fixAlignment"></div>';
                dialog += '    </div>';
                dialog += '</div>';

                $(dialog).hide().insertAfter(oInsertionPoint).children('#BugEventTextArea' + ixBugEvent);

                //Set the textarea's data. We have to do this after the preceeding DOM insertion, else it mysteriously fails.
                var textarea = $('#BugEventTextArea' + ixBugEvent);
                textarea.val(sData);

                //Show
                $('#BugEventTextNew' + ixBugEvent).slideDown(200, function() { // use slideDown instead of blind because of FF/Chrome graphical glitchs.
                    //Now that we've finished loading it, disable the 'loading' graphic and hide the edit icon.
                    $('#CaseEventEditIcon' + ixBugEvent).children().toggle();
                    $('#CaseEventEditIcon' + ixBugEvent).hide();
                });

                //Activate buttons:
                $('#TextSaveEditEvent' + ixBugEvent).click(function() { EditSave(ixBugEvent); return false; });
                $('#TextCancelEditEvent' + ixBugEvent).click(function() { EditStop(ixBugEvent); return false; });

                var sURLUploadFile = CaseEventEdit_sRawPageURL + Prefix('action=uploadFile');
                new AjaxUpload('BugEventAttachButton' + ixBugEvent, {
                    hoverClass: 'CaseEventEditUploadHover',
                    action: sURLUploadFile,
                    name: CaseEventEdit_sPluginPrefix + 'sFile',
                    autoSubmit: true,
                    responseType: false,
                    onChange: function(sFile, extension) { return AddAttachment(ixBugEvent, sFile); },
                    onSubmit: function(file, extension) { },
                    onComplete: function(sFile, response) {
                        if ($(response).attr('fOK')) {
                            hashFileToAttach[hashAttach[ixBugEvent][sFile]] = ParseData(response);
                        }
                        else {
                            var sErr = $(response).attr("sMsg");
                            if (sErr === undefined) {
                                sErr = "The server failed to receive the file- Perhaps the attachment is too large?";
                            }
                            $('#BugEventTextError' + ixBugEvent).html("There was a problem uploading " + sFile + ":<br/>" + sErr).effect('highlight', {}, 2000);
                            RemoveFile(hashAttach[ixBugEvent][sFile], ixBugEvent);
                        }
                        Uploader.UploadComplete();
                        $('#CaseEventEdit_FileLoading' + hashAttach[ixBugEvent][sFile]).fadeOut('slow', function() { $(this).remove(); });
                    }
                });

            };

            var sURL = CaseEventEdit_sRawPageURL;
            var ixEdit = hashBEToEdit[ixBugEvent];
            if (ixEdit === undefined) {
                ixEdit = -1;
            }

            sURL += Prefix('action=getEditS');
            sURL += Prefix('ixBugEvent=' + encodeURIComponent(ixBugEvent));
            sURL += Prefix('ixEdit=' + encodeURIComponent(ixEdit));

            jQuery.get(sURL, fxn);
        }

        function EditSave(ixBugEvent) {
            //If already saving, back off.
            if (IsEditSaving(ixBugEvent)) {
                return;
            }
            //Mark that we're working
            SetEditSaving(ixBugEvent, true);

            var textAreaNew = $('#BugEventTextArea' + ixBugEvent);

            //If save is a failure, notice it.
            var fxnFail = function(ixEditLatest, sErr, status) {

                if (sErr == 'Nothing Changed') {
                    //Tried to save something which was the same as the current version.
                    //Although effectively a 'cancel', we want to do a force refresh so as to have consistent side-effects. (like, say, with history.)
                    hashForceRefresh[ixBugEvent] = true;
                    EditStop(ixBugEvent);
                    return;
                }
                if (sErr == 'Newer') {
                    //The case has been edited since we last viewed it.

                    //Show the diff between our current and the latest version,
                    Historian.ShowHistory(ixBugEvent, ixEditLatest, hashBEToEdit[ixBugEvent]);
                    //Display warning text explaining what's going on,
                    $('#BugEventTextError' + ixBugEvent).text("The changes above were submitted while you were editing. They will be overwritten unless you incorporate them into your own changes.").stop(true, true).effect('highlight', {}, 2000);
                    //Mark a force refresh (So that we get the latest bugevent if we cancel the edit.)
                    hashForceRefresh[ixBugEvent] = true;
                    //Take note not to bounce again
                    hashBEToEdit[ixBugEvent] = ixEditLatest;
                    //Continue editing
                    SetEditSaving(ixBugEvent, false);
                    return;
                }

                if (!sErr) {
                    sErr = sREQUESTFAIL;
                }
                $('#BugEventTextError' + ixBugEvent).text("Unable to save edit: " + sErr);
            };

            //If save is successful, refresh the bugevent edited and add the new one.
            var fxnOK = function(ixBugEventCreated) {
                var fxn = function(sData, status) {
                    if ($(sData).attr('fOK') != 1) {
                        EditStop(ixBugEvent);
                        $('#bugeventBody_' + ixBugEvent).html('<em>Although the edit was saved successfully, Case Event Edit had a problem displaying the changes. The latest text will be available if you <strong>refresh this page</strong>.  If this message appears with any frequency, please report the issue to Fog Creek Software.<br/><br/>Error message:[' + $(sData).attr('sMsg') + ']<br/>AjaxStatus:[' + status + ']</em>');
                        return;
                    }
                    //Rerender the updated bugevent.
                    //This will set hashBEToEdit[ixBugEvent] for us.
                    $('#bugeventBody_' + ixBugEvent).parent('.bugevent').after($(sData).attr('sData0')).remove();
                    $('#bugeventBody_' + ixBugEvent).effect('highlight', {}, 2000);

                    //Insert the new bugevent.
                    var sRenderLatest = $(sData).attr('sData1');
                    //Test for' fMostRecentEventFirst == 1' explicitally, since we don't know the 'false' value.
                    var insert = fMostRecentEventFirst == 1 ? $('#newBugEventInsertBelowPoint') : $('#newBugEventInsertAbovePoint');
                    //If insert has no children, create one.  Else, add to the existing set.
                    if (insert.children().length == 0) {
                        insert.append(sRenderLatest);

                    }
                    else {
                        if (fMostRecentEventFirst == 1) {
                            insert.children(':first').before(sRenderLatest);
                        }
                        else {
                            insert.children(':last').after(sRenderLatest);
                        }
                    }
                    CaseEventEdit.FixImages();
                    
                    //If we're still viewing the latest ixBugEvent (with no gaps), disable the warning scripts:
                    if ($(sData).attr('sData2') == 1) {
                        ixBugEventLatest = ixBugEventCreated;
                        $('input[name="ixBugEventLatest"]').val(ixBugEventLatest);
                    }

                    //If we were forcing a refresh... stop.  We just did.
                    hashForceRefresh[ixBugEvent] = false;
                };
                var sURL = CaseEventEdit_sRawPageURL;
                sURL += Prefix('action=minimalUpdates');
                sURL += Prefix('ixBugEventThoughtLatest=' + encodeURIComponent(ixBugEventLatest));
                sURL += Prefix('ixBugEventEdited=' + encodeURIComponent(ixBugEvent));
                sURL += Prefix('ixBugEventCreated=' + encodeURIComponent(ixBugEventCreated));
                jQuery.get(sURL, fxn);
            };

            //I'm a continuation!
            var continuation = function() {

                var sRgIxAttachment = '';
                for (var sFile in hashAttach[ixBugEvent]) {
                    var ixAttachment = hashFileToAttach[hashAttach[ixBugEvent][sFile]];
                    if (ixAttachment !== undefined) {

                        if (sRgIxAttachment !== '') {
                            sRgIxAttachment += ',';
                        }
                        sRgIxAttachment += ixAttachment;
                    }
                }

                ajaxEditSave(textAreaNew.val(), sRgIxAttachment, ixBugEvent, fxnOK, fxnFail);
            };
            //If there are files uploading, wait for them.

            Uploader.OnNotUploading(continuation);


        }

        function ajaxEditSave(s, sRgIxAttachment, ixBugEvent, fxnOK, fxnFail) {
            var fxn = function(sAJAX, status) {
                var fxnArg = function(s) { return $(sAJAX).attr(s); };

                if (fxnArg('fOK') == 1) {
                    fxnOK(fxnArg('sData1'));
                }
                else {
                    fxnFail(fxnArg('sData0'), fxnArg('sMsg'), status);
                }
            };
            var ixEditCurrent = hashBEToEdit[ixBugEvent];
            if (ixEditCurrent === undefined) {
                ixEditCurrent = -1;
            }

            var sData = Prefix('action=save');
            sData += Prefix('s=' + encodeURIComponent(s));
            sData += Prefix('sRgIxAttachment=' + encodeURI(sRgIxAttachment));
            sData += Prefix('ixBugEvent=' + encodeURIComponent(ixBugEvent));
            sData += Prefix('ixEditCurrent=' + encodeURIComponent(ixEditCurrent));
            sData += Prefix('actionToken=' + CaseEventEdit_sActionToken);
            $.ajax({ type: "POST",
                url: CaseEventEdit_sRawPageURL,
                success: fxn,
                data: sData
            });
        }

        function EditStop(ixBugEvent) {
            //Display the normal bugevent view:
            if (hashForceRefresh[ixBugEvent]) {
                //Re-render
                hashForceRefresh[ixBugEvent] = false; //Only try once.

                var fxn = function(sData, status) {
                    if ($(sData).attr('fOK') != 1) {
                        //If failure, don't bother with a rendering- just restore the old stuff.
                        //This acts like a 'cancel'...  History will remain open, and the edit box will slide closed.
                        EditStop(ixBugEvent);
                        return;
                    }

                    //The rendering will set hashBEToEdit[ixBugEvent] and hashAttach for us.
                    $('#bugeventBody_' + ixBugEvent).parent('.bugevent').after(ParseData(sData)).remove();
                    $('#bugeventBody_' + ixBugEvent).effect('highlight', {}, 2000);
                    CaseEventEdit.FixImages();

                };
                var sURL = CaseEventEdit_sRawPageURL;
                sURL += Prefix('action=renderBugEvent');
                sURL += Prefix('ixBugEvent=' + encodeURIComponent(ixBugEvent));
                jQuery.get(sURL, fxn);
            }
            else {
                //Remove the editing dialog:
                $('#BugEventTextNew' + ixBugEvent).hide('blind', {}, 200, function() {
                    $(this).remove();
                });

                //Just make the old rendering visible again
                var insertion = GetInsertionPoint(ixBugEvent);
                insertion.attr('editing', 'false');
                $('#CaseEventEditIcon' + ixBugEvent).show();

                //Blank the attachments queue:
                hashAttach[ixBugEvent] = undefined;
            }
        }

        return members;
    }

    //Historian constructor
    function ConstructHistorian() {

        //Public Members:
        var members = {
            ToggleHistory: ToggleHistory,
            ShowHistory: ShowOrRefreshHistory,
            HideHistory: HideHistory,
            RefreshHistoryFromSelectObj: RefreshHistoryFromSelectObj
        };

        //Private members:

        function SetHistoryLoading(ixBugEvent, fLoading) {
            if (fLoading) {
                $('#EventHistory' + ixBugEvent).text('[Loading]');
            }
            else {
                $('#EventHistory' + ixBugEvent).html($('#EventHistory' + ixBugEvent).attr('value'));
            }
        }

        function DLCLHistory(ixBugEvent) {
            DropListControl.refreshWithin($('#CaseEventEditHistory' + ixBugEvent));
        }

        var FlashHistory = function(ixBugEvent) {
            $('#CaseEventEditHistory' + ixBugEvent).stop(true, true).effect('highlight', {}, 2000);
        };

        function HideHistory(ixBugEvent) {
            $("#CaseEventEditHistory" + ixBugEvent).stop(true).effect('blind', {}, 'fast', function() {
                $(this).remove();
                $("#EventHistory" + ixBugEvent).attr('fShowing', 0);
            });
        }

        /****************************
        *  History Ajaxy functions: *
        ****************************/

        function ShowOrRefreshHistory(ixBugEvent, ixEditNew, ixEditOld) {
            if ($('#EventHistory' + ixBugEvent).attr('fShowing') == 1) {
                //refresh
                if (ixEditOld === undefined) {
                    ixEditOld = -1;
                }
                RefreshHistory(ixBugEvent, ixEditNew, ixEditOld);
            }
            else {
                //show
                ToggleHistory(ixBugEvent, ixEditNew, ixEditOld, true);
            }
        }

        function CleanDataForIE(sData) {
            //IE8 MEGAHACK, (because white-space:pre-wrap is not respected, and whitespace is collapsed upon DOM insertion):
            //If not inside a <>, convert all spaces to &ensp;'s.
            if ($.browser.msie) {
                var sOut = '';
                var cLB = 0;
                for (var ixData = 0, lenData = sData.length; ixData < lenData; ixData++) {
                    var ch = sData.charAt(ixData);
                    if (ch == '<') {
                        cLB++;
                    }
                    else if (ch == '>' && cLB > 0) {
                        cLB--;
                    }
                    else if (ch === ' ') {
                        if (cLB === 0) {
                            ch = "&ensp;"
                        }
                    }

                    sOut += ch;
                }
                return sOut;
            }
            return sData;
        }

        function ToggleHistory(ixBugEvent, ixEditNew, ixEditOld, fFlash) {

            //Until the history is fixed for Safari, give an error rather than puking.        	
            if (window.safari) {
                $('#EventHistory' + ixBugEvent).html('<strong>[Unavailable in Safari]</strong>').stop(true, true).effect('highlight', {}, 5000);
                return;
            }
        
            //If already exists, hide
            if ($('#EventHistory' + ixBugEvent).attr('fShowing') == 1) {
                HideHistory(ixBugEvent);
                return;
            }
            $("#EventHistory" + ixBugEvent).attr('fShowing', 1);

            //Mark us as loading:
            SetHistoryLoading(ixBugEvent, true);


            //Get diff:
            var fxn = function(sAJAX, status) {
                var sData;
                if ($(sAJAX).attr('fOK') == 1) {
                    sData = ParseData(sAJAX);
                }
                else {
                    sData = $(sAJAX).attr('sMsg');
                    if (!sData) { sData = sREQUESTFAIL; }
                }

                //Create and show the history:
                var sHistory = '<div id="CaseEventEditHistory' + ixBugEvent + '" style="display:none; margin-top:2px; border:#adaba8 1px solid;"></div>';
                Editor.GetInsertionPoint(ixBugEvent).before(sHistory);
                $('#CaseEventEditHistory' + ixBugEvent).html(CleanDataForIE(sData)).slideDown('fast'); // use slideDown instead of blind because of an IE8 graphical glitch.
                DLCLHistory(ixBugEvent);

                //Remove the 'loading' notification.
                SetHistoryLoading(ixBugEvent, false);
                if (fFlash) {
                    FlashHistory(ixBugEvent);
                }

            };

            if (ixEditNew === undefined) {
                ixEditNew = hashBEToEdit[ixBugEvent];
                //if ixEditNew is still undefined at this point, ajaxGetDiff is going to fail.
                //If a history view is visible, however, it should not be undefined unless we're in Bulk Edit mode.
                if (CaseEventEdit_fBulkEdit && ixEditNew === undefined) {
                    ixEditNew = -1;
                }
            }
            if (ixEditOld === undefined) {
                ixEditOld = -1;
            }
            ajaxGetDiff(ixBugEvent, ixEditNew, ixEditOld, fxn);
        }

        function RefreshHistoryFromSelectObj(objSelect) {
            //Yield momentarily so that the javascript from the dropdowns doesn't step on us.
            //If it manages to anyway, the 'loading' text is obscured- no biggie.
            setTimeout(function() {

                var ixBugEvent, ixEditOld, ixEditNew;

                //Extract the params from the Option:
                //They look like this:  "[ixBugEvent,ixEditOld,ixEditNew]"
                var rgArgs = null;
                $(objSelect).children().each(function() {
                    if ($(this).attr('selected')) {
                        var re = /\[[0-9]+,[0-9]+,[0-9]+\]/;
                        if (re.test($(this).val())) {
                            rgArgs = eval($(this).val()); //Using 'eval' as my JSON parser.

                            //loading notifier.
                            var rg = [$('#idDropList_selectNew' + rgArgs[0] + '_oText'), $('#idDropList_selectOld' + rgArgs[0] + '_oText')];
                            rg[0].val('Loading...');
                            rg[1].val('Loading...');
                        }
                    }
                });
                if (rgArgs === null) {
                    return;
                }

                ixBugEvent = rgArgs[0];
                ixEditOld = rgArgs[1];
                ixEditNew = rgArgs[2];

                ajaxPerformRefresh(ixBugEvent, ixEditNew, ixEditOld, function() { });

            }, 10);
        }

        function ajaxPerformRefresh(ixBugEvent, ixEditNew, ixEditOld, fxnFinally) {

            //Get diff:
            var fxn = function(sAJAX, status) {
                var sData;
                if ($(sAJAX).attr('fOK') == 1) {
                    sData = ParseData(sAJAX);
                }
                else {
                    sData = $(sAJAX).attr('sMsg');
                    if (!sData) { sData = sREQUESTFAIL; }
                }

                //Update the history:
                $('#CaseEventEditHistory' + ixBugEvent).html(CleanDataForIE(sData));
                DLCLHistory(ixBugEvent);
                fxnFinally();
            };
            ajaxGetDiff(ixBugEvent, ixEditNew, ixEditOld, fxn);
        }

        function RefreshHistory(ixBugEvent, ixEditNew, ixEditOld) {

            SetHistoryLoading(ixBugEvent, true);

            var fxnFinally = function() {
                SetHistoryLoading(ixBugEvent, false);
                //Flash!
                FlashHistory(ixBugEvent);
            };

            ajaxPerformRefresh(ixBugEvent, ixEditNew, ixEditOld, fxnFinally);
        }

        function ajaxGetDiff(ixBugEvent, ixEditNew, ixEditOld, fxn) {
            var sURL = CaseEventEdit_sRawPageURL;
            sURL += Prefix('action=diff');
            sURL += Prefix('ixBugEvent=' + encodeURIComponent(ixBugEvent));
            sURL += Prefix('ixEditNew=' + encodeURIComponent(ixEditNew));
            sURL += Prefix('ixEditOld=' + encodeURIComponent(ixEditOld));
            jQuery.get(sURL, fxn);
        }

        return members;
    }

    return members;
} ();
/**
 * AJAX Upload ( http://valums.com/ajax-upload/ ) 
 * Copyright (c) Andris Valums
 * Licensed under the MIT license ( http://valums.com/mit-license/ )
 * Thanks to Gary Haran, David Mark, Corey Burns and others for contributions 
 */
(function () {
    /* global window */
    /* jslint browser: true, devel: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true */
    
    /**
     * Wrapper for FireBug's console.log
     */
    function log(){
        if (typeof(console) != 'undefined' && typeof(console.log) == 'function'){            
            Array.prototype.unshift.call(arguments, '[Ajax Upload]');
            console.log( Array.prototype.join.call(arguments, ' '));
        }
    } 

    /**
     * Attaches event to a dom element.
     * @param {Element} el
     * @param type event name
     * @param fn callback This refers to the passed element
     */
    function addEvent(el, type, fn){
        if (el.addEventListener) {
            el.addEventListener(type, fn, false);
        } else if (el.attachEvent) {
            el.attachEvent('on' + type, function(){
                fn.call(el);
	        });
	    } else {
            throw new Error('not supported or DOM not loaded');
        }
    }   
    
    /**
     * Attaches resize event to a window, limiting
     * number of event fired. Fires only when encounteres
     * delay of 100 after series of events.
     * 
     * Some browsers fire event multiple times when resizing
     * http://www.quirksmode.org/dom/events/resize.html
     * 
     * @param fn callback This refers to the passed element
     */
    function addResizeEvent(fn){
        var timeout;
               
	    addEvent(window, 'resize', function(){
            if (timeout){
                clearTimeout(timeout);
            }
            timeout = setTimeout(fn, 100);                        
        });
    }    
    
    // Needs more testing, will be rewriten for next version        
    // getOffset function copied from jQuery lib (http://jquery.com/)
    if (document.documentElement.getBoundingClientRect){
        // Get Offset using getBoundingClientRect
        // http://ejohn.org/blog/getboundingclientrect-is-awesome/
        var getOffset = function(el){
            var box = el.getBoundingClientRect();
            var doc = el.ownerDocument;
            var body = doc.body;
            var docElem = doc.documentElement; // for ie 
            var clientTop = docElem.clientTop || body.clientTop || 0;
            var clientLeft = docElem.clientLeft || body.clientLeft || 0;
             
            // In Internet Explorer 7 getBoundingClientRect property is treated as physical,
            // while others are logical. Make all logical, like in IE8.	
            var zoom = 1;            
            if (body.getBoundingClientRect) {
                var bound = body.getBoundingClientRect();
                zoom = (bound.right - bound.left) / body.clientWidth;
            }
            
            if (zoom > 1) {
                clientTop = 0;
                clientLeft = 0;
            }
            
            var top = box.top / zoom + (window.pageYOffset || docElem && docElem.scrollTop / zoom || body.scrollTop / zoom) - clientTop, left = box.left / zoom + (window.pageXOffset || docElem && docElem.scrollLeft / zoom || body.scrollLeft / zoom) - clientLeft;
            
            return {
                top: top,
                left: left
            };
        };        
    } else {
        // Get offset adding all offsets 
        var getOffset = function(el){
            var top = 0, left = 0;
            do {
                top += el.offsetTop || 0;
                left += el.offsetLeft || 0;
                el = el.offsetParent;
            } while (el);
            
            return {
                left: left,
                top: top
            };
        };
    }
    
    /**
     * Returns left, top, right and bottom properties describing the border-box,
     * in pixels, with the top-left relative to the body
     * @param {Element} el
     * @return {Object} Contains left, top, right,bottom
     */
    function getBox(el){
        var left, right, top, bottom;
        var offset = getOffset(el);
        left = offset.left;
        top = offset.top;
        
        right = left + el.offsetWidth;
        bottom = top + el.offsetHeight;
        
        return {
            left: left,
            right: right,
            top: top,
            bottom: bottom
        };
    }
    
    /**
     * Helper that takes object literal
     * and add all properties to element.style
     * @param {Element} el
     * @param {Object} styles
     */
    function addStyles(el, styles){
        for (var name in styles) {
            if (styles.hasOwnProperty(name)) {
                el.style[name] = styles[name];
            }
        }
    }
        
    /**
     * Function places an absolutely positioned
     * element on top of the specified element
     * copying position and dimentions.
     * @param {Element} from
     * @param {Element} to
     */    
    function copyLayout(from, to){
	    var box = getBox(from);
        
        addStyles(to, {
	        position: 'absolute',                    
	        left : box.left + 'px',
	        top : box.top + 'px',
	        width : from.offsetWidth + 'px',
	        height : from.offsetHeight + 'px'
	    });        
    }

    /**
    * Creates and returns element from html chunk
    * Uses innerHTML to create an element
    */
    var toElement = (function(){
        var div = document.createElement('div');
        return function(html){
            div.innerHTML = html;
            var el = div.firstChild;
            return div.removeChild(el);
        };
    })();
            
    /**
     * Function generates unique id
     * @return unique id 
     */
    var getUID = (function(){
        var id = 0;
        return function(){
            return 'ValumsAjaxUpload' + id++;
        };
    })();        
 
    /**
     * Get file name from path
     * @param {String} file path to file
     * @return filename
     */  
    function fileFromPath(file){
        return file.replace(/.*(\/|\\)/, "");
    }
    
    /**
     * Get file extension lowercase
     * @param {String} file name
     * @return file extenstion
     */    
    function getExt(file){
        return (-1 !== file.indexOf('.')) ? file.replace(/.*[.]/, '') : '';
    }

    function hasClass(el, name){        
        var re = new RegExp('\\b' + name + '\\b');        
        return re.test(el.className);
    }    
    function addClass(el, name){
        if ( ! hasClass(el, name)){   
            el.className += ' ' + name;
        }
    }    
    function removeClass(el, name){
        var re = new RegExp('\\b' + name + '\\b');                
        el.className = el.className.replace(re, '');        
    }
    
    function removeNode(el){
        el.parentNode.removeChild(el);
    }

    /**
     * Easy styling and uploading
     * @constructor
     * @param button An element you want convert to 
     * upload button. Tested dimentions up to 500x500px
     * @param {Object} options See defaults below.
     */
    window.AjaxUpload = function(button, options){
        this._settings = {
            // Location of the server-side upload script
            action: 'upload.php',
            // File upload name
            name: 'userfile',
            // Additional data to send
            data: {},
            // Submit file as soon as it's selected
            autoSubmit: true,
            // The type of data that you're expecting back from the server.
            // html and xml are detected automatically.
            // Only useful when you are using json data as a response.
            // Set to "json" in that case. 
            responseType: false,
            // Class applied to button when mouse is hovered
            hoverClass: 'hover',
            // Class applied to button when AU is disabled
            disabledClass: 'disabled',            
            // When user selects a file, useful with autoSubmit disabled
            // You can return false to cancel upload			
            onChange: function(file, extension){
            },
            // Callback to fire before file is uploaded
            // You can return false to cancel upload
            onSubmit: function(file, extension){
            },
            // Fired when file upload is completed
            // WARNING! DO NOT USE "FALSE" STRING AS A RESPONSE!
            onComplete: function(file, response){
            }
        };
                        
        // Merge the users options with our defaults
        for (var i in options) {
            if (options.hasOwnProperty(i)){
                this._settings[i] = options[i];
            }
        }
                
        // button isn't necessary a dom element
        if (button.jquery){
            // jQuery object was passed
            button = button[0];
        } else if (typeof button == "string") {
            if (/^#.*/.test(button)){
                // If jQuery user passes #elementId don't break it					
                button = button.slice(1);                
            }
            
            button = document.getElementById(button);
        }
        
        if ( ! button || button.nodeType !== 1){
            throw new Error("Please make sure that you're passing a valid element"); 
        }
                
        if ( button.nodeName.toUpperCase() == 'A'){
            // disable link                       
            addEvent(button, 'click', function(e){
                if (e && e.preventDefault){
                    e.preventDefault();
                } else if (window.event){
                    window.event.returnValue = false;
                }
            });
        }
                    
        // DOM element
        this._button = button;        
        // DOM element                 
        this._input = null;
        // If disabled clicking on button won't do anything
        this._disabled = false;
        
        // if the button was disabled before refresh if will remain
        // disabled in FireFox, let's fix it
        this.enable();        
        
        this._rerouteClicks();
    };
    
    // assigning methods to our class
    AjaxUpload.prototype = {
        setData: function(data){
            this._settings.data = data;
        },
        disable: function(){            
            addClass(this._button, this._settings.disabledClass);
            this._disabled = true;
            
            var nodeName = this._button.nodeName.toUpperCase();            
            if (nodeName == 'INPUT' || nodeName == 'BUTTON'){
                this._button.setAttribute('disabled', 'disabled');
            }            
            
            // hide input
            if (this._input){
                // We use visibility instead of display to fix problem with Safari 4
                // The problem is that the value of input doesn't change if it 
                // has display none when user selects a file           
                this._input.parentNode.style.visibility = 'hidden';
            }
        },
        enable: function(){
            removeClass(this._button, this._settings.disabledClass);
            this._button.removeAttribute('disabled');
            this._disabled = false;
            
        },
        /**
         * Creates invisible file input 
         * that will hover above the button
         * <div><input type='file' /></div>
         */
        _createInput: function(){ 
            var self = this;
                        
            var input = document.createElement("input");
            input.setAttribute('type', 'file');
            input.setAttribute('name', this._settings.name);
            
            addStyles(input, {
                'position' : 'absolute',
                // in Opera only 'browse' button
                // is clickable and it is located at
                // the right side of the input
                'right' : 0,
                'margin' : 0,
                'padding' : 0,
                'fontSize' : '480px',                
                'cursor' : 'pointer'
            });            

            var div = document.createElement("div");                        
            addStyles(div, {
                'display' : 'block',
                'position' : 'absolute',
                'overflow' : 'hidden',
                'margin' : 0,
                'padding' : 0,
                'opacity': 0,
                'cursor' : 'pointer !important',                
                // Make sure browse button is in the right side
                // in Internet Explorer
                'direction' : 'ltr',
                //Max zIndex supported by Opera 9.0-9.2
                'zIndex': 2147483583
            });
            
            // Make sure that element opacity exists.
            // Otherwise use IE filter            
            if ( div.style.opacity !== "0") {
                if (typeof(div.filters) == 'undefined'){
                    throw new Error('Opacity not supported by the browser');
                }
                div.style.filter = "alpha(opacity=0)";
            }            
            
            addEvent(input, 'change', function(){
                 
                if ( ! input || input.value === ''){                
                    return;                
                }
                            
                // Get filename from input, required                
                // as some browsers have path instead of it          
                var file = fileFromPath(input.value);
                                
                if (false === self._settings.onChange.call(self, file, getExt(file))){
                    self._clearInput();                
                    return;
                }
                
                // Submit form when value is changed
                if (self._settings.autoSubmit) {
                    self.submit();
                }
            });            

            addEvent(input, 'mouseover', function(){
                addClass(self._button, self._settings.hoverClass);
            });
            
            addEvent(input, 'mouseout', function(){
                removeClass(self._button, self._settings.hoverClass);
                
                // We use visibility instead of display to fix problem with Safari 4
                // The problem is that the value of input doesn't change if it 
                // has display none when user selects a file           
                input.parentNode.style.visibility = 'hidden';

            });   
                        
	        div.appendChild(input);
            document.body.appendChild(div);
              
            this._input = input;
        },
        _clearInput : function(){
            if (!this._input){
                return;
            }            
                             
            // this._input.value = ''; Doesn't work in IE6                               
            removeNode(this._input.parentNode);
            this._input = null;                                                                   
            this._createInput();
            
            removeClass(this._button, this._settings.hoverClass);
        },
        /**
         * Function makes sure that when user clicks upload button,
         * the this._input is clicked instead
         */
        _rerouteClicks: function(){
            var self = this;
            
            // IE will later display 'access denied' error
            // if you use using self._input.click()
            // other browsers just ignore click()

            addEvent(self._button, 'mouseover', function(){
                if (self._disabled){
                    return;
                }
                                
                if ( ! self._input){
	                self._createInput();
                }
                
                var div = self._input.parentNode;                            
                copyLayout(self._button, div);
                div.style.visibility = 'visible';
                                
            });
            
            
            // commented because we now hide input on mouseleave
            /**
             * When the window is resized the elements 
             * can be misaligned if button position depends
             * on window size
             */
            //addResizeEvent(function(){
            //    if (self._input){
            //        copyLayout(self._button, self._input.parentNode);
            //    }
            //});            
                                         
        },
        /**
         * Creates iframe with unique name
         * @return {Element} iframe
         */
        _createIframe: function(){
            // We can't use getTime, because it sometimes return
            // same value in safari :(
            var id = getUID();            
             
            // We can't use following code as the name attribute
            // won't be properly registered in IE6, and new window
            // on form submit will open
            // var iframe = document.createElement('iframe');
            // iframe.setAttribute('name', id);                        
 
            var iframe = toElement('<iframe src="javascript:false;" name="' + id + '" />');
            // src="javascript:false; was added
            // because it possibly removes ie6 prompt 
            // "This page contains both secure and nonsecure items"
            // Anyway, it doesn't do any harm.            
            iframe.setAttribute('id', id);
            
            iframe.style.display = 'none';
            document.body.appendChild(iframe);
            
            return iframe;
        },
        /**
         * Creates form, that will be submitted to iframe
         * @param {Element} iframe Where to submit
         * @return {Element} form
         */
        _createForm: function(iframe){
            var settings = this._settings;
                        
            // We can't use the following code in IE6
            // var form = document.createElement('form');
            // form.setAttribute('method', 'post');
            // form.setAttribute('enctype', 'multipart/form-data');
            // Because in this case file won't be attached to request                    
            var form = toElement('<form method="post" enctype="multipart/form-data"></form>');
                        
            form.setAttribute('action', settings.action);
            form.setAttribute('target', iframe.name);                                   
            form.style.display = 'none';
            document.body.appendChild(form);
            
            // Create hidden input element for each data key
            for (var prop in settings.data) {
                if (settings.data.hasOwnProperty(prop)){
                    var el = document.createElement("input");
                    el.setAttribute('type', 'hidden');
                    el.setAttribute('name', prop);
                    el.setAttribute('value', settings.data[prop]);
                    form.appendChild(el);
                }
            }
            return form;
        },
        /**
         * Gets response from iframe and fires onComplete event when ready
         * @param iframe
         * @param file Filename to use in onComplete callback 
         */
        _getResponse : function(iframe, file){            
            // getting response
            var toDeleteFlag = false, self = this, settings = this._settings;   
               
            addEvent(iframe, 'load', function(){                
                
                if (// For Safari 
                    iframe.src == "javascript:'%3Chtml%3E%3C/html%3E';" ||
                    // For FF, IE
                    iframe.src == "javascript:'<html></html>';"){                                                                        
                        // First time around, do not delete.
                        // We reload to blank page, so that reloading main page
                        // does not re-submit the post.
                        
                        if (toDeleteFlag) {
                            // Fix busy state in FF3
                            setTimeout(function(){
                                removeNode(iframe);
                            }, 0);
                        }
                                                
                        return;
                }
                
                var doc = iframe.contentDocument ? iframe.contentDocument : window.frames[iframe.id].document;
                
                // fixing Opera 9.26,10.00
                if (doc.readyState && doc.readyState != 'complete') {
                   // Opera fires load event multiple times
                   // Even when the DOM is not ready yet
                   // this fix should not affect other browsers
                   return;
                }
                
                // fixing Opera 9.64
                if (doc.body && doc.body.innerHTML == "false") {
                    // In Opera 9.64 event was fired second time
                    // when body.innerHTML changed from false 
                    // to server response approx. after 1 sec
                    return;
                }
                
                var response;
                
                if (doc.XMLDocument) {
                    // response is a xml document Internet Explorer property
                    response = doc.XMLDocument;
                } else if (doc.body){
                    // response is html document or plain text
                    response = doc.body.innerHTML;
                    
                    if (settings.responseType && settings.responseType.toLowerCase() == 'json') {
                        // If the document was sent as 'application/javascript' or
                        // 'text/javascript', then the browser wraps the text in a <pre>
                        // tag and performs html encoding on the contents.  In this case,
                        // we need to pull the original text content from the text node's
                        // nodeValue property to retrieve the unmangled content.
                        // Note that IE6 only understands text/html
                        if (doc.body.firstChild && doc.body.firstChild.nodeName.toUpperCase() == 'PRE') {
                            response = doc.body.firstChild.firstChild.nodeValue;
                        }
                        
                        if (response) {
                            response = eval("(" + response + ")");
                        } else {
                            response = {};
                        }
                    }
                } else {
                    // response is a xml document
                    response = doc;
                }
                
                settings.onComplete.call(self, file, response);
                
                // Reload blank page, so that reloading main page
                // does not re-submit the post. Also, remember to
                // delete the frame
                toDeleteFlag = true;
                
                // Fix IE mixed content issue
                iframe.src = "javascript:'<html></html>';";
            });            
        },        
        /**
         * Upload file contained in this._input
         */
        submit: function(){                        
            var self = this, settings = this._settings;
            
            if ( ! this._input || this._input.value === ''){                
                return;                
            }
                                    
            var file = fileFromPath(this._input.value);
            
            // user returned false to cancel upload
            if (false === settings.onSubmit.call(this, file, getExt(file))){
                this._clearInput();                
                return;
            }
            
            // sending request    
            var iframe = this._createIframe();
            var form = this._createForm(iframe);
            
            // assuming following structure
            // div -> input type='file'
            removeNode(this._input.parentNode);            
            removeClass(self._button, self._settings.hoverClass);
                        
            form.appendChild(this._input);
                        
            form.submit();

            // request set, clean up                
            removeNode(form); form = null;                          
            removeNode(this._input); this._input = null;
            
            // Get response from iframe and fire onComplete event when ready
            this._getResponse(iframe, file);            

            // get ready for next request            
            this._createInput();
        }
    };
})(); 

