
// Simulate Classic Inheritance 
if (typeof TTOOP == 'undefined' || typeof TTOOP.extend != 'function') {
    var TTOOP = {}; 

    /**
     * Extend superClass with subClass
     * @param {Object} subClass
     * @param {Object} superClass
     */
    TTOOP.extend = function (subClass, superClass) {
        var F = function() {};
        F.prototype = superClass.prototype;
        subClass.prototype = new F();
        subClass.prototype.constructor = subClass;
        
        subClass.parent = superClass.prototype;
        if (superClass.prototype.constructor == Object.prototype.constructor) {
            superClass.prototype.constructor = superClass;
        }
    };
}

/**
 * Base Class Multichoice
 * @author Michael Dreher <dreher@traveltainment.de>
 * @copyright 2011 Traveltainment GmbH
 * @class The Base Class for multi-choice selects.
 * @constructor
 */
function TTBaseMultiChoice(instanceName, dataFieldName, guiFieldName, dataFieldSeparator, containerId, itemPrefix) {
    this.instanceName       = instanceName;             // Name der Instanzvariable
    this.dataFieldName      = dataFieldName;            // Hier kommen die Daten hin (hidden field)
    this.guiFieldName       = guiFieldName;             // Das Formularfeld (select/input)
    this.dataFieldSeparator = dataFieldSeparator;       // separator between multi options in data-field
    this.containerId        = containerId;              // ID of container element
    this.itemPrefix         = itemPrefix;               // Prefix of single choice item

    this.dataField          = null;                     // data field (hidden) that stores the selected items
    this.guiField           = null;                     // Sichtbares Formularelement
    this.dataArr            = [];                       // ausgewählte Elemente -> this.dataField bei submitValues()
    this.labels             = {};                       // Labels of the data field values
    this.callbacks          = {};                       // collection of callback methods
    this.isPopup            = false;                    // is instance in popup?
    this.config             = {};                       // configuration options
    this.isDataChanged      = false;                    // data field value changed from previous selection

    // Base Configuration Settings
    this.setConfig({
        groups:            [],                          // data groups
        popup:             {},                          // Popup config
        inputType:         'text',                      // Typ des Felds im Formular (text/select-one)
        labelAny:          'beliebig',                  // Label für "beliebig" 
        labelAnyLayer:     'beliebig',                  // Label für "beliebig" im Layer, wenn dafür eine Checkbox existiert
        labelMultiChoice:  'Mehrfachauswahl',           // Label für "Mehrfachauswahl" in Select, wenn Select verwendet wird
        controlType:       'input',                     // input / graphic
        jQueryShowSpeed:   0,                           // show/hide speed for jQuery
        orientation:       'vertical'                   // 'vertical': fill columns top to bottom; 'horizontal': fill columns line-based 
    });
}

/**
 * Sets the config options.
 * @param {Object} oConfig
 */
TTBaseMultiChoice.prototype.setConfig = function(oConfig) {
    if (typeof oConfig == 'undefined') {
        return;
    }
    for (var k in oConfig) {
        if (oConfig.hasOwnProperty(k)) {
            this.config[k] = oConfig[k];
        }
    }
};

/**
 * Initialize Callback Methods
 */
TTBaseMultiChoice.prototype.setCallbacks = function() {
};

/**
 * 
 */
TTBaseMultiChoice.prototype.setCallback = function(name, func) {
    this.callbacks[name] = func;
};

/**
 * Initialization of the multi choice instance. 
 * To call after include of this script and after hidden field for data is available.
 * @param {Object} oConfig
 */
