define([
    'jquery',
    '/bower_components/chainpad-json-validator/json-ot.js',
    '/bower_components/chainpad-crypto/crypto.js',
    '/bower_components/chainpad/chainpad.dist.js',
], function ($, JsonOT, Crypto) {
    var ChainPad = window.ChainPad;
    var History = {};

    var getStates = function (rt) {
        var states = [];
        var b = rt.getAuthBlock();
        if (b) { states.unshift(b); }
        while (b.getParent()) {
            b = b.getParent();
            states.unshift(b);
        }
        return states;
    };

    var loadHistory = function (config, common, cb) {
        var network = common.getNetwork();
        var hkn = network.historyKeeper;

        var wcId = common.hrefToHexChannelId(config.href || window.location.href);

        var createRealtime = function () {
            return ChainPad.create({
                userName: 'history',
                initialState: '',
                transformFunction: JsonOT.validate,
                logLevel: 0,
                noPrune: true
            });
        };
        var realtime = createRealtime();

        var parsed = config.href ? common.parsePadUrl(config.href) : {};
        var secret = common.getSecrets(parsed.type, parsed.hash);

        History.readOnly = 1;
        if (!secret.keys) {
            secret.keys = secret.key;
            History.readOnly = 2;
        }
        else if (!secret.keys.validateKey) {
            History.readOnly = 0;
        }

        var crypto = Crypto.createEncryptor(secret.keys);

        var to = window.setTimeout(function () {
            cb('[GET_FULL_HISTORY_TIMEOUT]');
        }, 30000);

        var parse = function (msg) {
            try {
                return JSON.parse(msg);
            } catch (e) {
                return null;
            }
        };
        var onMsg = function (msg) {
            var parsed = parse(msg);
            if (parsed[0] === 'FULL_HISTORY_END') {
                console.log('END');
                window.clearTimeout(to);
                cb(null, realtime);
                return;
            }
            if (parsed[0] !== 'FULL_HISTORY') { return; }
            if (parsed[1] && parsed[1].validateKey) { // First message
                secret.keys.validateKey = parsed[1].validateKey;
                return;
            }
            msg = parsed[1][4];
            if (msg) {
                msg = msg.replace(/^cp\|/, '');
                var decryptedMsg = crypto.decrypt(msg, secret.keys.validateKey);
                realtime.message(decryptedMsg);
            }
        };

        network.on('message', function (msg) {
            onMsg(msg);
        });

        network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', wcId, secret.keys.validateKey]));
    };

    History.create = function (common, config) {
        if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
        if (History.loading) { return void console.error("History is already being loaded..."); }
        History.loading = true;
        var $toolbar = config.$toolbar;

        if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
            throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
        }

        // config.setHistory(bool, bool)
        // - bool1: history value
        // - bool2: reset old content?
        var render = function (val) {
            if (typeof val === "undefined") { return; }
            try {
                config.applyVal(val);
            } catch (e) {
                // Probably a parse error
                console.error(e);
            }
        };
        var onClose = function () { config.setHistory(false, true); };
        var onRevert = function () {
            config.setHistory(false, false);
            config.onLocal();
            config.onRemote();
        };
        var onReady = function () {
            config.setHistory(true);
        };

        var Messages = common.Messages;

        var realtime;

        var states = [];
        var c = states.length - 1;

        var $hist = $toolbar.find('.cryptpad-toolbar-history');
        var $left = $toolbar.find('.cryptpad-toolbar-leftside');
        var $right = $toolbar.find('.cryptpad-toolbar-rightside');
        var $cke = $toolbar.find('.cke_toolbox_main');

        $hist.html('').show();
        $left.hide();
        $right.hide();
        $cke.hide();

        common.spinner($hist).get().show();

        var onUpdate;

        var update = function () {
            if (!realtime) { return []; }
            states = getStates(realtime);
            if (typeof onUpdate === "function") { onUpdate(); }
            return states;
        };

        // Get the content of the selected version, and change the version number
        var get = function (i) {
            i = parseInt(i);
            if (isNaN(i)) { return; }
            if (i < 0) { i = 0; }
            if (i > states.length - 1) { i = states.length - 1; }
            var val = states[i].getContent().doc;
            c = i;
            if (typeof onUpdate === "function") { onUpdate(); }
            $hist.find('.next, .previous').css('visibility', '');
            if (c === states.length - 1) { $hist.find('.next').css('visibility', 'hidden'); }
            if (c === 0) { $hist.find('.previous').css('visibility', 'hidden'); }
            return val || '';
        };

        var getNext = function (step) {
            return typeof step === "number" ? get(c + step) : get(c + 1);
        };
        var getPrevious = function (step) {
            return typeof step === "number" ? get(c - step) : get(c - 1);
        };

        // Create the history toolbar
        var display = function () {
            $hist.html('');
            var $prev =$('<button>', {
                'class': 'previous fa fa-step-backward buttonPrimary',
                title: Messages.history_prev
            }).appendTo($hist);
            var $nav = $('<div>', {'class': 'goto'}).appendTo($hist);
            var $next = $('<button>', {
                'class': 'next fa fa-step-forward buttonPrimary',
                title: Messages.history_next
            }).appendTo($hist);

            $('<label>').text(Messages.history_version).appendTo($nav);
            var $cur = $('<input>', {
                'class' : 'gotoInput',
                'type' : 'number',
                'min' : '1',
                'max' : states.length
            }).val(c + 1).appendTo($nav).mousedown(function (e) {
                // stopPropagation because the event would be cancelled by the dropdown menus
                e.stopPropagation();
            });
            var $label2 = $('<label>').text(' / '+ states.length).appendTo($nav);
            $('<br>').appendTo($nav);
            var $close = $('<button>', {
                'class':'closeHistory',
                title: Messages.history_closeTitle
            }).text(Messages.history_closeTitle).appendTo($nav);
            var $rev = $('<button>', {
                'class':'revertHistory buttonSuccess',
                title: Messages.history_restoreTitle
            }).text(Messages.history_restore).appendTo($nav);
            if (!History.readOnly) { $rev.hide(); }

            onUpdate = function () {
                $cur.attr('max', states.length);
                $cur.val(c+1);
                $label2.text(' / ' + states.length);
            };

            var close = function () {
                $hist.hide();
                $left.show();
                $right.show();
                $cke.show();
            };

            // Buttons actions
            $prev.click(function () { render(getPrevious()); });
            $next.click(function () { render(getNext()); });
            $cur.keydown(function (e) {
                var p = function () { e.preventDefault(); };
                if (e.which === 13) { p(); return render( get($cur.val() - 1) ); } // Enter
                if ([37, 40].indexOf(e.which) >= 0) { p(); return render(getPrevious()); } // Left
                if ([38, 39].indexOf(e.which) >= 0) { p(); return render(getNext()); } // Right
                if (e.which === 33) { p(); return render(getNext(10)); } // PageUp
                if (e.which === 34) { p(); return render(getPrevious(10)); } // PageUp
                if (e.which === 27) { p(); $close.click(); }
            }).keyup(function (e) { e.stopPropagation(); }).focus();
            $cur.on('change', function () {
                render( get($cur.val() - 1) );
            });
            $close.click(function () {
                states = [];
                close();
                onClose();
            });
            $rev.click(function () {
                common.confirm(Messages.history_restorePrompt, function (yes) {
                    if (!yes) { return; }
                    close();
                    onRevert();
                    common.log(Messages.history_restoreDone);
                });
            });

            // Display the latest content
            render(get(c));
        };

        // Load all the history messages into a new chainpad object
        loadHistory(config, common, function (err, newRt) {
            History.loading = false;
            if (err) { throw new Error(err); }
            realtime = newRt;
            update();
            c = states.length - 1;
            display();
            onReady();
        });
    };

    return History;
});