﻿var Bwt = {
    /* @const
    * @type {boolean}
    */
    Debug: false,
    /* @const
    * @type {boolean}
    */
    ShowDebugWindow: false,
    Data: {
        Bindings: {},
        Stores: {},
        Types: {},
        ColumnModels: {},
        Grids: {},
        SelectionModels: {},
        Timers: {}
    }
};

if (typeof window.console === "undefined") {
    var console = {};

    if (Bwt.ShowDebugWindow) {
        Bwt.Data.LogData = "";

        var func = function(x) {
            x = '\n' + x;
            var elem = Ext.getCmp('xxdebugpanelxx');
            if (elem) {
                var v = elem.getValue();
                elem.setValue(v + x);
            } else {
                Bwt.Data.LogData += x;
            }
        };
        Ext.onReady(function() {
            var win = new Ext.Window({
                id: 'xxdebugpanelxxWindow',
                layout: 'fit',
                title: 'debug',
                items: { id: 'xxdebugpanelxx', xtype: 'textarea', value: Bwt.Data.LogData },
                maximizable: true
            });
            win.show();
            win.toFront();
        });
        console = {
            dir: func,
            monitor: func,
            debug: func,
            profile: func,
            log: func,
            error: func,
            warn: func,
            info: func,
            profileEnd: func,
            unmonitor: func
        };
    } else {
        console = {
            dir: function() { },
            monitor: function() { },
            debug: function() { },
            profile: function() { },
            log: function() { },
            error: function() { },
            warn: function() { },
            info: function() { },
            profileEnd: function() { },
            unmonitor: function() { }
        };
    }
}

/**
* @constructor
*/
Bwt.Message = function(_channel, _sender, _action, _content, _config) {
    Bwt.Message.Counter += 1;
    this.Name = Bwt.Message.Counter;
    this.Token = Bwt.Comet.token;
    this.Channel = _channel;
    this.Sender = _sender;
    this.Action = _action;
    this.Content = _content;
    this.Config = _config;

    this.toJson = function() {
        var result = "{__type: \'Message\', Name: \'";
        result += this.Name;
        result += "\', Token: \'";
        result += this.Token;
        result += "\', Channel: \'";
        result += this.Channel;
        result += "\', Action: \'";
        result += this.Action;
        result += "\', Sender: \'";
        result += this.Sender;
        result += "\', Content: '";
        result += this.Content;
        result += "', Config: ";

        result += Ext.util.JSON.encode(this.Config);

        result += "}";

        return result;
    };

    this.toFormattedJson = function() {
        var result = "{\n\t__type: \'Message\', \n\tName: \'";
        result += this.Name;
        result += "\', \n\tToken: \'";
        result += this.Token;
        result += "\', \n\tChannel: \'";
        result += this.Channel;
        result += "\', \n\tAction: \'";
        result += this.Action;
        result += "\', \n\tSender: \'";
        result += this.Sender;
        result += "\', \n\tContent: '";
        result += this.Content;
        result += "', \n\tConfig: ";

        result += Ext.util.JSON.encode(this.Config);

        result += "\n}";

        return result;
    };
};
Bwt.Message.Counter = 0;

