﻿// --------- miscellaneous helper functions ---------- //

// Check to see if a string begins with the specified string
String.prototype.beginsWith = function(t, i) {
    if (i == false) {
        return (t == this.substring(0, t.length));
    } else {
        return (t.toLowerCase() == this.substring(0, t.length).toLowerCase());
    } 
}

// Check to see if a string ends with the specified string
String.prototype.endsWith = function(t, i) {
    if (i == false) {
        return (t == this.substring(this.length - t.length));
    } else {
        return (t.toLowerCase() == this.substring(this.length - t.length).toLowerCase());
    }
}

// Uppercase the first character of the supplied string
String.prototype.ucFirst = function() {
    return this.charAt(0).toUpperCase() + this.slice(1);
}

// trim leading instances of a set of chars
String.prototype.lTrim = function(chars) {
    chars = chars || "\\s";
    return this.replace(new RegExp("^[" + chars + "]+", "g"), "");
}

// trim trailing instances of a set of chars
String.prototype.rTrim = function(chars) {
    chars = chars || "\\s";
    return this.replace(new RegExp("[" + chars + "]+$", "g"), "");
}

// create a delegate to call member functions with correct object context without using closures
// NOTE: arguments supplied by caller
// see: http://www.terrainformatica.com/?p=13 for more information
function delegate(that, thatMethod) {
    return function() { return thatMethod.apply(that, arguments); }
}

// create a delegate to call member functions with correct object context without using closures
// NOTE: arguments supplied when delegate is created
// see: http://www.terrainformatica.com/?p=13 for more information
function delegateWithArguments(that, thatMethod) {
    if(arguments.length > 2) {
        var _params = [];
        
        for(var n = 2; n < arguments.length; ++n) {
            _params.push(arguments[n]);
        }
        return function() { return thatMethod.apply(that, _params); }
    }
    else {
        return function() { return thatMethod.call(that); }
    }
}

// execute a function given its name. works with namespaced functions
// we need this because: window["functionName"] doesn't work with namespaced functions
// see: http://stackoverflow.com/questions/359788/javascript-function-name-as-a-string/359910 for more info
function executeFunctionByName(functionName, context /*, args */) {
    
    var args        = Array.prototype.slice.call(arguments).splice(2);
    var namespaces  = functionName.split(".");
    var func        = namespaces.pop();

    for (var i = 0; i < namespaces.length; i++) {
        context = context[namespaces[i]];
    }

    return context[func].apply(this, args);
}

// ------------ logging helper functions ------------- //

var debugLogger = new ClientDebugLogger();

function ClientDebugLogger() {

    function log(info) {
        if (window.console && window.console.firebug) {
            console.log.apply(this, arguments);
        }
        else if (window.debugService) {
            window.debugService.trace(info);
        }
    }

    // declare public methods
    this.log = log;

    return this;
}


// ------------- extjs helper functions -------------- //

// fix for using jQuery adapter behind ext js. not currently doing so but will leave fix here just in case
// see: http://extjs.com/forum/showthread.php?t=46300 for more info
Ext.lib.Event.resolveTextNode = function(node) {
    if (Ext.isSafari && node && 3 == node.nodeType) {
        return node.parentNode;
    } else {
        return node;
    }
}

// ------------ extjs error handling ---------- //

// handle an ajax request failure
// see: http://extjs.com/forum/showthread.php?t=35014 for more info
function extJs_ajaxRequestFailed(connection, response, options) {  
    // report the error back to the server
    ajax_logError(response);
}

// -------------- image helper functions -------------- //

// preload specified images
jQuery.preloadImages = function() {
    for (var i = 0; i < arguments.length; i++) {
        if (arguments[i] !== "") {
            jQuery("<img />").attr("src", arguments[i]);
        }
    }
}

