/*
 * Copyright 2014 XWiki SAS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
define([
    '/common/common-util.js',
    '/customize/application_config.js',
    '/bower_components/chainpad/chainpad.dist.js'
], function (Util, AppConfig, ChainPad) {
    var module = { exports: {} };

    var badStateTimeout = typeof(AppConfig.badStateTimeout) === 'number' ?
        AppConfig.badStateTimeout : 30000;

    var verbose = function (x) { console.log(x); };
    verbose = function () {}; // comment out to enable verbose logging

    module.exports.start = function (config) {
        var onConnectionChange = config.onConnectionChange || function () { };
        var onRemote = config.onRemote || function () { };
        var onInit = config.onInit || function () { };
        var onLocal = config.onLocal || function () { };
        var setMyID = config.setMyID || function () { };
        var onReady = config.onReady || function () { };
        var onError = config.onError || function () { };
        var userName = config.userName;
        var initialState = config.initialState;
        if (config.transformFunction) { throw new Error("transformFunction is nolonger allowed"); }
        var patchTransformer = config.patchTransformer;
        var validateContent = config.validateContent;
        var avgSyncMilliseconds = config.avgSyncMilliseconds;
        var logLevel = typeof(config.logLevel) !== 'undefined'? config.logLevel : 1;
        var readOnly = config.readOnly || false;
        var sframeChan = config.sframeChan;
        var metadataMgr = config.metadataMgr;
        var updateLoadingProgress = config.updateLoadingProgress;
        config = undefined;

        var evPatchSent = Util.mkEvent();
        var chainpad;

        var makeChainPad = function () {
            var _chainpad = ChainPad.create({
                userName: userName,
                initialState: initialState,
                patchTransformer: patchTransformer,
                validateContent: validateContent,
                avgSyncMilliseconds: avgSyncMilliseconds,
                logLevel: logLevel
            });
            _chainpad.onMessage(function(message, cb) {
                // -1 ==> no timeout, we may receive the callback only when we reconnect
                sframeChan.query('Q_RT_MESSAGE', message, function (_err, obj) {
                    var err = _err || (obj && obj.error);
                    if (!err) { evPatchSent.fire(); }
                    cb(err);
                }, { timeout: -1 });
            });
            _chainpad.onPatch(function () {
                onRemote({ realtime: chainpad });
            });
            return _chainpad;
        };
        chainpad = makeChainPad();

        var myID;
        var isReady = false;
        var isHistory = 1;
        var evConnected = Util.mkEvent(true);
        var evInfiniteSpinner = Util.mkEvent(true);

        window.setInterval(function () {
            if (!chainpad || !myID) { return; }
            var l;
            try {
                l = chainpad.getLag();
            } catch (e) {
                throw new Error("ChainPad.getLag() does not exist, please `bower update`");
            }
            if (l.lag < badStateTimeout) { return; }
            evInfiniteSpinner.fire();
        }, 2000);

        sframeChan.on('EV_RT_DISCONNECT', function (isPermanent) {
            isReady = false;
            chainpad.abort();
            // Permanent flag is here to choose if we wnat to display
            // "reconnecting" or "disconnected" in the toolbar state
            onConnectionChange({ state: false, permanent: isPermanent });
        });
        sframeChan.on('EV_RT_ERROR', function (err) {
            isReady = false;
            chainpad.abort();
            if (err.type === 'EUNKNOWN') {
                // Recoverable error: make a new chainpad
                chainpad = makeChainPad();
                return;
            }
            onError(err);
        });
        sframeChan.on('EV_RT_CONNECT', function (content) {
            //content.members.forEach(userList.onJoin);
            if (isReady && myID === content.myID) {
                // We are connected and we are "reconnecting" ==> we probably had to rejoin
                // the channel because of a server error (enoent), don't update the toolbar
                return;
            }
            isReady = false;
            if (myID) {
                // it's a reconnect
                myID = content.myID;
                //chainpad.start();
                onConnectionChange({ state: true, myId: myID });
                return;
            }
            myID = content.myID;
            onInit({
                myID: myID,
                realtime: chainpad,
                readOnly: readOnly
            });
            evConnected.fire();
        });
        sframeChan.on('Q_RT_MESSAGE', function (content, cb) {
            if (isReady) {
                onLocal(true); // should be onBeforeMessage
            }
            chainpad.message(content);
            if (isHistory && updateLoadingProgress) {
                updateLoadingProgress({
                    state: 2,
                    progress: isHistory
                }, false);
                isHistory++;
            }
            cb('OK');
        });
        sframeChan.on('EV_RT_READY', function () {
            if (isReady) { return; }
            isReady = true;
            isHistory = false;
            chainpad.start();
            setMyID({ myID: myID });
            onReady({ realtime: chainpad });
        });

        var whenRealtimeSyncs = function (cb) {
            evConnected.reg(function () {
                if (chainpad.getAuthDoc() === chainpad.getUserDoc()) {
                    return void cb();
                } else {
                    chainpad.onSettle(cb);
                }
            });
        };

        var cpNfInner = {
            getMyID: function () { return myID; },
            metadataMgr: metadataMgr,
            whenRealtimeSyncs: whenRealtimeSyncs,
            onInfiniteSpinner: evInfiniteSpinner.reg,
            onPatchSent: evPatchSent.reg,
            offPatchSent: evPatchSent.unreg,
        };
        cpNfInner.__defineGetter__("chainpad", function () {
            return chainpad;
        });
        return Object.freeze(cpNfInner);
    };
    return Object.freeze(module.exports);
});