/**
* @constructor
*/
Bwt.Comet = function(config) {
    this.events = {
        receive: true
    };

    Ext.apply(this, config || {});

    Ext.Ajax.on("requestexception", function(conn, response, options) {
        console.warn("Request Exception: ", conn, response, options);
        Bwt.Comet._errorCounter += 1;
        delete Bwt.Comet._requestList[options.params.identifier];
        if (Bwt.Comet._errorCounter > 10) {
            Bwt.Comet.stop();
        }
    }, this);

    Ext.EventManager.addListener(window, "beforeunload", function() {
        Bwt.Comet.stop();
    });
};
Bwt.Comet.prototype = {
    url: null,
    beforeListeners: {},
    listeners: {},
    afterListeners: {},
    stopped: true,
    isPolling: false,
    token: '',
    windowInstance: '',
    useCookies: true,
    _totalRequestCounter: 0,
    _requestList: {},
    _pollingRequest: null,
    _errorCounter: 0,
    _paused: false,
    _pauseQueue: [],

    start: function() {
        this._errorCounter = 0;
        if (this.stopped) {
            this._hello();
        }
    },

    stop: function() {
        this.stopped = true;

        for (var id in this._requestList) {
            if (Ext.Ajax.isLoading(this._requestList[id].id)) {
                Ext.Ajax.abort(this._requestList[id].id);
            }
            delete this._requestList[id].obj;
        }

        this._requestList = {};
    },

    pause: function() {
        this._paused = true;
    },

    resume: function() {
        if (this._paused) {
            this._paused = false;
            this._sendQueuedMessages();
        }
    },

    sendHook: function() {
    },

    deactivatedHook: function() {
        alert("window is deactivated.");
    },

    _sendQueuedMessages: function() {
        this._pauseQueue.reverse();
        this._sendMessage(this._pauseQueue, this._requestCallback);
    },

    _array2JsonMsg: function(msglist) {
        var datapacket = "[";
        var first = true;
        for (var key in msglist) {
            if (typeof msglist[key] === 'function') { continue; }
            if (!first) {
                datapacket += ", ";
            }
            first = false;
            datapacket += msglist[key].toJson();
        }
        datapacket += "]";
        return datapacket;
    },

    _sendMessage: function(msg, callback) {
        var datapacket = null;
        if (Bwt.isArray(msg)) {
            datapacket = this._array2JsonMsg(msg);
        } else {
            datapacket = msg.toJson();
        }

        if (datapacket !== null && datapacket !== undefined) {
            if (this.sendHook) {
                this.sendHook(msg);
            }

            this._totalRequestCounter += 1;

            var identifier = this._totalRequestCounter;
            var requestObj = {
                url: this.url,
                callback: callback,
                scope: this,
                params: {
                    data: datapacket,
                    identifier: identifier
                }
            };
            var requestId = Ext.Ajax.request(requestObj);


            this._requestList[identifier] = {
                id: requestId,
                obj: requestObj
            };

            return requestId;
        } else {
            console.warn("Bwt.Comet._sendMessage: no data to send");
        }

        return null;
    },

    _hello: function() {
        if (this.useCookies) {
            this.token = Bwt.Cookie("BWT.SessionId") || this.token;
        }
        this.token = this.token || "";
        this._sendMessage(new Bwt.Message("", "Client", "Hello", "", {}), this._requestCallback);
        this._helloSend = true;
    },

    _goodbye: function() {
        this._sendMessage(new Bwt.Message("", "Client", "Goodbye", "", {}), this._requestCallback);
    },

    _poll: function() {
        if ((this._pollingRequest === null || !Ext.Ajax.isLoading(this._pollingRequest)) && !this._paused) {
            this._pollingRequest = this._sendMessage(new Bwt.Message("", "Client", "Ping", "", { instance: this.windowInstance }), this._requestCallback);
            console.info("poll send");
        }
    },

    _requestCallback: function(options, success, response) {
        if (success) { this._errorCounter = 0; }
        if (Bwt.Debug) {
            console.log("Message-Response: ", response);
        }
        if (success) {
            this._convertAndNotify(response.responseText);
        }

        delete this._requestList[options.params.identifier];

        if (!this.stopped) {
            this._poll();
        }
    },

    _convertAndNotify: function(data) {
        if (data !== null && data !== undefined && data.length > 0) {
            //console.log(data);

            var msgData = Ext.util.JSON.decode(data) || [];

            if (Ext.isArray(msgData)) {
                for (var i in msgData) {
                    if (msgData[i] && typeof (msgData[i]) === 'object') {
                        this._notify(this._createMessageFromObject(msgData[i]));
                    }
                }
            } else if (msgData) {
                this._notify(this._createMessageFromObject(msgData));
            }
        }
    },

    _createMessageFromObject: function(obj) {
        var msg = new Bwt.Message();
        Ext.apply(msg, obj);
        return msg;
    },

    beforeReload: function() { return true; },
    reload: function() { document.location.reload(true); },

    _notify: function(msg) {
        //notify listeners
        if (msg) {
            if (msg.Action === "NOP") {
                //do nothing
            } else if (msg.Action === "Reload") {
                if (this.beforeReload()) {
                    this.reload();
                }
            } else if (msg.Action === "Hello") {
                if (this._helloSend) {
                    this.stopped = false;
                    this._helloSend = false;
                }
                this.token = msg.Content;
                this.windowInstance = msg.Config.instance;
                if (this.useCookies) {
                    Bwt.Cookie("BWT.SessionId", this.token);
                }
                this._execOnReady();
            } else if (msg.Action === "Deactivate" && !this.stopped) {
                this.stop();
                Bwt.Comet.deactivatedHook();
            } else {
                this._notifyListeners(msg, this.listeners);
                this._notifyListeners(msg, this.afterListeners);
                this._notifyListeners(msg, this.beforeListeners);
            }

            this._handleDownloadMessage(msg);
        }
    },

    _onReadyList: [],

    onReady: function(func) {
        this._onReadyList.push(func);
    },

    _execOnReady: function() {
        for (var i in this._onReadyList) {
            this._onReadyList[i]();
        }
    },

    _isIE: function() {
        var result = !!window.ActiveXObject;
        var ua = navigator.userAgent.toLowerCase();
        result = result && /msie/.test(ua) && !/opera/.test(ua);
        return result;
    },

    _handleDownloadMessage: function(msg) {
        if (msg.Channel === "Application/Download" && msg.Action === 'Download') {
            if (!this._isIE()) {
                var iframe = document.getElementById("BwtDownloadFrame");
                if (!iframe) {
                    iframe = document.createElement("iframe");
                    iframe.id = "BwtDownloadFrame";
                    iframe.style.display = "none";
                    document.body.appendChild(iframe);
                }
                iframe.src = msg.Config.url;
            } else {
                Ext.NotificationMessage.msg("Download", '<a href="' + msg.Config.url + '" target="blank">' + msg.Config.filename + '</a>');
            }
        }
    },

    _notifyListeners: function(msg, listeners) {
        var j;
        if (msg.Channel) {
            if (listeners[msg.Channel]) {
                for (j = 0; j < listeners[msg.Channel].length; j++) {
                    listeners[msg.Channel][j](msg);
                }
            }
            if (listeners['']) {
                for (j = 0; j < listeners[''].length; j++) {
                    listeners[''][j](msg);
                }
            }
        } else {
            for (var channel in listeners) {
                if (listeners[channel]) {
                    for (j = 0; j < listeners[channel].length; j++) {
                        listeners[channel][j](msg);
                    }
                }
            }
        }
    },

    subscribeBefore: function(channel, func) {
        this._subscribeToListenerList(channel, func, this.beforeListeners);
    },

    subscribe: function(channel, func) {
        this._subscribeToListenerList(channel, func, this.listeners);
    },

    subscribeAfter: function(channel, func) {
        this._subscribeToListenerList(channel, func, this.afterListeners);
    },

    _subscribeToListenerList: function(channel, func, listenerlist) {
        if (!channel) { channel = ''; }
        if (!listenerlist[channel]) {
            listenerlist[channel] = [];
        }
        listenerlist[channel].push(func);
    },

    unsubscribeBefore: function(channel, func) {
        this._unsubscribeFromListenerList(channel, func, this.beforeListeners);
    },

    unsubscribe: function(channel, func) {
        this._unsubscribeFromListenerList(channel, func, this.listeners);
    },

    unsubscribeAfter: function(channel, func) {
        this._unsubscribeFromListenerList(channel, func, this.afterListeners);
    },

    _unsubscribeFromListenerList: function(channel, func, listenerlist) {
        if (listenerlist[channel]) {
            var templisteners = listenerlist[channel];
            for (var i = 0; i < templisteners.length; i++) {
                if (templisteners[i] === func) {
                    listenerlist[channel].splice(i, 1);
                    break;
                }
            }
        }
    },

    publish: function(msg) {
        if (this._paused) {
            this._pauseQueue.push(msg);
        } else {
            this._sendMessage(msg, this._requestCallback);
        }
    }
};