// show inactive images except the supplied one
jQuery.showInactiveImages = function(id, handlerId, imageArray) {
    $.each(imageArray, function(i, imageInfo) {
        if (id != imageInfo[0]) {
            // replace image
            $('#' + imageInfo[0] + '').attr("src", imageInfo[2]);
        }

        if (handlerId != imageInfo[1]) {
            // replace handler image
            $('#' + imageInfo[1] + '').attr("src", imageInfo[2]);
        }
    });
}

// bind hover image for image with specified id
// perform checks to ensure we don't change active / disabled / dragdrop images
jQuery.bindHoverImages = function(
    imageId, hoverImagePath, inactiveImagePath, id,
    checkIsActive, checkAllowDropOnActive, checkIsEnabled, checkIsDropState, checkIsOnState,
    setIsHover) {

    var bIsEnabled = true;

    if ((hoverImagePath !== "") && (inactiveImagePath !== "")) {
        $("#" + imageId + "").hover(function() {

            if ((checkIsEnabled !== null) && (checkIsEnabled !== "")) {
                bIsEnabled = executeFunctionByName(checkIsEnabled, window);
            }

            if ((((checkIsActive === null) ||
                 ((checkIsActive !== null) && (!checkIsActive(id)))) ||
                 ((checkAllowDropOnActive !== null) && (checkAllowDropOnActive === true))) && // no dropping on active unless we explicitly allow it

                bIsEnabled &&
                ((checkIsDropState === null) || ((checkIsDropState !== null) && (!checkIsDropState(id))))) {

                // if we're in the 'on' state, don't change the image, but do set the flag
                if ((checkIsOnState === null) || ((checkIsOnState != null) && (!checkIsOnState(id)))) {
                    $("#" + imageId + "").attr("src", hoverImagePath);
                }

                // set flag to indicate hovering
                if (setIsHover) {
                    setIsHover(id);
                }
            }
        }, function() {

            if ((checkIsEnabled !== null) && (checkIsEnabled !== "")) {
                bIsEnabled = executeFunctionByName(checkIsEnabled, window);
            }

            if (((checkIsActive === null) || ((checkIsActive !== null) && (!checkIsActive(id)))) &&
                bIsEnabled &&
                ((checkIsDropState === null) || ((checkIsDropState !== null) && (!checkIsDropState(id))))) {

                // if we're in the 'on' state, don't change the image, but do reset the flag
                if ((checkIsOnState === null) || ((checkIsOnState !== null) && (!checkIsOnState(id)))) {
                    $("#" + imageId + "").attr("src", inactiveImagePath);
                }

                // set flag to indicate no longer hovering
                if (setIsHover) {
                    setIsHover(g_default_xPOPropertyId);
                }
            }
        });
    }
}

// flash the specified image
var g_aImageFlashImageInfo = new Object();
jQuery.flashImageStart = function(imageId, hoverImagePath, dropTargetImagePath, inactiveImagePath, onImagePath, id, checkIsActive, checkIsDropState, checkIsHover, setIsOnState) {

    var bIsEnabled = true;

    // check to see if the image is flashing before starting. don't ever want two timers going off for the same image
    if (!g_aImageFlashImageInfo[imageId]) {

        if ((checkIsActive === null) || ((checkIsActive !== null) && (!checkIsActive(id)))) {

            // change image immediately
            $("#" + imageId + "").attr("src", onImagePath);

            // store flash image info
            var flashImageInfo = new Object();
            flashImageInfo['state'] = 1;
            flashImageInfo['hoverImagePath'] = hoverImagePath;
            flashImageInfo['dropTargetImagePath'] = dropTargetImagePath;
            flashImageInfo['inactiveImagePath'] = inactiveImagePath;
            flashImageInfo['onImagePath'] = onImagePath;

            // start timer (using anonymous function to enable us to pass args)
            flashImageInfo['timerId'] = setTimeout(
                function() { flashImageToggle(imageId, id, checkIsHover, checkIsDropState, setIsOnState); },
                g_imageFlashTime_milliseconds);

            // persist info
            g_aImageFlashImageInfo[imageId] = flashImageInfo;
        }
    }
}