TTBaseMultiChoice.prototype.init = function(oConfig) {
    this.setConfig(oConfig);
    this.setCallbacks();

    if (typeof this.callbacks.customerInit == 'function') {
        this.callbacks.customerInit();
    }

    // befindet sich die Instanz in einem Popup?
    try {
        this.isPopup = window.opener != null && typeof window.opener[this.instanceName] != 'undefined';
    } catch(ex) {
        // bleibt false
    }
    
    // hiddenfield
    this.dataField = this.isPopup ? opener.document.getElementById(this.dataFieldName) : document.getElementById(this.dataFieldName);
    if (this.dataField == null) {
        this.dataField = this.isPopup ? opener.document.bengine[this.dataFieldName] : document.bengine[this.dataFieldName];
    }
    if (this.dataField == null) {
        alert('Hidden Field <' + this.dataFieldName + '> nicht vorhanden!');
        return;
    }
    
    // formfield
    this.guiField = this.isPopup ? opener.document.getElementById(this.guiFieldName) : document.getElementById(this.guiFieldName);
    if (this.guiField == null) {
        this.guiField = this.isPopup ? opener.document.bengine[this.guiFieldName] : document.bengine[this.guiFieldName];
    }
    if (this.guiField == null) {
        alert('Formularfeld <' + this.guiFieldName + '> nicht vorhanden!');
        return;
    }
    this.config.inputType = this.guiField.type;
    if (this.config.inputType != 'text' && this.config.inputType != 'select-one') {
        alert('Bitte ein Textfeld oder ein Select im Formular für ' + this.guiFieldName + ' verwenden!');
        return;
    }
    
    if (typeof this.initFields == 'function') {
        this.initFields();
    }
    
    // Subclass hook to init data
    this.initData();
    
    // set form field values in object
    this.updateFromDataField();
    
    // Fill select if neccessary
    if (this.config.inputType == 'select-one' && !this.isPopup) {
        this.fillSelect();
    }
    
    // Update Form field value
    this.updateGuiField();
    
    // call open in popup on init
    if (this.isPopup) {
        this.open();
    }
    
    if (typeof this.callbacks.customerInitCallback == 'function') {
        this.callbacks.customerInitCallback();
    }
};


/**
 * Opens the tour operator multi choice box (layer or popup).
 */
TTBaseMultiChoice.prototype.open = function() {
    // set form field values in object
    this.updateFromDataField();

    if (typeof this.callbacks.customerOpen == 'function') {
        this.callbacks.customerOpen(this.config.groups);
        return;
    }
    
    if (typeof this.config.popup.url != 'undefined' && this.config.popup.url != '' && !this.isPopup) {
        // Popup
        var iH = 0;
        var oH = 0;
        if (document.body && document.body.offsetHeight && document.body.offsetHeight > 0) {          
            iH = document.body.offsetHeight;    
        } else if (document.body && document.body.clientHeight && document.body.clientHeight > 0) {         
            iH = document.body.clientHeight;    
        } else if (window.innerHeight && window.innerHeight > 0) {         
            iH = window.innerHeight;    
        }
        if (window.outerHeight && window.outerHeight > 0) {
            oH = window.outerHeight;
        } else if (screen.availHeight && screen.availHeight > 0) {
            oH = screen.availHeight;
        }         
        var screenY = 100;
        if (oH > iH && iH > 0) {
            screenY += parseInt(oH - iH);
        } 
        window.open(this.config.popup.url, this.config.popup.name, "width=" + this.config.popup.width + ",height=" + this.config.popup.height + ",left=140,top=" + screenY + ",status=no,location=no,menubar=no,toolbar=no,dependent=yes");        
    } else {
        this.printItems();
        this.printAdditional();
        
        // show multi choice box
        if (typeof jQuery != 'undefined') {
            jQuery('#' + this.containerId).show(this.config.jQueryShowSpeed);
        } else {
            document.getElementById(this.containerId).style.display = 'block';
        }
    }

    if (typeof this.callbacks.customerOpenCallback == 'function') {
        this.callbacks.customerOpenCallback();
    }
};

/**
 * Closes the tour operator multi choice box without setting the values.
 */
TTBaseMultiChoice.prototype.close = function() {
    if (typeof this.callbacks.customerClose == 'function') {
        this.callbacks.customerClose();
        return;
    }
    
    // hide multi choice box
    if (this.isPopup) {
        // Popup
        window.close();
    } else {
        // Layer
        if (typeof jQuery != 'undefined') {
            jQuery('#' + this.containerId).hide(this.config.jQueryShowSpeed);
        } else {
            document.getElementById(this.containerId).style.display = 'none';
        }
        
        if (typeof this.callbacks.customerCloseCallback == 'function') {
            this.callbacks.customerCloseCallback();
        }
    }
};

