"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyMessages = exports.generateClientNames = exports.generateOperationMessagesForClients = exports.runMergeTreeOperationRunner = exports.replayResultsPath = exports.doOverRanges = exports.resolveRanges = exports.resolveRange = exports.doOverRange = exports.insert = exports.insertAtRefPos = exports.annotateRange = exports.obliterateRange = exports.removeRange = void 0;
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const assert_1 = require("assert");
const fs = __importStar(require("fs"));
const ops_1 = require("../ops.cjs");
const textSegment_1 = require("../textSegment.cjs");
const mergeTreeNodes_1 = require("../mergeTreeNodes.cjs");
const mergeTreeNodeWalk_1 = require("../mergeTreeNodeWalk.cjs");
const testClientLogger_1 = require("./testClientLogger.cjs");
const removeRange = (client, opStart, opEnd) => client.removeRangeLocal(opStart, opEnd);
exports.removeRange = removeRange;
const obliterateRange = (client, opStart, opEnd) => client.obliterateRangeLocal(opStart, opEnd);
exports.obliterateRange = obliterateRange;
const annotateRange = (client, opStart, opEnd) => client.annotateRangeLocal(opStart, opEnd, { client: client.longClientId });
exports.annotateRange = annotateRange;
const insertAtRefPos = (client, opStart, opEnd, random) => {
    const segs = [];
    // gather all the segments at the pos, including removed segments
    (0, mergeTreeNodeWalk_1.walkAllChildSegments)(client.mergeTree.root, (seg) => {
        const pos = client.getPosition(seg);
        if (pos >= opStart) {
            if (pos <= opStart) {
                segs.push(seg);
                return true;
            }
            return false;
        }
        return true;
    });
    if (segs.length > 0) {
        const text = client.longClientId.repeat(random.integer(1, 3));
        const seg = random.pick(segs);
        const lref = client.createLocalReferencePosition(seg, (0, mergeTreeNodes_1.toRemovalInfo)(seg) ? 0 : random.integer(0, seg.cachedLength - 1), (0, mergeTreeNodes_1.toRemovalInfo)(seg)
            ? ops_1.ReferenceType.SlideOnRemove
            : random.pick([
                ops_1.ReferenceType.Simple,
                ops_1.ReferenceType.SlideOnRemove,
                ops_1.ReferenceType.Transient,
            ]), undefined);
        return client.insertAtReferencePositionLocal(lref, textSegment_1.TextSegment.make(text));
    }
};
exports.insertAtRefPos = insertAtRefPos;
const insert = (client, _start, _end, random) => {
    // Note: the _start param is generated using exclusive range. This provides more coverage by allowing
    // insertion at the end.
    const start = random.integer(0, client.getLength());
    const text = client.longClientId.repeat(random.integer(1, 3));
    return client.insertTextLocal(start, text);
};
exports.insert = insert;
function doOverRange(range, defaultGrowthFunc, doAction) {
    let lastCurrent = Number.NaN;
    for (let current = range.min; current <= range.max; current = (range.growthFunc ?? defaultGrowthFunc)(current)) {
        // let growth funcs be simple
        // especially around 0 and 1
        // if the value didn't change,
        // just increment it
        if (current === lastCurrent) {
            current++;
        }
        if (current <= range.max) {
            lastCurrent = current;
            doAction(current);
        }
    }
}
exports.doOverRange = doOverRange;
function resolveRange(range, defaultGrowthFunc) {
    const results = [];
    doOverRange(range, range.growthFunc ?? defaultGrowthFunc, (num) => {
        results.push(num);
    });
    return results;
}
exports.resolveRange = resolveRange;
function resolveRanges(ranges, defaultGrowthFunc) {
    return Object.entries(ranges)
        .filter(([_, value]) => isConfigRange(value))
        .map(([key, value]) => [key, resolveRange(value, defaultGrowthFunc)])
        .reduce((prev, [key, resolvedRange]) => {
        prev[key] = resolvedRange;
        return prev;
    }, {});
}
exports.resolveRanges = resolveRanges;
function isConfigRange(t) {
    return typeof t === "object" && typeof t.min === "number" && typeof t.max === "number";
}
function doOverRanges(ranges, doAction) {
    const rangeEntries = Object.entries(ranges).filter(([_, value]) => isConfigRange(value));
    const doOverRangesHelper = (selections) => {
        if (selections.length === rangeEntries.length) {
            const selectionsObj = {};
            for (const [key, value] of selections) {
                selectionsObj[key] = value;
            }
            const description = selections.map(([key, value]) => `${key}:${value}`).join("_");
            doAction(selectionsObj, description);
        }
        else {
            const [key, value] = rangeEntries[selections.length];
            doOverRange(value, value.growthFunc ?? ranges.growthFunc, (selection) => {
                selections.push([key, selection]);
                doOverRangesHelper(selections);
                selections.pop();
            });
        }
    };
    doOverRangesHelper([]);
}
exports.doOverRanges = doOverRanges;
exports.replayResultsPath = `${__dirname}/../../src/test/results`;
function runMergeTreeOperationRunner(random, startingSeq, clients, minLength, config, apply = applyMessages) {
    let seq = startingSeq;
    const results = [];
    doOverRange(config.opsPerRoundRange, config.growthFunc, (opsPerRound) => {
        if (config.incrementalLog) {
            console.log(`MinLength: ${minLength} Clients: ${clients.length} Ops: ${opsPerRound} Seq: ${seq}`);
        }
        for (let round = 0; round < config.rounds; round++) {
            const initialText = clients[0].getText();
            const logger = new testClientLogger_1.TestClientLogger(clients, `Clients: ${clients.length} Ops: ${opsPerRound} Round: ${round}`);
            const messageData = generateOperationMessagesForClients(random, seq, clients, logger, opsPerRound, minLength, config.operations);
            const msgs = messageData.map((md) => md[0]);
            seq = apply(seq, messageData, clients, logger, random);
            const resultText = logger.validate();
            results.push({
                initialText,
                resultText,
                msgs,
                seq,
            });
            logger.dispose();
        }
    });
    if (config.resultsFilePostfix !== undefined) {
        const resultsFilePath = `${exports.replayResultsPath}/len_${minLength}-clients_${clients.length}-${config.resultsFilePostfix}`;
        fs.writeFileSync(resultsFilePath, JSON.stringify(results, undefined, 4));
    }
    return seq;
}
exports.runMergeTreeOperationRunner = runMergeTreeOperationRunner;
function generateOperationMessagesForClients(random, startingSeq, clients, logger, opsPerRound, minLength, operations) {
    const minimumSequenceNumber = startingSeq;
    let tempSeq = startingSeq * -1;
    const messages = [];
    for (let i = 0; i < opsPerRound; i++) {
        // pick a client greater than 0, client 0 only applies remote ops
        // and is our baseline
        const client = clients[random.integer(1, clients.length - 1)];
        const len = client.getLength();
        const sg = client.peekPendingSegmentGroups();
        let op;
        if (len === 0 || len < minLength) {
            const text = client.longClientId.repeat(random.integer(1, 3));
            op = client.insertTextLocal(random.integer(0, len), text);
        }
        else {
            let opIndex = random.integer(0, operations.length - 1);
            const start = random.integer(0, len - 1);
            const end = random.integer(start + 1, len);
            for (let y = 0; y < operations.length && op === undefined; y++) {
                op = operations[opIndex](client, start, end, random);
                opIndex++;
                opIndex %= operations.length;
            }
        }
        if (op !== undefined) {
            // Pre-check to avoid logger.toString() in the string template
            if (sg === client.peekPendingSegmentGroups()) {
                assert_1.strict.notEqual(sg, client.peekPendingSegmentGroups(), `op created but segment group not enqueued.${logger}`);
            }
            const message = client.makeOpMessage(op, --tempSeq);
            message.minimumSequenceNumber = minimumSequenceNumber;
            messages.push([
                message,
                client.peekPendingSegmentGroups(op.type === ops_1.MergeTreeDeltaType.GROUP ? op.ops.length : 1),
            ]);
        }
    }
    return messages;
}
exports.generateOperationMessagesForClients = generateOperationMessagesForClients;
function generateClientNames() {
    const clientNames = [];
    function addClientNames(startChar, count) {
        const startCode = startChar.charCodeAt(0);
        for (let i = 0; i < count; i++) {
            clientNames.push(String.fromCharCode(startCode + i));
        }
    }
    addClientNames("A", 26);
    addClientNames("a", 26);
    addClientNames("0", 17);
    return clientNames;
}
exports.generateClientNames = generateClientNames;
function applyMessages(startingSeq, messageData, clients, logger) {
    let seq = startingSeq;
    try {
        // log and apply all the ops created in the round
        // eslint-disable-next-line @typescript-eslint/prefer-for-of
        for (let i = 0; i < messageData.length; i++) {
            const [message] = messageData[i];
            message.sequenceNumber = ++seq;
            clients.forEach((c) => c.applyMsg(message));
        }
    }
    catch (e) {
        throw logger.addLogsToError(e);
    }
    return seq;
}
exports.applyMessages = applyMessages;
//# sourceMappingURL=mergeTreeOperationRunner.cjs.map