// toggle the flash image
function flashImageToggle(imageId, id, checkIsHover, checkIsDropState, setIsOnState) {

    var flashImageInfo = g_aImageFlashImageInfo[imageId];
    if (flashImageInfo) {
        // toggle image & state flag
        if (flashImageInfo.state === 0) {
            flashImageInfo['state'] = 1;
            $("#" + imageId + "").attr("src", flashImageInfo['onImagePath']);

            // set flag to indicate on state
            if (setIsOnState) {
                setIsOnState(id);
            }
        }
        else {
            flashImageInfo['state'] = 0;

            // off state image is usually inactive, but if mouse is hovering or in drop state, we need to use the hover or dropstate image instead
            // we've passed in delegates becauuse they encapsulate the arguments that we need to call with to do the check
            // and we don't want to go passing in all of the object-specific arguments (i.e. device id, tab name, etc.) necessary, cluttering up this generic function
            if ((checkIsHover !== null) && (checkIsHover(id))) {
                // hovering - switch back to hover image
                $("#" + imageId + "").attr("src", flashImageInfo['hoverImagePath']);
            }
            else if ((checkIsDropState !== null) && (checkIsDropState(id))) {
                // drop state - switch back to drop state
                $("#" + imageId + "").attr("src", flashImageInfo['dropTargetImagePath']);
            }
            else {
                // inactive
                $("#" + imageId + "").attr("src", flashImageInfo['inactiveImagePath']);
            }

            // set flag to indicate no longer on state
            if (setIsOnState) {
                setIsOnState(g_default_xPOPropertyId);
            }
        }

        // loop, setting new timer id (using anonymous function to enable us to pass args)
        flashImageInfo['timerId'] = setTimeout(function() { flashImageToggle(imageId, id, checkIsHover, checkIsDropState, setIsOnState); }, g_imageFlashTime_milliseconds);
        g_aImageFlashImageInfo[imageId] = flashImageInfo;
    }
}

// stop flashing the specified image
function flashImageStop(imageId) {

    var flashImageInfo = g_aImageFlashImageInfo[imageId];
    if (flashImageInfo) {
        // stop the timer and delete the array element
        clearTimeout(flashImageInfo['timerId']);
        delete (g_aImageFlashImageInfo[imageId]);
    }
}


// ---------------- helper functions ----------------- //

// sort numbers properly
function sortNumber(a, b) {
    return a - b;
}

// focus first control in error otherwise first control
function focusFirstControl(id) {
    var e = $('.input-validation-error:first').get(0);
    if (e) {
        e.focus();
    }
    else {
        var c = $("#" + id);
        if (c) {
            $("#" + id).focus();
        }
    }
}

// strip the params from a url
function stripParamsFromUrl(url) {

    if (url) {
        var pos = url.lastIndexOf('?');
        if (pos != -1) {
            url = url.substr(0, pos);
        }
    }

    return url;
}


// set an element to an image
function setImage(elementId, imageUrl) {
    $("#" + elementId + "").attr("src", imageUrl);
}


// ------- general error handler helper functions ------- //

// display an authentication error
function show_authenticationError() {
    // remove 'close' button & unbind esccape-to-close
    $(document).unbind('keydown.facebox');
    $(document).bind('afterReveal.facebox', function() {
        $("#facebox_footer").css({ "display": "none" });
        $(document).unbind('afterReveal.facebox');
    });

    $.facebox({ div: "#authentication_error_dialog" });
}

// display a server error
function show_serverError() {
    // remove 'close' button & unbind esccape-to-close
    $(document).unbind('keydown.facebox');
    $(document).bind('afterReveal.facebox', function() {
        $("#facebox_footer").css({ "display": "none" });
        $(document).unbind('afterReveal.facebox');
    });
    
    $.facebox({ div: "#server_error_dialog" });
}