function toIterable(iterable) {
    if (!iterable) { return []; }
    var length = iterable.length || 0, results = [length];
    while (length--) { results[length] = iterable[length]; }
    return results;
}

function bind() {
    if (arguments.length < 2 && typeof arguments[1] === "undefined") { return arguments[0]; }
    var args = toIterable(arguments);
    var __method = args.shift(), object = args.shift();
    return function() {
        return __method.apply(object, args.concat(toIterable(arguments)));
    };
}

/**
* Binds on a Channel to the server
* @constructor
*/
Bwt.Binding = function(_channel, _sender, _updatefunc, _generateparamsfunc, _validateArguments, _recordId, _recordIdProperty) {
    this.channel = _channel;
    this.sender = _sender;
    this.updatefunc = _updatefunc;
    this.generateparamsfunc = _generateparamsfunc;
    this.validateArguments = _validateArguments;
    this.recordId = _recordId;
    this.recordIdProperty = _recordIdProperty;
    this.currentData = null;

    this._init = function() {
        this.sourceUpdated = bind(this.sourceUpdated, this);
        Bwt.Comet.subscribe(this.channel, this.sourceUpdated);
    };

    this.destroy = function() {
        Bwt.Comet.unsubscribe(this.channel, this.sourceUpdated);
    };

    this._isSingleInstanceBinding = function() {
        return typeof this.recordId !== 'undefined' &&
               this.recordId !== null &&
               typeof this.recordId !== 'undefined' &&
               typeof this.recordIdProperty !== 'undefined' &&
               this.recordIdProperty !== null &&
               typeof this.recordIdProperty !== 'undefined';
    };

    this._dataListContainsRecord = function(data) {
        var containsRecord = false;
        for (var id in data) {
            if (id === this.recordId) {
                containsRecord = true;
                break;
            }
        }
        return containsRecord;
    };

    this._isRelatevant = function(data, islist) {
        var relevant = true;
        if (this._isSingleInstanceBinding()) {
            if (islist) {
                relevant = this._dataListContainsRecord(data);
            } else {
                relevant = data[this.recordIdProperty] === this.recordId;
            }
        }
        return relevant;
    };

    /// is called on update message from server
    this.sourceUpdated = function(msg) {
        if (msg.Action === "Update") {
            if (msg.Config && msg.Config.data) {
                var islist = msg.Content === "list";
                if (this._isRelatevant(msg.Config.data, islist)) {
                    this.updatefunc(msg.Config.data, islist);
                    this.currentData = msg.Config.data;
                }
            }
        }
    };

    /// sends an read message to the server
    this.updateSource = function() {
        var config = {};
        config.parameter = [];
        for (var i = 0; i < arguments.length; i++) {
            config.parameter[i] = arguments[i];
        }
        var proceed = true;
        if (typeof this.validateArguments === "function") {
            proceed = this.validateArguments(config.parameter);
        }
        if (proceed) {
            var msg = new Bwt.Message(this.channel, this.sender, "Read", '', config);
            Bwt.Comet.publish(msg);
        }
    } .createDelegate(this);

    /// sends an update message to the server (new data at clientside)
    this.updateTarget = function(record) {
        var params = [];
        if (typeof this.generateparamsfunc === "function") {
            params = this.generateparamsfunc(record);
        }
        var msg = new Bwt.Message(this.channel, this.sender, "Update", '', { parameter: params, data: record.data });
        Bwt.Comet.publish(msg);
    };

    /// sends an delete message to the server (deleted data at clientside)
    this.removeTarget = function(record) {
        var params = [];
        if (typeof this.generateparamsfunc === "function") {
            params = this.generateparamsfunc(record);
        }
        var msg = new Bwt.Message(this.channel, this.sender, "Delete", '', { parameter: params });
        Bwt.Comet.publish(msg);
    };

    this._init();
};