/**
 * Resets the tour operators choice to any
 */
TTBaseMultiChoice.prototype.clear = function() {
    for (var i = 0; i < this.dataArr.length; i++) {
        if (this.config.controlType == 'input') {
            this.checkItem(document.getElementById(this.itemPrefix + '_' + this.dataArr[i]), false);
        }
        this.checkItem(document.getElementById(this.itemPrefix + 'Checkbox_' + this.dataArr[i]), false);
    }
    this.dataArr = [];
    
    // subclass callback after submit
    if (typeof this.onAfterClear == 'function') {
        this.onAfterClear();
    }

    // Beliebig Element setzen
    this.setAnyElementValue(true);
    this.printItems();
};

/**
 * Submits the selected operators.
 */
TTBaseMultiChoice.prototype.submit = function() {
    var oldDataFieldValue = this.dataField.value;

    //if (typeof this.getDataArray == 'function') {
    //    this.dataField.value = this.getDataArray(this.dataArr).join(this.dataFieldSeparator);
    //} else 
    if (this.dataArr.length > 0) {
        this.dataField.value = this.dataArr.join(this.dataFieldSeparator);
    } else {
        this.dataField.value = '';
    }
    
    // selection changed?
    this.isDataChanged = oldDataFieldValue != this.dataField.value;

    // subclass callback after submit
    if (typeof this.onAfterSubmit == 'function') {
        this.onAfterSubmit();
    }
    this.close(); 
    this.updateGuiField();

    if (typeof this.callbacks.customerSubmitCallback == 'function') {
        this.callbacks.customerSubmitCallback(this.dataArr, this.isDataChanged);
    }
};

/**
 * Handles the click event of an item.
 * @param {Object} elem
 */
TTBaseMultiChoice.prototype.clickItem = function(elem) {
    var elemValue  = this.getItemValue(elem);
    var checkState = false;

    if (elemValue == '') {
        // beliebig gewählt
        this.clear();
    } else if (this.config.controlType == 'input' && !this.isItemChecked(elem) || this.config.controlType == 'graphic' && this.isItemChecked(elem)) {
        // switch off
        this.removeItemFromDataArray(elemValue);
        checkState = false;
    } else {
        // switch on
        this.addItemToDataArray(elemValue);
        checkState = true;
    }
    //console.debug(this.dataArr);
    
    this.updateItemView(elem, elemValue, checkState);
    
    if (typeof this.callbacks.customerClickItemCallback == 'function') {
        this.callbacks.customerClickItemCallback(elem);
    }
};

TTBaseMultiChoice.prototype.removeItemFromDataArray = function(elemValue) {
    for (var i = 0; i < this.dataArr.length; i++) {
        if (elemValue == this.dataArr[i]) {
            this.dataArr.splice(i, 1);
            break;
        }
    }
};

TTBaseMultiChoice.prototype.addItemToDataArray = function(elemValue) {
    this.dataArr.push(elemValue);
};

TTBaseMultiChoice.prototype.updateItemView = function(elem, elemValue, checkState) {
    if (this.config.controlType == 'graphic') {
        this.checkItem(elem, checkState);
    } else if (this.config.controlType == 'input') {
        this.checkItem(document.getElementById(this.itemPrefix + '_' + (elemValue == '' ? '0' : elemValue)), checkState);
    }

    // Beliebig Element setzen
    this.setAnyElementValue(this.dataArr.length == 0);
};

/**
 * 
 */
TTBaseMultiChoice.prototype.setAnyElementValue = function(value) {
    if (document.getElementById(this.itemPrefix + 'Checkbox_0') != null) {
        if (this.config.controlType == 'input') {
            this.checkItem(document.getElementById(this.itemPrefix + '_0'), value);
        }
        this.checkItem(document.getElementById(this.itemPrefix + 'Checkbox_0'), value);
    }
};

