/**
 * The CFp class handles all basic tasks related to the FuturePlacement website.
 *
 * @author    Steffen Eckardt
 * @copyright Steffen Eckardt
 * @version   1.0 (2007-07-05)
 */
var CFp = {
    oInstance: null,

    /**
     * Returns a singleton CFp object.
     *
     * @return CFp
     */
    getInstance: function() {
        if (null === CFp.oInstance) {
            CFp.oInstance = CFp.prototype;
        }
        return CFp.oInstance;
    }
}

CFp.prototype = {
    sPathImages: null,
    sFormatDate: null,
    sFormatDateTime: null,
    sFormatTime: null,

    /**
     * This method is invoked when the DOM is ready. Actually this is the entry
     * point for the CFp class.
     *
     * @return void
     */
    onDomReady: function() {
        this.redesignTablesOutline1();
        this.redesignTablesList1();
        this.addPagination();
        this.redesignTablesStatic1();
        this.hideEventPanels();
        this.addAccordions();

        // Set image path for the thickbox class
        tb_pathToImage = this.getPathImages() + 'loadingAnimation.gif';
    },

    getPathImages: function() {
        return this.sPathImages;
    },

    setPathImages: function(sPath) {
        this.sPathImages = sPath;
    },

    getFormatDate: function() {
        return this.sFormatDate;
    },

    setFormatDate: function(sFormat) {
        this.sFormatDate = sFormat;
    },

    getFormatDateTime: function() {
        return this.sFormatDateTime;
    },

    setFormatDateTime: function(sFormat) {
        this.sFormatDateTime = sFormat;
    },

    getFormatTime: function() {
        return this.sFormatTime;
    },

    setFormatTime: function(sFormat) {
        this.sFormatTime = sFormat;
    },

    /**
     * Fetch all tables of class "outline1" and do the design rewritings.
     *
     * @return void
     */
    redesignTablesOutline1: function() {
        // Show <select> elements with class "hide" which are not empty
        var oSelect = $('select').filter('.hide').each(function() {
            if ($(this).children().length > 0) {
                $(this).show();
            }
        });

        // Fetch the number of rows
        $('table.outline1').not('.redesigned').each(function() {
            var oTable = $(this);
            var iRows  = oTable.find('> tbody > tr').length;

            if (iRows > 2) {
                // Show all <td class="left"> elements
                var oLast = null;
                oTable.find('td.left').each(function(i) {
                    var oElem = $(this);
                    oElem.css('visibility', 'visible');
                    var bIsContent = oElem.parent().hasClass('content');

                    if (i == 0) {
                        // The top <td class="left"> elements
                        oElem.html('<img src="' + Fp.getPathImages() + 'outline1Top.gif" alt="" style="width:15px; height:28px;" />');
                    }

                    if (i > 0 && i < (iRows - 1)) {
                        // The <td class="left"> elements between the first and the last row
                        oElem.css('background-image', 'url(\'' + Fp.getPathImages() + 'outline1Default.gif\')');
                        oElem.css('background-repeat', 'repeat-y');
                        if (!bIsContent) {
                            oElem.html('<img src="' + Fp.getPathImages() + 'outline1Center.gif" alt="" style="width:15px; height:28px;" />');
                        }
                    }

                    if (!bIsContent) {
                        oLast = oElem;
                    }
                });

                // The bottom <td class="left"> elements
                if (oLast) {
                    oLast.html('<img src="' + Fp.getPathImages() + 'outline1Bottom.gif" alt="" style="width:15px; height:28px;" />');
                    oLast.css('background-image', '');
                    oLast.css('background-repeat', '');
                }
            }

            // Indent the first <th> of tables inside the content
            oTable.find('tr.content td.content table').not('.list1').find('th:first').css({'padding-left': '35px', 'font-weight': 'bold'});

            // Add spaces before and after content table using <div>s
            oTable.find('tr.content td.content').prepend('<div class="prepend"></div>').append('<div class="append"></div>');

            // Add underlines to given <th> elements
            oTable.find('th.underline').append('<div style="height: 1px; overflow: hidden; border-bottom: 1px solid #FFD452; margin-right: 35px;"></div>');

            // Add the "cross" bullets
            oTable.find('td.heading').each(function(i) {
                var oElem = $(this);
                var sImg  = null;
                if (oElem.hasClass('active')) {
                    sImg = 'bullet1Active.gif';
                } else if (oElem.hasClass('inactive')) {
                    sImg = 'bullet1Inactive.gif';
                } else if (!oElem.hasClass('noselector')) {
                    sImg = 'bullet1Open.gif';
                }
                if (null !== sImg) {
                    var sUrl = oElem.attr('title');
                    oElem.prepend('<div class="selector">' + (sUrl ? '<a href="' + sUrl + '">' : '') + '<img src="' + Fp.getPathImages() + sImg + '" border="0" />' + (sUrl ? '</a>' : '') + '</div>');
                    if (sUrl) {
                        oElem.attr('title', oElem.text());
                    }
                }
            });

            oTable.addClass('redesigned');
        });
    },

    /**
     * Fetch all tables of class "list1" and do the design rewritings.
     *
     * @return void
     */
    redesignTablesList1: function() {
        // Adds the table sorter to all <table class="list1"> elements
        // NOTE: It is possible to set the sorted column and the sort direction via the table class!
        // - Setting up the table like <table class="list1 sortcol3"> would result in a sorting of column 3
        // - Setting up the table like <table class="list1 sortcol2 sortdesc"> would result in a DESC sorting of col 2

        this.redesignTablesList1Toggle();

        // Add our own parser for the dates
        $.tablesorter.addParser({
            id: 'fpDate',
            is: function(s) {
                return false;
            },
            format: function(s, sTable, sCell) {
                try {
                    var aClass = $(sCell).attr('class').split(' ');
                    for (var i=0; i<aClass.length; i++) {
                        var sClass = aClass[i].toLowerCase();
                        if (sClass.search(/^sortdate\d+/) != -1) {
                            return parseInt(sClass.replace(/^sortdate/, ''));
                        }
                    }
                } catch (e) {
                }
                return -1;
            },
            type: 'numeric'
        });

        // Add our own parser for prices
        $.tablesorter.addParser({
            id: 'fpPrice',
            is: function(s) {
                return false;
            },
            format: function(s, sTable, sCell) {
                try {
                    var aClass = $(sCell).attr('class').split(' ');
                    for (var i=0; i<aClass.length; i++) {
                        var sClass = aClass[i].toLowerCase();
                        if (sClass.search(/^sortprice\d+/) != -1) {
                            return parseInt(sClass.replace(/^sortprice/, '').replace(/^\_/, '.'));
                        }
                    }
                } catch (e) {
                }
                return -1;
            },
            type: 'numeric'
        });

        $('table.list1').not('.dontsort, .redesigned').each(function() {
            var oTable = $(this);
            var iRows  = oTable.find('tbody > tr').length;

            if (iRows > 1) {
                var aClasses = oTable.attr('class').split(' ');
                var iSortCol  = 0;
                var iSortDir  = 0;
                var bSortLast = false;

                for (var i=0; i<aClasses.length; i++) {
                    var sClass = aClasses[i].toLowerCase();

                    if (sClass == 'sortlastcol') {
                        bSortLast = true;
                    }

                    if (sClass.search(/^sortcol\d+/) != -1) {
                        try {
                            iSortCol = parseInt(sClass.replace(/^sortcol/, ''));
                        } catch (e) {
                        }
                    } else if (sClass == 'sortdesc') {
                        iSortDir = 1;
                    }
                }

                // Find table columns to be sorted by the date-sorter
                var aHeaders = {};
                oTable.find('thead > tr > th.sortdate').each(function() {
                    var iPrev       = $(this).prevAll().length;
                    aHeaders[iPrev] = {sorter: 'fpDate'};
                });

                oTable.find('thead > tr > th.sortprice').each(function() {
                    var iPrev       = $(this).prevAll().length;
                    aHeaders[iPrev] = {sorter: 'fpPrice'};
                });

                var aOptions = {
                    headers:    aHeaders,
                    sortList:   [[iSortCol, iSortDir]],
                    cssHeader:  '',
                    cssAsc:     'sortasc',
                    cssDesc:    'sortdesc',
                    dateFormat: Fp.getFormatDateTime()
                };

                // Due to some to some weird behaviours in Opera, we cannot use some of the TableSorter
                // features...Also we have to strip off all row classes in Opera...
                if ($.browser.opera) {
                    oTable.find('tbody > tr').removeClass().addClass('odd');
                } else {
                    aOptions['widthFixed']  = true;
                    aOptions['widgets']     = ['zebra'];
                    aOptions['widgetZebra'] = {css: ['odd', 'even']};
                }

                // Init table sorter(s)
                if (!bSortLast) {
                    var iHeaders                  = oTable.find('thead th').length - 1;
                    aOptions['headers'][iHeaders] = {sorter: false};
                }
                oTable.tablesorter(aOptions);
            }
        });
    },

    /**
     * This method handles a special kind of "list1" tables. It adds some toggle icons to be able to show/hide
     * a given set of rows.
     *
     * @return void
     */
    redesignTablesList1Toggle: function() {
        $('table.list1 > tbody > tr.head').each(function() {
            var oRow = $(this);
            if (oRow.is('.empty')) {
                oRow.find('td:first').prepend('<img src="' + Fp.getPathImages() + 'iconTogglePlus.gif" alt="" style="width: 16px; height: 16px;" class="expandImage" />');
            } else {
                oRow.find('td:first').prepend('<img src="' + Fp.getPathImages() + 'iconToggleMinus.gif" alt="" style="width: 16px; height: 16px;" class="expandImage" />');
            }
        });

        $('img.expandImage').click(function() {
            var oImg     = $(this);
            var oRow     = oImg.parent().parent();
            var aClasses = oRow.attr('class').split(' ');
            var sClass   = null;
            for (var i=0; i<aClasses.length; i++) {
                if (aClasses[i].match(/^row\d+/)) {
                    sClass = aClasses[i];
                    break;
                }
            }

            if (null !== sClass) {
                oRow.siblings('.' + sClass).not('.head').each(function() {
                    if ($(this).css('display') == 'none') {
                        $(this).show();
                        oImg.attr('src', Fp.getPathImages() + 'iconToggleMinus.gif');
                    } else {
                        $(this).hide();
                        oImg.attr('src', Fp.getPathImages() + 'iconTogglePlus.gif');
                    }
                });
            }
        });
    },

    /**
     * Adds pagination to all pager elements on a page.
     *
     * @param  boolean  bStart
     * @return void
     */
    addPagination: function() {
        var aLinkTexts = new Array();

        $('.pager > div.page').each(function(i) {
            var sTitle = $(this).attr('title') || $(this).find('h2:first').html() || (i + 1);
            aLinkTexts.push(sTitle);
        });

        $('.pager').pager('div.page', {
            navClass: 'pagerNav',
            navAttach: 'prepend',
            linkText: aLinkTexts
        });
    },

    /**
     * Fetch all tables of class "tableStatic1" and redesign them.
     *
     * @return void
     */
    redesignTablesStatic1: function() {
        $('.tableStatic1').each(function() {
            var oParent = $(this).parent();
            if (oParent.hasClass('page')) {
                var iHeight = parseInt(oParent.css('min-height'));
                if (isNaN(iHeight)) {
                    // Seems to be an IE
                    iHeight = parseInt(oParent.css('height'));
                }
                $(this).height(iHeight);
            }
        });
    },

    /**
     * Hides all event messages after some amount of time.
     *
     * @param  boolean  bStart
     * @return void
     */
    hideEventPanels: function(bStart) {
        if (true === bStart) {
            $('div#eventSave, div#eventEdit, div#eventRemove, div#eventError, div#eventWarning').fadeOut(1000);
        } else {
            delayObject = this;
            delayMethod = function() {
                delayObject.hideEventPanels(true);
            }
            setTimeout(delayMethod, 10000);
        }
    },

    /**
     * Init all accordions.
     *
     * @return void
     */
    addAccordions: function() {
        if ($.ui && $.ui.accordion) {
            if (!$.browser.msie) {
                $('a.accordionLink').click(function() {
                    $(this).blur();
                    if (!$(this).hasClass('selected')) {
                        $('div.accordionContent').hide();
                    }
                });
            }

            $('ul.accordionContainer').accordion({
                autoheight: true
            }).change(function() {
                if (!$.browser.msie) {
                    $('div.accordionContent').show();
                }
            });
        }
    },

    /**
     * Shows a loading panel in thickbox style. Usually used while an AJAX request
     * is running.
     *
     * @return void
     */
    showLoadingPanel: function() {
        if ($.browser.msie) {
            // Hide all visible form elements (Needed in IE because all <select> elements are kinda heavy weight)
            $('#content select').css('visibility', 'hidden');

            // IE doesn't know "position: fixed". So we gotta force him to fix the loading div via JS.
            var oDiv = $('div#loadingPanel');
            try {
                oDiv.css({'position': 'absolute', 'top': $(window).innerHeight() + $(document).scrollTop() - (($(window).innerHeight() - oDiv.height()) / 2) + 'px'});
            } catch (e) {
            }

            // Add scroll listener to the window
            try {
                $(window).scroll(function() {
                    oDiv.css('top', $(this).innerHeight() + $(document).scrollTop() - (($(this).innerHeight() - oDiv.height()) / 2) + 'px');
                });
            } catch (e) {
            }
        }

        // Fade in and resize loading panel container
        var oContainer = $('div#loadingPanelContainer');
        oContainer.fadeTo(0, 0, function() {
            $(this).css('visibility', 'visible').width('100%').height(Math.max($(document).height(), $(window).height())).fadeTo(100, 0.6);
        });
    },

    /**
     * Hides the loading panel.
     *
     * @return void
     */
    hideLoadingPanel: function() {
        // Show all visible form elements (Needed inside IE)
        if ($.browser.msie) {
            $('#content select').css('visibility', 'visible');
        }

        $('div#loadingPanelContainer').fadeTo(100, 0, function() {
            $(this).css('visibility', 'hidden');
        });
    },

    /**
     * This method can be used to dynamically add rows to a given table.
     *
     * @param  string  sUrl         The URL for the AJAX request.
     * @param  string  sIdHidden    The ID of the hidden field to increase the number of rows. This is needed to send the current number to the PHP.
     * @param  string  sIdTable     The ID of the <table> where to add the row.
     * @return void
     */
    ajaxAddElement: function(sUrl, sIdHidden, sIdTable) {
        this.showLoadingPanel();

        var iElements = 1;
        try {
            iElements = parseInt($('input[name=' + sIdHidden + ']').val());
        } catch (e) {
        }

        try {
            $.post(sUrl, {'number': iElements, 'idHidden': sIdHidden, 'idTable': sIdTable}, function(sData) {
                // Add response after last row
                $('#' + sIdTable + ' > tbody > tr:last').after(sData);

                // Update number of elements
                $('input[name=' + sIdHidden + ']').val(iElements + 1);

                Fp.hideLoadingPanel();
            });
        } catch (e) {
            this.hideLoadingPanel();
        }
    },

    /**
     * This method can be used to dynamically remove rows from a given table.
     *
     * @param  mixed   oElem      The element clicked on or the row index (iNumber)
     * @param  string  sUrl       The URL for the AJAX request (Optional).
     * @param  string  sIdHidden  The ID of the hidden field to decrease the number of rows. This is needed to send the current number to the PHP.
     * @param  string  sIdTable   The ID of the <table> where to remove the row.
     * @return void
     */
    ajaxRemoveElement: function(oElem, sUrl, sIdHidden, sIdTable) {
        // Fetch number of row from element class
        var iNumber = 0;
        if (typeof oElem == 'number') {
            iNumber = oElem;
        } else {
            try {
                iNumber = parseInt($(oElem).attr('class'));
            } catch (e) {
            }
        }

        if (typeof sUrl === 'string' && sUrl.length > 0) {
            this.showLoadingPanel();

            // The element is representing a database entry -> Remove it first
            try {
                $.post(sUrl, null, function(sData) {
                    Fp.__ajaxRemoveElement(iNumber, sIdHidden, sIdTable);
                    Fp.hideLoadingPanel();
                });
            } catch (e) {
                this.hideLoadingPanel();
            }
        } else {
            // The element is not representing a database entry -> No need to remove
            this.__ajaxRemoveElement(iNumber, sIdHidden, sIdTable);
        }
    },

    /**
     * This is the method which actually does the removing of rows from a given table.
     *
     * @param  int     iNumber    The number of the row to remove (Starting from 0).
     * @param  string  sIdHidden  The ID of the hidden field to decrease the number of rows. This is needed to send the current number to the PHP.
     * @param  string  sIdTable   The ID of the <table> where to remove the row.
     * @return void
     */
    __ajaxRemoveElement: function(iNumber, sIdHidden, sIdTable) {
        var i;
        var iElements = 1;
        try {
            iElements = parseInt($('input[name=' + sIdHidden + ']').val());
        } catch (e) {
        }

        // Reset row classes
        if (iElements > 1) {
            // Remove element
            $('#' + sIdTable + ' > tbody > tr:eq(' + iNumber + ')').remove();

            // Update number of elements
            $('input[name=' + sIdHidden + ']').val(iElements - 1);
        } else {
            // Just reset form contents
            $('#' + sIdTable + ' > tbody > tr').each(function() {
                $(this).find('select > option[selected]').removeAttr('selected');
                $(this).find('input.input, select, textarea').val('');
                $(this).find('input[type=radio][value=1]').attr('checked', 'checked');
                $(this).find('input[type=checkbox]').removeAttr('checked');
                $(this).find('input, select, textarea').filter('.disabled').attr('disabled', 'disabled');
                $(this).find('select').filter('.remove').empty();
                $(this).find('select').filter('.hide').empty().hide();
            });

            // Hide error <div>s that are eventually open
            $('div.error').hide();
        }

        // Reset form field names and row classes
        i = 0;
        $('#' + sIdTable + ' > tbody > tr').removeClass().each(function() {
            $(this).addClass((i % 2 ? 'even' : 'odd'));
            $(this).find('input.input, select, textarea').each(function() {
                $(this).attr('name', $(this).attr('name').replace(/\_[0-9]+\_/, '_' + i + '_'));
            });
            i++;
        });

        // Reset remove buttons
        i = 0;
        $('#' + sIdTable).find('td.remove img').each(function() {
            $(this).removeClass().addClass(new String(i));
            i++;
        });
    },

    /**
     * This method calls an URL and expects a JSON response which then is being used to fill a given <select> element.
     *
     * @param  object  oElemSrc     The source <select> element which invoked the call.
     * @param  string  sUrl         The URL to be called.
     * @param  string  sIdElemDest  The ID of the <select> element to be filled with the response array.
     * @return void
     */
    jsonSelect: function(oElemSrc, sUrl, sIdElemDest) {
        try {
            Fp.showLoadingPanel();
            var sSelected = $(oElemSrc).find('option[selected]').val();
            var oElemDest = $('#' + sIdElemDest);

            if (oElemDest) {
                // Remove existing children
                oElemDest.empty().show();

                // JSON call
                $.getJSON(sUrl, {index: sSelected}, function(mJson) {
                    if (typeof mJson === 'string') {
                        oElemDest.text(mJson);
                    } else {
                        // Add response elements as <option>s to the <select> element
                        $.each(mJson, function(mIndex, mValue) {
                            var oOption = document.createElement('option');
                            oOption.setAttribute('value', mIndex);
                            oOption.appendChild(document.createTextNode(mValue));
                            $(oOption).appendTo(oElemDest);
                        });
                    }

                    Fp.hideLoadingPanel();
                });
            }
        } catch (e) {
            Fp.hideLoadingPanel();
        }
    },

    /**
     * Unfortunately the jQuery.Thickbox class has one big disadvantage. The content of the help caption is
     * hard-coded in english only. So we gotta use this method to provide a flexible workaround to change
     * the help text by ourselves...
     *
     * @param  string  sTextNew
     * @return void
     */
    setHelpCloseText: function(sTextNew) {
        var oElem = $('#TB_closeWindowButton');
        if (typeof oElem === 'undefined' || null === oElem || !oElem.text) {
            return;
        }
        $('#TB_closeAjaxWindow').hide();

        var sText = oElem.text();
        if (typeof sText === 'undefined' || sText.length === 0) {
            setTimeout("Fp.setHelpCloseText('" + sTextNew + "')", 50);
            return;
        } else if (sText.toLowerCase() === 'close') {
            $('#TB_closeAjaxWindow').html(unescape(sTextNew)).show();
        }
    },

    /**
     * Adds the values of one or more elements to one ore more textfields or textareas.
     *
     * @param  object(s)  oElem1
     * @param  object(s)  oElem2
     * @param  string     sSeparator
     * @return void
     */
    addValuesToElements: function(oElem1, oElem2, sSeparator) {
        if (oElem1 && oElem1.length && oElem1.length > 0 && oElem2 && oElem2.length && oElem2.length > 0) {
            // Fetch the values
            var aValues = new Array();
            oElem1.each(function() {
                var sValue = null;

                switch ($(this).attr('type')) {
                case 'checkbox':
                    sValue = $('label[for=' + $(this).attr('id') + ']').html();
                    break;
                default:
                    sValue = $(this).html();
                    break;
                }

                if (null !== sValue) {
                    aValues.push(sValue);
                }
            });

            // Append the values
            sSeparator = typeof sSeparator === 'string' ? sSeparator : ', ';
            oElem2.each(function() {
                Fp.insertAtCaret(this, ($(this).val().length > 0 ? sSeparator : '') + aValues.join(sSeparator));
            });
        }
    },

    /**
     * Insert a given text at the current caret position of a given element.
     *
     * @param  object  oElem
     * @param  string  sText
     * @return void
     */
    insertAtCaret: function(oElem, sText) {
        var iStart = null;

        if (document.selection) {
            oElem.focus();
            var sOrig = oElem.value.replace(/\r\n/g, '\n');
            var oRange = document.selection.createRange();
            if (oRange.parentElement() != oElem) {
                return false;
            }
            oRange.text  = sText;
            var sCurrent = sTemp = oElem.value.replace(/\r\n/g, '\n');

            for (var iDiff = 0; iDiff < sOrig.length; iDiff++) {
                if (sOrig.charAt(iDiff) != sCurrent.charAt(iDiff)) {
                    break;
                }
            }

            for (var iIndex = 0, iStart = 0; sTemp.match(sText) && (sTemp = sTemp.replace(sText, '')) && iIndex <= iDiff; iIndex = iStart + sText.length) {
                iStart = sCurrent.indexOf(sText, iIndex);
            }
        } else if (oElem.selectionStart || oElem.selectionStart === 0) {
            iStart      = oElem.selectionStart;
            var iEnd    = oElem.selectionEnd;
            oElem.value = oElem.value.substr(0, iStart) + sText + oElem.value.substr(iEnd, oElem.value.length);
        }

        if (null !== iStart) {
            this.setCaretTo(oElem, iStart + sText.length);
        } else {
            oElem.value += sText;
        }
    },

    /**
     * Reset the caret position.
     *
     * @param  object  oElem
     * @param  int     iPos
     * @return void
     */
    setCaretTo: function(oElem, iPos) {
        if (oElem.createTextRange) {
            var oRange = oElem.createTextRange();
            oRange.move('character', iPos);
            oRange.select();
        } else if (oElem.selectionStart) {
            oElem.focus();
            oElem.setSelectionRange(iPos, iPos);
        }
    }
}

// Create a globally accessible CFp object
var Fp = CFp.getInstance();

// The DOM is ready -> Start the application
$(document).ready(function() {
    Fp.onDomReady();
});