// display a no data error
function show_noDataError() {
    $.facebox({ div: "#data_error_dialog" });
}

// display a timeout error (currently unused)
function show_timeoutError() {
    $.facebox({ div: "#timeout_error_dialog" });
}

// display a playlist create error
function show_playlistCreateError() {
    $.facebox({ div: "#playlistcreate_error_dialog" });
}

// display a playlist rename error
function show_playlistRenameError() {
    $.facebox({ div: "#playlistrename_error_dialog" });
}

// display a playlist delete error
function show_playlistDeleteError() {
    $.facebox({ div: "#playlistdelete_error_dialog" });
}

// display a not streamable error
function show_notStreamableError() {
    $.facebox({ div: "#notstreamable_error_dialog" });
}

// display a not uploded error
function show_notUploadedError() {
    $.facebox({ div: "#notuploaded_error_dialog" });
}

// display an ie6 error
function show_ie6Error() {
    // remove 'close' button & unbind esccape-to-close
    $(document).unbind('keydown.facebox');
    $(document).bind('afterReveal.facebox', function() {
        $("#facebox_footer").css({ "display": "none" });
        $(document).unbind('afterReveal.facebox');
    });

    $.facebox({ div: "#ie6_error_dialog" });
}

// display a warning about a popup blocker
function show_popupBlockerWarning() {
    $.facebox({ div: "#popupblocker_warning_dialog" });
}

// display a warning about flash / flashblocker
function checkAndShow_flashWarning() {
    var bHasRequiredVersion = DetectFlashVer(9, 0, 115);
    if (!bHasRequiredVersion) {
        // required flash version not installed
        $.facebox({ div: "#flash_warning_dialog" });
    }
    
    return bHasRequiredVersion;
}

// log an axax failure
function ajax_logError(response, url, params, error) {
    // params / error may be null so log with care
    if (response) {
        var message = "AJAX request failed.\n" +
            "readyState: " + response.readyState + "\n" +
            "status: " + response.status + "\n" +
            "statusText: " + response.statusText + "\n" +
            "responseText: " + response.responseText + "\n" +
            "responseXml: " + response.responseXml + "\n" +
            "url: " + url;

        if (error) {
            message += "\n";
            message += "error: ";
            message += error;
        }

        var stacktrace = printStackTrace();
        errorLogger.log(message, "n/a", "n/a", stacktrace);
    }
}

// log an exception
function ajax_logException(ex) {
    if (ex) {
        var message = "An exception was caught.\n" +
            "message: " + ex.message;

        var stacktrace = printStackTrace();
        errorLogger.log(message, "n/a", "n/a", stacktrace);
    }
}


// --- form validation handler helper functions -- //

function modalForm_validate_disableButtons(data, form) {

    var ret = xVal_validateForm(data, form);
    if (ret) {
        $("#modal_form_submit_button_span").text("Please Wait...");
        $("#modal_form_submit_button_span").addClass('no_hover_span');

        $("#modal_form_content form button[type=submit]").attr('disabled', 'disabled');
        $("#modal_form_content form button[type=submit]").addClass('no_hover_button');
    }

    return ret;
}

// validate the form using xVal and prevent submission if invalid
function xVal_validateForm(data, form) {
    return $("form").validate().form();
}


// ------- jquery error handler helper functions ------ //

// handle an ajax error returned by jQuery's $.ajax function
function jQuery_ajaxRequestFailed(XMLHttpRequest, textStatus, errorThrown) {

    if ((XMLHttpRequest) &&
       ((XMLHttpRequest.status == g_httpStatusCode_unauthorised) ||
        (XMLHttpRequest.status == g_httpStatusCode_forbidden))) {
        
        // show an authentication error
        show_authenticationError();
    }
}