/**
 * Imports the data from the data field.
 */
TTBaseMultiChoice.prototype.updateFromDataField = function() {
    var value = this.dataField.value;  
    if (value != '-1' && value != '-1,' && value != '') {
        this.dataArr = value.split(this.dataFieldSeparator);
    } else {
        this.dataArr = [];
    }
    
    if (typeof this.updateFromDataFieldAdditional == 'function') {
        this.updateFromDataFieldAdditional();
    }
};

/**
 * Returns whether the selection of the user did change on the last submit event.
 */
TTBaseMultiChoice.prototype.isSelectionChanged = function() {
    return this.isDataChanged;
};

/**
 * Fills the select if it exists and is not already filled with more than one item.
 */
TTBaseMultiChoice.prototype.fillSelect = function() {
    if (this.config.inputType == 'select-one' && this.guiField.length < 2) {
        // If the select has only one options, it is assumed that it is the option 'any'
        if (this.guiField.length == 0) {
            // Any-Option
            this.guiField.options[0] = new Option(this.config.labelAny, '-1');
        }
        // Multi-Choice Option
        this.guiField.options[1] = new Option(this.config.labelMultiChoice, '-2');

        // Mehrfachauswahl onclick-event setzen (klappt nicht im IE)
        var THIS = this;
        this.guiField.options[1].onclick = function() {
            THIS.setValue('-2');
        };
        
        // Fill additional Items on top of list
        if (typeof this.getSelectAdditionalTop == 'function') {
            var addTop = this.getSelectAdditionalTop();
            for (var i = 0; i < addTop.length; i++) {
                this.guiField.options[this.guiField.options.length] = new Option(
                    addTop[i].label, 
                    addTop[i].value
                );
            }
        }

        // Fill Options
        for (var i = 0; i < this.config.groups.length; i++) {
            for (var j = 0; j < this.config.groups[i].length; j++) {
                this.guiField.options[this.guiField.options.length] = new Option(
                    this.labels[this.config.groups[i][j]].label, 
                    this.config.groups[i][j]
                );
            }
        }

        // Fill additional Items on bottom of list
        if (typeof this.getSelectAdditionalBottom == 'function') {
            var addBottom = this.getSelectAdditionalBottom();
            for (var i = 0; i < addBottom.length; i++) {
                this.guiField.options[this.guiField.options.length] = new Option(
                    addBottom[i].label, 
                    addBottom[i].value
                );
            }
        }
    }
};

/**
 * Sets a single value from for example a SELECT input
 * @param {Object} value
 */
TTBaseMultiChoice.prototype.setValue = function(value) {
    if (value == '-1') {
        // Beliebig
        this.dataField.value = '';
    } else if (value == '-2') {
        // Mehrfachauswahl
        this.open();
    } else if (value != '') {
        // Veranstalterkürzel
        this.dataField.value = value;
    }
    this.updateFromDataField();
    this.updateGuiFieldTitle();
};

/**
 * Returns the value of the given element.
 * @param {Object} elem
 */
TTBaseMultiChoice.prototype.getItemValue = function(elem) {
    if (typeof elem.type != 'undefined' && elem.type == 'checkbox') {
        return elem.value;
    } else {
        return elem.getAttribute('mcValue');
    }
};

/**
 * Updates the form field value.
 */
TTBaseMultiChoice.prototype.updateGuiField = function() {
    if (this.config.inputType == 'text') {
        // Text-Input
        if (this.dataArr.length == 0) {
            this.guiField.value = this.config.labelAny;
        } else if (this.dataArr.length == 1) {
            this.guiField.value = this.labels[this.dataArr[0]].label;
        } else {
            this.guiField.value = this.labels[this.dataArr[0]].label + ' ...';
        }
    } else {
        // Select-Input
        if (this.dataArr.length == 0) {
            this.guiField.selectedIndex = 0;
        } else if (this.dataArr.length == 1) {
            for (var i = 0; i < this.guiField.length; i++) {
                if (this.guiField.options[i].value == this.dataArr[0]) {
                    this.guiField.selectedIndex = i;
                    break;
                }
            }
        } else {
            this.guiField.selectedIndex = 1;
        }
    }
    this.updateGuiFieldTitle();
};

