"use strict";
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;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ToolRunner = void 0;
const path_1 = __importDefault(require("path"));
const chalk_1 = __importDefault(require("chalk"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const lodash_1 = __importDefault(require("lodash"));
const looks_same_1 = __importDefault(require("looks-same"));
const runner_1 = require("./runner");
const report_subscriber_1 = require("./report-subscriber");
const gui_1 = require("../../report-builder/gui");
const event_source_1 = require("../event-source");
const common_utils_1 = require("../../common-utils");
const reporterHelper = __importStar(require("../../reporter-helpers"));
const constants_1 = require("../../constants");
const utils_1 = require("./utils");
const server_1 = require("../../db-utils/server");
const server_utils_1 = require("../../server-utils");
const sqlite_client_1 = require("../../sqlite-client");
const p_queue_1 = __importDefault(require("p-queue"));
const os_1 = __importDefault(require("os"));
const cache_1 = require("../../cache");
const images_info_saver_1 = require("../../images-info-saver");
const image_store_1 = require("../../image-store");
class ToolRunner {
    static create(...args) {
        return new this(...args);
    }
    constructor(...[paths, testplane, { program: globalOpts, pluginConfig, options: guiOpts }]) {
        this._testFiles = [].concat(paths);
        this._testplane = testplane;
        this._tree = null;
        this._collection = null;
        this._globalOpts = globalOpts;
        this._guiOpts = guiOpts;
        this._reportPath = pluginConfig.path;
        this._pluginConfig = pluginConfig;
        this._eventSource = new event_source_1.EventSource();
        this._reportBuilder = null;
        this._tests = {};
        this._expectedImagesCache = new cache_1.Cache(server_utils_1.getExpectedCacheKey);
    }
    get config() {
        return this._testplane.config;
    }
    get tree() {
        return this._tree;
    }
    async initialize() {
        await (0, utils_1.mergeDatabasesForReuse)(this._reportPath);
        const dbClient = await sqlite_client_1.SqliteClient.create({ htmlReporter: this._testplane.htmlReporter, reportPath: this._reportPath, reuse: true });
        const imageStore = new image_store_1.SqliteImageStore(dbClient);
        const imagesInfoSaver = new images_info_saver_1.ImagesInfoSaver({
            imageFileSaver: this._testplane.htmlReporter.imagesSaver,
            expectedPathsCache: this._expectedImagesCache,
            imageStore,
            reportPath: this._testplane.htmlReporter.config.path
        });
        this._reportBuilder = gui_1.GuiReportBuilder.create(this._testplane.htmlReporter, this._pluginConfig, { dbClient, imagesInfoSaver });
        this._subscribeOnEvents();
        this._collection = await this._readTests();
        this._testplane.htmlReporter.emit(constants_1.PluginEvents.DATABASE_CREATED, dbClient.getRawConnection());
        await this._reportBuilder.saveStaticFiles();
        this._reportBuilder.setApiValues(this._testplane.htmlReporter.values);
        await this._handleRunnableCollection();
    }
    async _readTests() {
        const { grep, set: sets, browser: browsers } = this._globalOpts;
        const replMode = this._getReplModeOption();
        return this._testplane.readTests(this._testFiles, { grep, sets, browsers, replMode });
    }
    _getReplModeOption() {
        const { repl = false, replBeforeTest = false, replOnFail = false } = this._globalOpts;
        return {
            enabled: repl || replBeforeTest || replOnFail,
            beforeTest: replBeforeTest,
            onFail: replOnFail
        };
    }
    _ensureReportBuilder() {
        if (!this._reportBuilder) {
            throw new Error('ToolRunner has to be initialized before usage');
        }
        return this._reportBuilder;
    }
    _ensureTestCollection() {
        if (!this._collection) {
            throw new Error('ToolRunner has to be initialized before usage');
        }
        return this._collection;
    }
    async finalize() {
        return this._ensureReportBuilder().finalize();
    }
    addClient(connection) {
        this._eventSource.addConnection(connection);
    }
    sendClientEvent(event, data) {
        this._eventSource.emit(event, data);
    }
    getTestsDataToUpdateRefs(imageIds) {
        return this._ensureReportBuilder().getTestsDataToUpdateRefs(imageIds);
    }
    getImageDataToFindEqualDiffs(imageIds) {
        const [selectedImage, ...comparedImages] = this._ensureReportBuilder().getImageDataToFindEqualDiffs(imageIds);
        const imagesWithEqualBrowserName = comparedImages.filter((image) => image.browserName === selectedImage.browserName);
        const imagesWithEqualDiffSizes = (0, utils_1.filterByEqualDiffSizes)(imagesWithEqualBrowserName, selectedImage.diffClusters);
        return lodash_1.default.isEmpty(imagesWithEqualDiffSizes) ? [] : [selectedImage].concat(imagesWithEqualDiffSizes);
    }
    async updateReferenceImage(tests) {
        const reportBuilder = this._ensureReportBuilder();
        return Promise.all(tests.map(async (test) => {
            const updateResult = this._createTestplaneTestResult(test);
            const currentResult = (0, server_utils_1.formatTestResult)(updateResult, constants_1.UPDATED, test.attempt);
            const estimatedStatus = reportBuilder.getUpdatedReferenceTestStatus(currentResult);
            const formattedResultWithoutAttempt = (0, server_utils_1.formatTestResult)(updateResult, constants_1.UPDATED);
            const formattedResult = reportBuilder.provideAttempt(formattedResultWithoutAttempt);
            const formattedResultUpdated = await reporterHelper.updateReferenceImages(formattedResult, this._reportPath, this._handleReferenceUpdate.bind(this));
            await reportBuilder.addTestResult(formattedResultUpdated, { status: estimatedStatus });
            return reportBuilder.getTestBranch(formattedResultUpdated.id);
        }));
    }
    async undoAcceptImages(tests) {
        const updatedImages = [], removedResultIds = [];
        const reportBuilder = this._ensureReportBuilder();
        await Promise.all(tests.map(async (test) => {
            const updateResult = this._createTestplaneTestResult(test);
            const formattedResultWithoutAttempt = (0, server_utils_1.formatTestResult)(updateResult, constants_1.UPDATED);
            await Promise.all(formattedResultWithoutAttempt.imagesInfo.map(async (imageInfo) => {
                const { stateName } = imageInfo;
                const undoResultData = reportBuilder.undoAcceptImage(formattedResultWithoutAttempt, stateName);
                if (undoResultData === null) {
                    return;
                }
                const { updatedImage, removedResult, previousExpectedPath, shouldRemoveReference, shouldRevertReference, newResult } = undoResultData;
                updatedImage && updatedImages.push(updatedImage);
                removedResult && removedResultIds.push(removedResult.id);
                if (shouldRemoveReference) {
                    await reporterHelper.removeReferenceImage(newResult, stateName);
                }
                if (shouldRevertReference && removedResult) {
                    await reporterHelper.revertReferenceImage(removedResult, newResult, stateName);
                }
                if (previousExpectedPath && updateResult.fullTitle) {
                    this._expectedImagesCache.set([{
                            testPath: [updateResult.fullTitle()],
                            browserId: updateResult.browserId
                        }, stateName], previousExpectedPath);
                }
            }));
        }));
        return { updatedImages, removedResults: removedResultIds };
    }
    async findEqualDiffs(images) {
        const [selectedImage, ...comparedImages] = images;
        const { tolerance, antialiasingTolerance } = this.config;
        const compareOpts = { tolerance, antialiasingTolerance, stopOnFirstFail: true, shouldCluster: false };
        const comparisons = await Promise.all(comparedImages.map(async (image) => {
            for (let i = 0; i < image.diffClusters.length; i++) {
                const diffCluster = image.diffClusters[i];
                try {
                    const refComparisonRes = await (0, looks_same_1.default)({ source: this._resolveImgPath(selectedImage.expectedImg.path), boundingBox: selectedImage.diffClusters[i] }, { source: this._resolveImgPath(image.expectedImg.path), boundingBox: diffCluster }, compareOpts);
                    if (!refComparisonRes.equal) {
                        return false;
                    }
                    const actComparisonRes = await (0, looks_same_1.default)({ source: this._resolveImgPath(selectedImage.actualImg.path), boundingBox: selectedImage.diffClusters[i] }, { source: this._resolveImgPath(image.actualImg.path), boundingBox: diffCluster }, compareOpts);
                    if (!actComparisonRes.equal) {
                        return false;
                    }
                }
                catch (err) {
                    if (err !== false) {
                        throw err;
                    }
                    return false;
                }
            }
            return image;
        }));
        return comparisons.filter(Boolean).map(image => image.id);
    }
    async run(tests = []) {
        const { grep, set: sets, browser: browsers, devtools = false } = this._globalOpts;
        const replMode = this._getReplModeOption();
        return (0, runner_1.createTestRunner)(this._ensureTestCollection(), tests)
            .run((collection) => this._testplane.run(collection, { grep, sets, browsers, devtools, replMode }));
    }
    async _handleRunnableCollection() {
        const reportBuilder = this._ensureReportBuilder();
        const queue = new p_queue_1.default({ concurrency: os_1.default.cpus().length });
        this._ensureTestCollection().eachTest((test, browserId) => {
            if (test.disabled || this._isSilentlySkipped(test)) {
                return;
            }
            // TODO: remove toString after publish major version
            const testId = (0, utils_1.formatId)(test.id.toString(), browserId);
            this._tests[testId] = lodash_1.default.extend(test, { browserId });
            if (test.pending) {
                queue.add(async () => reportBuilder.addTestResult((0, server_utils_1.formatTestResult)(test, constants_1.SKIPPED)));
            }
            else {
                queue.add(async () => reportBuilder.addTestResult((0, server_utils_1.formatTestResult)(test, constants_1.IDLE)));
            }
        });
        await queue.onIdle();
        await this._fillTestsTree();
    }
    _isSilentlySkipped({ silentSkip, parent }) {
        return silentSkip || parent && this._isSilentlySkipped(parent);
    }
    _subscribeOnEvents() {
        (0, report_subscriber_1.subscribeOnToolEvents)(this._testplane, this._ensureReportBuilder(), this._eventSource);
    }
    _createTestplaneTestResult(updateData) {
        const { browserId } = updateData;
        const fullTitle = (0, utils_1.mkFullTitle)(updateData);
        const testId = (0, utils_1.formatId)((0, common_utils_1.getShortMD5)(fullTitle), browserId);
        const testplaneTest = this._tests[testId];
        const { sessionId, url } = updateData.metaInfo;
        const assertViewResults = [];
        updateData.imagesInfo
            .filter(({ stateName, actualImg }) => Boolean(stateName) && Boolean(actualImg))
            .forEach((imageInfo) => {
            const { stateName, actualImg } = imageInfo;
            const path = this._testplane.config.browsers[browserId].getScreenshotPath(testplaneTest, stateName);
            const refImg = { path, size: actualImg.size };
            assertViewResults.push({ stateName, refImg, currImg: actualImg, isUpdated: (0, common_utils_1.isUpdatedStatus)(imageInfo.status) });
        });
        const testplaneTestResult = lodash_1.default.merge({}, testplaneTest, {
            assertViewResults,
            err: updateData.error,
            sessionId,
            meta: { url }
        });
        // _.merge can't fully clone test object since hermione@7+
        // TODO: use separate object to represent test results. Do not extend test object with test results
        return testplaneTest && testplaneTest.clone
            ? Object.assign(testplaneTest.clone(), testplaneTestResult)
            : testplaneTestResult;
    }
    _handleReferenceUpdate(testResult, imageInfo, state) {
        this._expectedImagesCache.set([testResult, imageInfo.stateName], imageInfo.expectedImg.path);
        this._testplane.emit(this._testplane.events.UPDATE_REFERENCE, { refImg: imageInfo.refImg, state });
    }
    async _fillTestsTree() {
        const reportBuilder = this._ensureReportBuilder();
        const { autoRun } = this._guiOpts;
        const testsTree = await this._loadDataFromDatabase();
        if (testsTree && !lodash_1.default.isEmpty(testsTree)) {
            reportBuilder.reuseTestsTree(testsTree);
        }
        this._tree = { ...reportBuilder.getResult(), autoRun };
    }
    async _loadDataFromDatabase() {
        const dbPath = path_1.default.resolve(this._reportPath, constants_1.LOCAL_DATABASE_NAME);
        if (await fs_extra_1.default.pathExists(dbPath)) {
            return (0, server_1.getTestsTreeFromDatabase)(constants_1.ToolName.Testplane, dbPath, this._pluginConfig.baseHost);
        }
        common_utils_1.logger.warn(chalk_1.default.yellow(`Nothing to reuse in ${this._reportPath}: can not load data from ${constants_1.DATABASE_URLS_JSON_NAME}`));
        return null;
    }
    _resolveImgPath(imgPath) {
        return path_1.default.resolve(process.cwd(), this._pluginConfig.path, imgPath);
    }
}
exports.ToolRunner = ToolRunner;
//# sourceMappingURL=index.js.map