// handle an ajax error returned by jQuery's $.ajax function & redirect to the login page
function jQuery_ajaxRequestFailed_redirectToLogin(XMLHttpRequest, textStatus, errorThrown) {

    if ((XMLHttpRequest) &&
       ((XMLHttpRequest.status == g_httpStatusCode_unauthorised) ||
        (XMLHttpRequest.status == g_httpStatusCode_forbidden))) {

        // redirect the user to the login page with return url back to current page
        document.location.href = g_loginUrl + "?ReturnUrl=" + escape(document.location.pathname + document.location.search);
    }
    else {
        ajax_logError(XMLHttpRequest);
    }
}


// ------ facebox error handler helper functions ------ //

// clean up after a failed facebox modal form display / submission
function facebox_modalFormFailed(XMLHttpRequest, textStatus, errorThrown) {
    jQuery(document).trigger('close.facebox');
    jQuery_ajaxRequestFailed_redirectToLogin(XMLHttpRequest, textStatus, errorThrown);
}


// ------------ extjs error handling ---------- //

// handle an ajax load failure
function extJs_ajaxLoadFailed(proxy, options, response, error) {

    if (response.status === 0) {
        // drop on floor
    }
    else if ((response.status == g_httpStatusCode_unauthorised) ||
             (response.status == g_httpStatusCode_forbidden)) {
        show_authenticationError();
    }
    else if ((response.status == g_httpStatusCode_badrequest)) {
        ajax_logError(response, proxy.conn.url, proxy.options, error);
        show_serverError();
    }
    else if ((response.status == g_httpStatusCode_internalservererror)) {
        ajax_logError(response, proxy.conn.url, proxy.options, error);
        show_serverError();
    }
    else {
        // report the error back to the server
        ajax_logError(response, proxy.conn.url, proxy.options, error);
    }
}


// ----------- drag & drop helper functions ---------- //

// set up drag & drop
function initializeDragDrop(
    dragDropGroups, dropTargetImage,
    activeImagePath, dropTargetImagePath, inactiveImagePath, hoverImagePath,
    droppedOnId, checkIsActive, checkAllowDropOnActive, checkIsHover, checkIsOnState, setIsDropState, dropHandler) {

    var dropTargetImageId = Ext.get(dropTargetImage);
    var deviceImageDropTarget = new Ext.dd.DropTarget(dropTargetImageId, {

        notifyEnter: function(ddSource, e, data) {
        
        // extract the type from the first item
        var type = data.selections[0].data.type;

        if ((checkIsActive === null) ||
            ((checkIsActive !== null) && (!checkIsActive(droppedOnId))) ||
            ((checkAllowDropOnActive !== null) && (checkAllowDropOnActive(type) === true))) { // no dropping on active unless we explicitly allow it

                // if we're in the 'on' state, don't change the image, but do set the flag
                if ((checkIsOnState === null) || ((checkIsOnState !== null) && (!checkIsOnState(droppedOnId)))) {
                    $("#" + dropTargetImage + "").attr("src", hoverImagePath);
                     $("#" + dropTargetImage + "").attr("src", dropTargetImagePath);
                }

                // set flag to indicate hovering
                if (setIsDropState) {
                    setIsDropState(droppedOnId);
                }
            }
        },

        notifyOut: function(ddSource, e, data) {
            var imagePath;

            if ((checkIsActive !== null) && (checkIsActive(droppedOnId))) {
                imagePath = activeImagePath; // replace with active image
            }
            else {
                imagePath = inactiveImagePath; // replace with inactive image
            }

            // if we're in the 'on' state, don't change the image, but do reset the flag
            if ((checkIsOnState === null) || ((checkIsOnState !== null) && (!checkIsOnState(droppedOnId)))) {
                $("#" + dropTargetImage + "").attr("src", imagePath); 
            }

            // set flag to indicate no longer hovering
            if (setIsDropState) {
                setIsDropState(g_default_xPOPropertyId);
            }
        },

        notifyDrop: function(ddSource, e, data) {
            var ret = false;

            // extract the type from the first item
            var type = data.selections[0].data.type;

            if ((checkIsActive === null) ||
                ((checkIsActive !== null) && (!checkIsActive(droppedOnId))) ||
                ((checkAllowDropOnActive !== null) && (checkAllowDropOnActive(type) === true))) { // no dropping on active unless we explicitly allow it
                ret = dropHandler(droppedOnId, data);
            }

            return ret;
        }
    });

    // add dragdrop groups
    for (var iGroup = 0; iGroup < dragDropGroups.length; iGroup++) {
        deviceImageDropTarget.addToGroup(dragDropGroups[iGroup]);
    }
}