/**
 * Updates the title of the GUI field.
 */
TTBaseMultiChoice.prototype.updateGuiFieldTitle = function() {
    var tmpArr = [];
    for (var i = 0; i < this.dataArr.length; i++) {
        if (typeof this.labels[this.dataArr[i]] != 'undefined') {
            tmpArr[tmpArr.length] = this.labels[this.dataArr[i]].label;
        }
    }
    this.guiField.title = tmpArr.join(', ');
};

/**
 * Adds a label for the given value.
 * @param {Object} value
 * @param {Object} label
 */
TTBaseMultiChoice.prototype.setLabel = function(value, label) {
    this.labels[value] = {
        label: label,
        value: value
    };
};

/**
 * Adds a group of values to the multi choice
 * @param {Object} groupArr Array of group values
 */
TTBaseMultiChoice.prototype.addGroup = function(groupArr) {
    this.config.groups[this.config.groups.length] = groupArr;
};

/**
 * Sets an array of values active.
 * @param {Array} values Array of values.
 * @param {bool}  clear  TRUE if all currently active values shall be cleared before setting the new values, FALSE otherwise.
 */
TTBaseMultiChoice.prototype.setValuesActive = function(values, clear) {
    if (clear) {
        this.dataArr = [];
    }
    for (var i = 0; i < values.length; i++) {
        if (!TTBaseMultiChoice.containsItem(values[i], this.dataArr)) {
            this.dataArr[this.dataArr.length] = values[i];
        }
    }
    this.printItems();
};

/**
 * Sets an array of values inactive.
 * @param {Array} values
 */
TTBaseMultiChoice.prototype.setValuesInactive = function(values) {
    var delItems = [];
    for (var i = 0; i < this.dataArr.length; i++) {
        if (TTBaseMultiChoice.containsItem(this.dataArr[i], values)) {
            delItems[delItems.length] = i;
        }
    }
    for (var i = delItems.length; i >= 0; i--) {
        this.dataArr.splice(i, 1);
    }
    this.printItems();
};

/**
 * Sets a group defined by the index in the init-array active, without changing other values.
 * @param {int} groupIndex Array-Index of the group.
 * @param {bool}  clear    TRUE if all currently active values shall be cleared before setting the new values, FALSE otherwise.
 */
TTBaseMultiChoice.prototype.setGroupActive = function(groupIndex, clear) {
    if (typeof this.config.groups[groupIndex] == 'undefined') {
        return;
    }
    this.setValuesActive(this.config.groups[groupIndex], clear);
};

/**
 * Sets a group defined by the index in the init-array inactive.
 * @param {int} groupIndex Array-Index of the group.
 */
TTBaseMultiChoice.prototype.setGroupInactive = function(groupIndex) {
    if (typeof this.config.groups[groupIndex] == 'undefined') {
        return;
    }
    this.setValuesInactive(this.config.groups[groupIndex]);
};

/**
 * Prints the items to the existing containers, including the any-item if neccessary.
 */
TTBaseMultiChoice.prototype.printItems = function() {
    // Element beliebig
    var elemAny = document.getElementById(this.itemPrefix + 'Any');
    if (elemAny != null) {
        elemAny.innerHTML = this.getItemSrcAny();
    }
    
    for (var i = 0; i < this.config.groups.length; i++) {
        var elems = [];
        // find column containers to fill
        for (var j = 1; j <= 10; j++) {
            var elem = document.getElementById(this.itemPrefix + 's-' + i + '-' + j);
            if (elem == null) {
                break;
            }
            elems[elems.length] = elem;
        }
        // fill column containers with operators
        this.fillItems(elems, this.config.groups[i]);
    }    
};

/**
 * Prints all items for one group into the existing containers for that group.
 * @param {Object} elems
 * @param {Object} items
 */