Bwt.BindingTargetUpdated = function(store, record, operation, binding) {
    if (operation === Ext.data.Record.COMMIT) {
        binding.updateTarget(record);
    }
};

Bwt.BindingTargetRemoved = function(record, binding) {
    if (record) {
        binding.removeTarget(record);
    }
};

Bwt._ExtractData = function(obj) {
    var data = {};
    for (var property in obj) {
        if (typeof obj[property] != 'function') {
            data[property] = obj[property];
        }
    }
    return data;
};
Bwt._UpdateStore = function(store, record, newvalue, islist, addFn) {
    //TODO add updatepaused functionality
    if (newvalue) {
        if (islist) {
            store.removeAll();
            for (var key in newvalue) {
                if (newvalue[key] && typeof newvalue[key] != 'function') {
                    var data = Bwt._ExtractData(newvalue[key]);
                    addFn(store, new record(data));
                }
            }
        } else {
            console.log('updating single item');
            var row = store.findExact('Id', newvalue.Id);
            console.log('updateing row: ' + row + ' of: ' + store.getCount());
            if (row != null && row != undefined && row >= 0 && row < store.getCount()) {
                store.remove(store.getAt(row));
            }
            var data = Bwt._ExtractData(newvalue);
            addFn(store, new record(data));
        }
    } else {
        console.error('Bwt.ServerStore.UpdateStore() - newvalue is null');
    }
};
Bwt.UpdateStore = function(store, record, newvalue, islist) {
    Bwt._UpdateStore(store, record, newvalue, islist, function(store, rec) {
        store.add([rec]);
    });
};
Bwt.UpdateSortedStore = function(store, record, newvalue, islist) {
    Bwt._UpdateStore(store, record, newvalue, islist, function(store, rec) {
        store.addSorted(rec);
    });
};

Bwt.UpdateColumnConfig = function(cm, newColumns) {
    if (!cm) {
        return;
    }
    for (var i = 0; i < cm.columns.length; i++) {
        if (i >= newColumns.length) { break; }

        // preserve layout
        var oldColumn = cm.getColumnAt(i);
        if (oldColumn) {
            if (oldColumn.width) { newColumns[i].width = cm.getColumnAt(i).width; }
            if (cm.isHidden(i)) { newColumns[i].hidden = true; }
        }
    }
    cm.setConfig(newColumns);
};

