"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NedbAdapter = void 0;
const dirs = require("./dirs");
const configLoader = require("../lib/config-loader");
const constants = require("./constants");
const helpers = require("./helpers");
const mkdirp = require("mkdirp");
const NeDBDatastore = require("nedb");
const path = require("path");
const util = require("util");
class NedbAdapter {
    constructor(options) {
        this.options = options || {};
        this.options.nedbPath = this.options.nedbPath || path.join(dirs.workingDir(), 'db');
    }
    closeDb() {
        this.sessionsCol.persistence.stopAutocompaction();
    }
    deleteActor(actorId) {
        return __awaiter(this, void 0, void 0, function* () {
            let self = this;
            return new Promise(function (resolve, reject) {
                self.actorsCol.remove({ id: actorId }, {}, function (err, numRemoved) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(numRemoved);
                    }
                });
            });
        });
    }
    deleteSession(sessionId) {
        return __awaiter(this, void 0, void 0, function* () {
            let self = this;
            return new Promise(function (resolve, reject) {
                self.sessionsCol.remove({ id: sessionId }, {}, function (err, numRemoved) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(numRemoved);
                    }
                });
            });
        });
    }
    getActor(actorId) {
        return __awaiter(this, void 0, void 0, function* () {
            let self = this;
            return new Promise(function (resolve, reject) {
                self.actorsCol.findOne({ id: actorId }, function (err, actor) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(actor);
                    }
                });
            });
        });
    }
    getActorsBySession(sessionId) {
        return __awaiter(this, void 0, void 0, function* () {
            let self = this;
            return new Promise(function (resolve, reject) {
                let query = sessionId ? { testSessionId: sessionId } : {};
                self.actorsCol.find(query, function (err, actors) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(actors);
                    }
                });
            });
        });
    }
    getActorsByTypeAndTags(actorType, actorTags) {
        return __awaiter(this, void 0, void 0, function* () {
            let self = this;
            return new Promise(function (resolve, reject) {
                let query = {};
                if (actorType) {
                    query.type = actorType;
                }
                let requestedTags = actorTags || [];
                // Trim tags and remove empty ones
                requestedTags = requestedTags
                    .map(tag => tag.trim())
                    .filter(tag => tag.length > 0);
                self.actorsCol.find(query, function (err, actors) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        const actorsWithTags = actors.filter(function (actor) {
                            const actorTagRegex = /^((.+?)\s*:\s*)?([\w\-+\[\].\/]+)$/;
                            let hasAllTags = true;
                            for (let requestedTag of requestedTags) {
                                const tagMatch = actorTagRegex.exec(requestedTag);
                                if (tagMatch) {
                                    const actorType = tagMatch[2];
                                    const tag = tagMatch[3];
                                    const actorTypeMatches = !actorType || (actor.type.toLowerCase() === actorType.toLowerCase());
                                    const tagExists = actor.tags.indexOf(tag) >= 0;
                                    if (actorTypeMatches && !tagExists) {
                                        hasAllTags = false;
                                        break;
                                    }
                                }
                            }
                            return hasAllTags;
                        });
                        resolve(actorsWithTags);
                    }
                });
            });
        });
    }
    getArchivedSession(sessionId) {
        return __awaiter(this, void 0, void 0, function* () {
            let self = this;
            return new Promise(function (resolve, reject) {
                self.sessionsCol.findOne({ id: sessionId }, function (err, actor) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(actor);
                    }
                });
            });
        });
    }
    getSession(sessionId) {
        return __awaiter(this, void 0, void 0, function* () {
            let self = this;
            return new Promise(function (resolve, reject) {
                self.sessionsCol.findOne({ id: sessionId }, function (err, session) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(session);
                    }
                });
            });
        });
    }
    getArchivedSessions() {
        return __awaiter(this, void 0, void 0, function* () { return null; });
    }
    getSessions(options = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            var self = this;
            return new Promise(function (resolve, reject) {
                var cursor = self.sessionsCol
                    .find({})
                    .sort({ timeCreated: -1 });
                if (options.limit) {
                    cursor = cursor.limit(options.limit);
                }
                if (options.skip) {
                    cursor = cursor.skip(options.skip);
                }
                cursor.exec(function (err, docs) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(docs);
                    }
                });
            });
        });
    }
    initDb() {
        const config = configLoader.getConfig();
        const dbCompactionIntervalMs = Math.min(config.dbCompactionIntervalSec * 1000, 10 * 60 * 1000);
        mkdirp.sync(this.options.nedbPath);
        this.actorsCol = new NeDBDatastore({ filename: path.join(this.options.nedbPath, 'actors'), autoload: true });
        this.sessionsCol = new NeDBDatastore({ filename: path.join(this.options.nedbPath, 'sessions'), autoload: true });
        this.sessionsCol.persistence.setAutocompactionInterval(dbCompactionIntervalMs);
        this.usersCol = new NeDBDatastore({ filename: path.join(this.options.nedbPath, 'users'), autoload: true });
    }
    insertActor(actorId, actorType) {
        return __awaiter(this, void 0, void 0, function* () {
            var self = this;
            var countPromise = new Promise((resolve, reject) => {
                self.actorsCol.count({ id: actorId }, function (err, recordCount) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(recordCount);
                    }
                });
            });
            return countPromise.then(function (recordCount) {
                if (recordCount > 0) {
                    return Promise.reject(new Error(util.format('Actor ID %s already exists', actorId)));
                }
                else {
                    return new Promise(function (resolve, reject) {
                        let newActor = {
                            id: actorId,
                            lastSeenTime: Date.now(),
                            testSessionId: null,
                            type: actorType,
                            tags: []
                        };
                        self.actorsCol.insert(newActor, function (err, actor) {
                            if (err) {
                                reject(err);
                            }
                            else {
                                resolve(actor);
                            }
                        });
                    });
                }
            });
        });
    }
    insertArchivedSession(sessionId, label) {
        return __awaiter(this, void 0, void 0, function* () {
            var self = this;
            let now = Date.now();
            var countPromise = new Promise((resolve, reject) => {
                self.sessionsCol.count({ id: sessionId }, function (err, recordCount) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(recordCount);
                    }
                });
            });
            return countPromise.then(function (recordCount) {
                if (recordCount > 0) {
                    return Promise.reject(new Error(util.format('Session ID %s already exists', sessionId)));
                }
                else {
                    return new Promise(function (resolve, reject) {
                        let newSession = {
                            id: sessionId,
                            actors: {},
                            maxIterations: 1,
                            currentIteration: 1,
                            currentTestIndex: 0,
                            currentSegmentIndex: 0,
                            label: label,
                            result: constants.testSessionResult.PENDING,
                            status: constants.testSessionStatus.ACQUIRING_ACTORS,
                            tests: [],
                            timeCreated: now,
                            timeStarted: null,
                            timeCompleted: null,
                            lastActivity: now
                        };
                        self.sessionsCol.insert(newSession, function (err, actor) {
                            if (err) {
                                reject(err);
                            }
                            else {
                                resolve(actor);
                            }
                        });
                    });
                }
            });
        });
    }
    insertSession(sessionId, label) {
        return __awaiter(this, void 0, void 0, function* () {
            var self = this;
            let now = Date.now();
            var countPromise = new Promise((resolve, reject) => {
                self.sessionsCol.count({ id: sessionId }, function (err, recordCount) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(recordCount);
                    }
                });
            });
            return countPromise.then(function (recordCount) {
                if (recordCount > 0) {
                    return Promise.reject(new Error(util.format('Session ID %s already exists', sessionId)));
                }
                else {
                    return new Promise(function (resolve, reject) {
                        let newSession = {
                            id: sessionId,
                            actors: {},
                            maxIterations: 1,
                            currentIteration: 1,
                            currentTestIndex: 0,
                            currentSegmentIndex: 0,
                            label: label,
                            result: constants.testSessionResult.PENDING,
                            status: constants.testSessionStatus.ACQUIRING_ACTORS,
                            tests: [],
                            timeCreated: Date.now(),
                            timeStarted: null,
                            timeCompleted: null,
                            lastActivity: now
                        };
                        self.sessionsCol.insert(newSession, function (err, actor) {
                            if (err) {
                                reject(err);
                            }
                            else {
                                resolve(actor);
                            }
                        });
                    });
                }
            });
        });
    }
    resetDb() {
        return __awaiter(this, void 0, void 0, function* () {
            let self = this;
            yield self.actorsCol.remove({}, { multi: true });
            yield self.actorsCol.persistence.compactDatafile();
            yield self.sessionsCol.remove({}, { multi: true });
            yield self.sessionsCol.persistence.compactDatafile();
            yield self.usersCol.remove({}, { multi: true });
            yield self.usersCol.persistence.compactDatafile();
        });
    }
    updateActorSegment(sessionId, actorId, testIndex, segmentIndex, newData) {
        return __awaiter(this, void 0, void 0, function* () {
            const self = this;
            const now = Date.now();
            const sessionHelper = require('../lib/session-helper');
            const testSession = yield this.getSession(sessionId);
            if (!testSession) {
                throw new Error(util.format('Session ID %s was not found', sessionId));
            }
            testSession.lastActivity = now;
            const sessionActor = testSession.actors[actorId];
            // Print update requests at the console for troubleshooting purposes
            // console.log(`${sessionActor.type}, TS${testIndex}, SEG${segmentIndex}, ${JSON.stringify(newData)}`);
            if (!sessionActor) {
                throw new Error(util.format('Session actor with ID %s was not found for session %s', actorId, sessionId));
            }
            // Make sure testIndex is integer
            let testIndexInt = parseInt(testIndex);
            if (!Number.isInteger(testIndexInt)) {
                throw new Error(helpers.format("Method updateActorSegment was called with an invalid testIndex parameter. The value received was {0}", testIndex));
            }
            testIndex = testIndexInt;
            if (newData.actions) {
                if (Array.isArray(newData.actions)) {
                    for (let action of newData.actions) {
                        if (action.duration && !action.durationMs) {
                            action.durationMs = action.duration * 1000;
                        }
                        // Remove this block once support for the "step"
                        // terminology is completely abandoned
                        if (action.step) {
                            if (!action.segment) {
                                action.segment = action.step;
                            }
                            delete action.step;
                        }
                        action.args = helpers.escape$Properties(action.args);
                    }
                }
                testSession.tests[testIndex].actions = testSession.tests[testIndex].actions || [];
                testSession.tests[testIndex].actions = testSession.tests[testIndex].actions.concat(newData.actions);
            }
            // In segment 0 of data-driven tests, actors report the total number of
            // data records available. We store that number in the test context so
            // we know how many iterations to process.
            if (newData.dataRecordCount) {
                if (!Number.isInteger(newData.dataRecordCount)) {
                    throw new Error(helpers.format("The dataRecordCount field must be an integer. The value received was {0}.", newData.dataRecordCount));
                }
                // The dataRecordCount field will only be sent by actors in segment 0 of data-driven tests
                if (testSession.tests[testIndex].currentSegmentIndex == 0) {
                    testSession.tests[testIndex].dataRecordCount = newData.dataRecordCount;
                    testSession.tests[testIndex].currentSegmentIndex = 1;
                }
            }
            if (newData.stackTrace) {
                testSession.actors[actorId].tests[testIndex].segments[segmentIndex].stackTrace = newData.stackTrace;
            }
            if (newData.status) {
                sessionHelper.updateActorSegmentStatus(testSession, actorId, testIndex, segmentIndex, newData.status);
            }
            if (newData.result) {
                testSession.actors[actorId].tests[testIndex].segments[segmentIndex].result = newData.result;
            }
            sessionHelper.updateSessionState(testSession);
            yield self.updateSession(sessionId, testSession);
        });
    }
    updateActor(actorId, newData) {
        return __awaiter(this, void 0, void 0, function* () {
            let self = this;
            actorId = parseInt(actorId);
            return new Promise(function (resolve, reject) {
                self.actorsCol.update({ id: actorId }, { $set: newData }, {}, function (err, numUpdated) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(numUpdated);
                    }
                });
            });
        });
    }
    updateArchivedSession(sessionId, newData) {
        return __awaiter(this, void 0, void 0, function* () {
            let self = this;
            return new Promise(function (resolve, reject) {
                self.sessionsCol.update({ id: sessionId }, { $set: newData }, {}, function (err, numUpdated) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(numUpdated);
                    }
                });
            });
        });
    }
    updateSession(sessionId, newData) {
        return __awaiter(this, void 0, void 0, function* () {
            let self = this;
            sessionId = parseInt(sessionId);
            return new Promise(function (resolve, reject) {
                self.sessionsCol.update({ id: sessionId }, { $set: newData }, {}, function (err, numUpdated) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(numUpdated);
                    }
                });
            });
        });
    }
}
exports.NedbAdapter = NedbAdapter;
//# sourceMappingURL=nedb-adapter.js.map