TTBaseMultiChoice.prototype.fillItems = function(elems, items) {
    // Spalten durchlaufen
    for (var i = 0; i < elems.length; i++) {
        var col      = elems[i];
        var html     = '';
        var nrPerCol = Math.ceil(items.length / elems.length);
         
        for (var j = i * nrPerCol; j < (i + 1) * nrPerCol && j < items.length; j++) {
            html += this.getItemSrc(
                items[j], 
                this.dataArr.length > 0 && TTBaseMultiChoice.containsItem(items[j], this.dataArr)
            );
        }
        col.innerHTML = html;
    }
};

TTBaseMultiChoice.prototype.printAdditional = function() {
};


// -------------------------------
/**
 * Adds 'Over' to the class name of the given element.
 * @param {Object} elem
 */
TTBaseMultiChoice.prototype.mouseOver = function(elem) {
    if (!this.isItemChecked(elem)) {
        elem.className = elem.className + 'Over';
    }
};

/**
 * Removes 'Over' from the end of the class name of the given element.
 * @param {Object} elem
 */
TTBaseMultiChoice.prototype.mouseOut = function(elem) {
    if (!this.isItemChecked(elem)) {
        var tmp = elem.className.match(/^(.+)Over$/);
        if (tmp) {
            elem.className = tmp[1];
        }
    }
};

/**
 * Checks whether the class name of the given element ends with 'Checked',
 * or if it's a checkbox element checks whether its checked-attribute is true. 
 * @param {Object} elem
 */
TTBaseMultiChoice.prototype.isItemChecked = function(elem) {
    if (typeof elem.type != 'undefined' && elem.type == 'checkbox') {
        return elem.checked;
    } else {
        return elem.className.match(/^(.+)Checked$/);
    }
};

/**
 * Sets the checked-attribute of the given element if it's a checkbox, otherwise
 * adds 'Checked' to the end of the class name of the element, removing 'Over' from the end if neccessary. 
 * @param {Object} elem
 * @param {Object} value
 */
TTBaseMultiChoice.prototype.checkItem = function(elem, value) {
    if (typeof elem == 'undefined' || elem == null) {
        return;
    }
    if (typeof elem.type != 'undefined' && elem.type == 'checkbox') {
        elem.checked = value;
    } else {
        var className = elem.className;
        var tmp = className.match(/^(.+)Over$/);
        if (tmp) {
            className = tmp[1];
        }

        var tmp = className.match(/^(.+)Checked$/);
        if (tmp && !value) {
            elem.className = tmp[1];
        } else if (tmp == null && value) {
            elem.className = className + 'Checked';
        }
    }
};

/**
 * Prevents bubbling on mouseover/-out
 * @param {Object} e
 * @param {Object} handler
 */
TTBaseMultiChoice.prototype.isMouseLeaveOrEnter = function(e, handler) {
    if (!e) { 
        var e = window.event;
    }
    if (e.type != 'mouseout' && e.type != 'mouseover') { 
        return false;
    }
    if (!handler) {
        return false;
    }     
    var reltg = e.relatedTarget ? e.relatedTarget : e.type == 'mouseout' ? e.toElement : e.fromElement;     
    while (reltg && reltg != handler) {
        reltg = reltg.parentNode;
    } 
    return (reltg != handler);
};

TTBaseMultiChoice.prototype.parseUmlaute = function(str) {
    str = str.replace(/^Ae/, '\xC4');
    str = str.replace(/^Oe/, '\xD6');
    //str = str.replace(/Ue/, '\xDC');
    return str;
};

/**
 * Search Element in Array.
 * @param {Object} needle
 */
TTBaseMultiChoice.containsItem = function(needle, haystack) {
    for (var i = 0; i < haystack.length; i++) {
        if (haystack[i] == needle) return true;
    }
    return false;
};

TTBaseMultiChoice.arrUnique = function(arr) {
    arr     = arr.sort(function(a, b) { return a - b; });
    var ret = [];
    if (arr.length > 0) {
        ret[0] = arr[0];
    }

    for (var i = 1; i < arr.length; i++) {
        if (arr[i] != arr[i - 1]) {
            ret[ret.length] = arr[i];
        }
    }

    return ret;
};