Bwt.isArray = function(v) {
    return v && typeof v.length === "number" && typeof v.splice === "function";
};

Bwt.trim = function(text) {
    return (text || "").replace(/^\s+|\s+$/g, "");
};

Bwt.Cookie = function(name, value, options) {
    if (typeof value !== 'undefined') { // name and value given, set cookie
        options = options || {};
        if (value === null) {
            value = '';
            options.expires = -1;
        }
        var expires = '';
        if (options.expires && (typeof options.expires === 'number' || options.expires.toUTCString)) {
            var date;
            if (typeof options.expires === 'number') {
                date = new Date();
                date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
            } else {
                date = options.expires;
            }
            expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
        }
        // CAUTION: Needed to parenthesize options.path and options.domain
        // in the following expressions, otherwise they evaluate to undefined
        // in the packed version for some reason...
        var path = options.path ? '; path=' + (options.path) : '';
        var domain = options.domain ? '; domain=' + (options.domain) : '';
        var secure = options.secure ? '; secure' : '';
        document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
    } else { // only name given, get cookie
        var cookieValue = null;
        if (document.cookie && document.cookie !== '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = Bwt.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
};

Bwt.Data.SetWatchDog = function(key, delay, data, fn) {
    if (!Bwt.Data.Timers[key]) {
        Bwt.Data.Timers[key] = { timer: null, fn: fn, data: data };
    }
    if (Bwt.Data.Timers[key].timer) {
        window.clearTimeout(Bwt.Data.Timers[key].timer);
    }
    if (data) {
        Bwt.Data.Timers[key].data = data;
    }
    if (fn) {
        Bwt.Data.Timers[key].fn = fn;
    }
    if (Bwt.Data.Timers[key].fn) {
        Bwt.Data.Timers[key].timer = window.setTimeout(Bwt.Data.Timers[key].fn, delay);
    }
};

Bwt.TranslationIetfLanguageTag = null;
Bwt.Translate = function(values) {
    var result = "";
    if (Bwt.TranslationIetfLanguageTag === null) {
        console.error("Bwt.Translate - No language selected!");
    } else {
        result = values[Bwt.TranslationIetfLanguageTag];
    }
    return result;
};
/**
* @constructor
*/
Bwt.ServerSelectionModel = function() {

    this.Setup = function(id) {
        var timerFunction = function(id) {
            var t = Bwt.Data.Timers[id];
            var b = Bwt.Data.Bindings[id];
            if (t) { window.clearTimeout(t.timer); }
            if (t && t.data && b) {
                var msg = new Bwt.Message(b.channel, b.sender, 'Update', '', { data: t.data });
                Bwt.Comet.publish(msg);
            }
        };

        var updateTargetfunction = function(sm, selection) {
            if (!selection) { return; }
            var col = selection.cell[1];
            var row = selection.cell[0];

            if (this.IsSelectionChanged(sm, col, row)) {
                console.log('selectionchange()', [row, col]);
                col = sm.grid.getColumnModel().getDataIndex(col);
                var record = sm.grid.getStore().getAt(row);
                row = record.get('Id') || record.data.Id || record.json.Id;
                Bwt.Data.SetWatchDog(id, 500, { row: row, col: col }, timerFunction.createDelegate(this, [id], false));
            }
        };

        var s = Bwt.Data.SelectionModels[id];
        if (s) {
            s.on('selectionchange', updateTargetfunction.createDelegate(this));
            s.destroy = function() {
                var sm = Bwt.Data.SelectionModels[id];
                if (sm) {
                    sm.purgeListeners();
                    sm.un('selectionchange', updateTargetfunction.createDelegate(this));
                }
                var b = Bwt.Data.Bindings[id];
                if (b && b.destroy) {
                    Bwt.Data.Bindings[id].destroy();
                }
            };
        }
    };

    this.IsSelectionChanged = function(sm, rowIndex, colIndex) {
        if (this.IsSingleSelect(sm)) {
            if ((sm.selectedCellRange[0] === rowIndex) && (sm.selectedCellRange[1] === colIndex)) {
                return false;
            }
        }
        return true;
    };

    this.IsSingleSelect = function(sm) {
        if (sm.selectedCellRange
            && (sm.selectedCellRange[0] === sm.selectedCellRange[2])
            && (sm.selectedCellRange[1] === sm.selectedCellRange[3])) {
            return true;
        }
        return false;
    };
};
Bwt.ServerSelectionModel = new Bwt.ServerSelectionModel();