// -------------- dialog helper functions ------------ //

function submitDialogIdToHide(dialogId) {

    // persist this info
    // create json data to pass back to server
    // fields must match the mappings in IdInfo class
    var dialogInfo      = { id: dialogId };
    var dialogInfoJson  = $.toJSON(dialogInfo);

    // post ajax request to server
    $.ajax({
        url: g_setDialogHiddenAsyncUrl, // note: global url
        type: g_method_post,
        dataType: g_dataType_json,
        data: dialogInfoJson,
        contentType: g_contentType_json,
        error: jQuery_ajaxRequestFailed_redirectToLogin
    });
}

// get the most recent blog item
function ajax_mostRecentBlogPost(successCallback) {

    // post ajax request to server
    $.ajax({
        url: g_mostRecentBlogPostAsyncUrl, // note: global url
        type: g_method_get,
        dataType: g_dataType_json,
        success: successCallback
    });
}

// adjust scrollbars smoothly
function flexscroll_adjustHeight(ancestor, delta, duration) {

    var sAncestorPrefix = '';
    
    if((ancestor) && (ancestor != "")) {
        sAncestorPrefix = "#" + ancestor + " ";
    }

    $(sAncestorPrefix + ".scrollwrapper").animate({ height: '+=' + delta }, duration);
    $(sAncestorPrefix + ".mcontentwrapper").animate({ height: '+=' + delta }, duration);
    $(sAncestorPrefix + ".vscrollerbaseend").animate({ top: '+=' + delta }, duration);
    $(sAncestorPrefix + ".vscrollerbase").animate({ height: '+=' + delta }, duration);
    $(sAncestorPrefix + ".vscrollerbasebeg").animate({ height: '+=' + delta }, duration);
}

// set the document title
function document_setPageTitle(pageTitle) {
    document.title = pageTitle;
}

// set the location hash
function document_setLocationHash(locationHash) {
    document.location.hash = "/" + locationHash.ucFirst() + "/";
}

// recursively print a javascript object to a string
function print_r(theObj) {
    return print_r_int(theObj, '', 0);
}

// internal printing function
function print_r_int(theObj, indentString, depth) {
    var retStr = '';
    if (typeof theObj == 'object') {
        retStr += '';

        try {
            for (var p in theObj) {
                if (typeof theObj[p] == 'object') {

                    retStr += indentString;
                    retStr += '[' + p + '] => ' + typeof (theObj) + '\n';

                    indentString += '        ';
                    depth += 1;

                    // prevent unnecessary / infinite recursion
                    if (depth < 5) {
                        retStr += print_r_int(theObj[p], indentString, depth) + '\n';
                    }
                    else {
                        retStr = retStr + "<truncated due to depth = " + depth + ">";
                    }
                }
                else {
                    retStr += indentString;
                    retStr += '[' + p + '] => ' + (theObj[p] + '').replace(new RegExp('\n', "g"), '\n' + indentString) + '\n';
                }
            }
        }
        catch (e) {
            retStr = retStr + "<truncated due to exception when enumerating object>";
        }
        
        retStr += '\n';
    }
    return retStr;
}