// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later

/*  three types of actions:
    * read
    * write
    * append
    each of which take a random amount of time

*/
var Util = require("../../lib/common-util");
var schedule = require("../../lib/schedule")();
var nThen = require("nthen");

var rand = function (n) {
    return Math.floor(Math.random() * n);
};

var rand_time = function () {
    // between 51 and 151
    return rand(300) + 25;
};

var makeAction = function (type) {
    var i = 0;
    return function (time) {
        var j = i++;
        return function (next) {
            console.log("  Beginning action: %s#%s", type, j);
            setTimeout(function () {
                console.log("    Completed action: %s#%s", type, j);
                next();
            }, time);
            return j;
        };
    };
};

var TYPES = ['WRITE', 'READ', 'APPEND'];
var chooseAction = function () {
    var n = rand(100);

    if (n < 50) { return 'APPEND'; }
    if (n < 90) { return 'READ'; }
    return 'WRITE';

    //return TYPES[rand(3)];
};

var test = function (script, cb) {
    var uid = Util.uid();

    var TO_RUN = script.length;
    var total_run = 0;

    var parallel = 0;
    var last_run_ordered = -1;
    //var i = 0;

    var ACTIONS = {};
    TYPES.forEach(function (type) {
        ACTIONS[type] = makeAction(type);
    });

    nThen(function (w) {
        setTimeout(w(), 3000);
        // run scripted actions with assertions
        script.forEach(function (scene) {
            var type = scene[0];
            var time = typeof(scene[1]) === 'number'? scene[1]: rand_time();

            var action = ACTIONS[type](time);
            console.log("Queuing action of type: %s(%s)", type, time);

            var proceed = w();

            switch (type) {
                case 'APPEND':
                    return schedule.ordered(uid, w(function (next) {
                        parallel++;
                        var temp = action(function () {
                            parallel--;
                            total_run++;
                            proceed();
                            next();
                        });
                        if (temp !== (last_run_ordered + 1)) {
                            throw new Error("out of order");
                        }
                        last_run_ordered = temp;
                    }));
                case 'WRITE':
                    return schedule.blocking(uid, w(function (next) {
                        parallel++;
                        action(function () {
                            parallel--;
                            total_run++;
                            proceed();
                            next();
                        });
                        if (parallel > 1) {
                            console.log("parallelism === %s", parallel);
                            throw new Error("too much parallel");
                        }
                    }));
                case 'READ':
                    return schedule.unordered(uid, w(function (next) {
                        parallel++;
                        action(function () {
                            parallel--;
                            total_run++;
                            proceed();
                            next();
                        });
                    }));
                default:
                    throw new Error("wut");
            }
        });
    }).nThen(function () {
        // make assertions about the whole script
        if (total_run !== TO_RUN) {
            console.log("Ran %s / %s", total_run, TO_RUN);
            throw new Error("skipped tasks");
        }
        console.log("total_run === %s", total_run);

        cb();
    });
};


var randomScript = function () {
    var len = rand(15) + 10;
    var script = [];
    while (len--) {
        script.push([
            chooseAction(),
            rand_time(),
        ]);
    }
    return script;
};

var WRITE = function (t) {
    return ['WRITE', t];
};
var READ = function (t) {
    return ['READ', t];
};

var APPEND = function (t) {
    return ['APPEND', t];
};

nThen(function (w) {
    test([
        ['READ', 150],
        ['APPEND', 200],
        ['APPEND', 100],
        ['READ', 350],
        ['WRITE', 400],
        ['APPEND', 275],
        ['APPEND', 187],
        ['WRITE', 330],
        ['WRITE', 264],
        ['WRITE', 256],
    ], w(function () {
        console.log("finished pre-scripted test\n");
    }));
}).nThen(function (w) {
    test([
        WRITE(289),
        APPEND(281),
        READ(207),
        WRITE(225),
        READ(279),
        WRITE(300),
        READ(331),
        APPEND(341),
        APPEND(385),
        READ(313),
        WRITE(285),
        READ(304),
        APPEND(273),
        APPEND(150),
        WRITE(246),
        READ(244),
        WRITE(172),
        APPEND(253),
        READ(215),
        READ(296),
        APPEND(281),
        APPEND(296),
        WRITE(168),
    ], w(function () {
        console.log("finished 2nd pre-scripted test\n");
    }));
}).nThen(function () {
    var totalTests = 50;
    var randomTests = 1;

    var last = nThen(function () {
        console.log("beginning randomized tests");
    });

    var queueRandomTest = function (i) {
        last = last.nThen(function (w) {
            console.log("running random test script #%s\n", i);
            test(randomScript(), w(function () {
                console.log("finished random test #%s\n", i);
            }));
        });
    };

    while (randomTests <=totalTests) { queueRandomTest(randomTests++);  }

    last.nThen(function () {
        console.log("finished %s random tests", totalTests);
    });
});


