"use strict";
/*
 * Copyright 2018, collaboration Factory AG. All rights reserved.
 */
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.AssetsCompiler = void 0;
const fs = require("fs");
const path = require("path");
const CplacePlugin_1 = require("./CplacePlugin");
const executor_1 = require("../executor");
const utils_1 = require("../utils");
const NPMResolver_1 = require("./NPMResolver");
const ImlParser_1 = require("./ImlParser");
const CplaceVersion_1 = require("./CplaceVersion");
/**
 * This represents the main execution logic for the whole compilation process
 */
class AssetsCompiler {
    constructor(runConfig, repositoryDir) {
        this.runConfig = runConfig;
        this.repositoryDir = repositoryDir;
        /**
         * Map of known plugin names to plugin instance
         */
        this.projects = new Map();
        /**
         * Executor used to run all compilation steps
         */
        this.executor = null;
        /**
         * Schedule managing order and necessity of execution
         */
        this.scheduler = null;
        /**
         * NPMResolver to manage node_modules
         */
        this.npmResolver = null;
        console.log(`⟲ Starting the main process with pid ${process.pid}`);
        if (AssetsCompiler.shouldUseAscLocal()) {
            console.error(utils_1.cred `✗`, `Starting from the cplace release 23.2 you should be using @cplace/asc-local instead of @cplace/asc!`);
            throw Error(`cplace-asc won't start! Check the error above!`);
        }
        this.repositoryName = path.basename(repositoryDir);
        this.projects = this.setupProjects();
    }
    static shouldUseAscLocal() {
        const version = CplaceVersion_1.CplaceVersion.get();
        return ((version.major === 23 && version.minor >= 2) || version.major > 23);
    }
    start(updateDetails) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.projects.size) {
                console.log(utils_1.cgreen `->`, 'Nothing to do, no plugins detected...');
                return new Promise((resolve) => resolve());
            }
            const mainRepoPath = AssetsCompiler.getMainRepoPath(this.repositoryDir, this.runConfig.localOnly);
            if (mainRepoPath === null) {
                utils_1.debug(`(AssetsCompiler) Main repo cannot be found...`);
                return new Promise((resolve, reject) => reject('Main repo cannot be found...'));
            }
            const start = new Date().getTime();
            if (this.runConfig.clean) {
                utils_1.debug(`(AssetsCompiler) running clean for all plugins...`);
                for (const plugin of this.projects.values()) {
                    if (this.isInCompilationScope(plugin)) {
                        yield plugin.cleanGeneratedOutput();
                    }
                }
            }
            if (this.runConfig.packagejson) {
                utils_1.debug(`(AssetsCompiler) generating package.json for all plugins...`);
                for (const plugin of this.projects.values()) {
                    if (this.isInCompilationScope(plugin)) {
                        plugin.generatePackageJson(this.repositoryDir);
                    }
                }
            }
            this.npmResolver = new NPMResolver_1.NPMResolver(mainRepoPath, this.runConfig.watchFiles);
            yield this.npmResolver.resolve();
            if (this.runConfig.onlyPreprocessing) {
                console.log();
                console.log(utils_1.csucc `Preprocessing completed successfully`);
                console.log();
                return new Promise((resolve) => resolve());
            }
            this.executor = new executor_1.ExecutorService(this.runConfig.maxParallelism);
            this.scheduler = new executor_1.Scheduler(this.executor, this.projects, this.repositoryName, mainRepoPath, this.runConfig.production, this.runConfig.noParents, this.runConfig.watchFiles, this.runConfig.withYaml, updateDetails);
            utils_1.debug(`(AssetsCompiler) starting scheduler for compilation tasks...`);
            return this.scheduler.start().then(() => {
                const end = new Date().getTime();
                const successLog = () => {
                    console.log();
                    console.log(utils_1.csucc `Assets compiled successfully (${utils_1.formatDuration(end - start)})`);
                    console.log();
                };
                if (!!this.executor) {
                    this.executor.destroy().then(successLog, successLog);
                }
            }, (e) => {
                utils_1.debug(`(AssetsCompiler) Error while running assets compiler: ${e}`);
                const errorLog = () => {
                    console.log();
                    console.error(utils_1.cerr `COMPILATION FAILED - please check errors in output above`);
                    console.log();
                };
                if (!!this.executor) {
                    this.executor.destroy().then(errorLog, errorLog);
                }
                throw e;
            });
        });
    }
    shutdown() {
        return __awaiter(this, void 0, void 0, function* () {
            if (!!this.scheduler) {
                this.scheduler.stop();
            }
            if (!!this.npmResolver) {
                this.npmResolver.stop();
            }
            if (!!this.executor) {
                try {
                    yield this.executor.destroy();
                }
                catch (e) {
                    utils_1.debug(e);
                }
            }
        });
    }
    setupProjects() {
        let knownRepoDependencies;
        if (this.runConfig.localOnly) {
            knownRepoDependencies = [];
            utils_1.debug(`(AssetsCompiler) Ignoring repo dependencies since localOnly execution...`);
        }
        else {
            knownRepoDependencies = AssetsCompiler.getRepoDependencies(this.repositoryDir);
            utils_1.debug(`(AssetsCompiler) Detected repo dependencies: ${knownRepoDependencies.join(', ')}`);
        }
        const projects = new Map();
        const files = fs.readdirSync(this.repositoryDir);
        files.forEach((file) => {
            const filePath = path.join(this.repositoryDir, file);
            if (fs.lstatSync(filePath).isDirectory()) {
                const potentialPluginName = path.basename(file);
                if ((this.runConfig.rootPlugins.length === 0 ||
                    this.runConfig.rootPlugins.indexOf(potentialPluginName) !== -1) &&
                    AssetsCompiler.directoryLooksLikePlugin(filePath, potentialPluginName)) {
                    AssetsCompiler.addProjectDependenciesRecursively(this.repositoryDir, projects, knownRepoDependencies, potentialPluginName, filePath, this.runConfig);
                }
            }
        });
        projects.forEach((project) => {
            if (!this.isInCompilationScope(project)) {
                return;
            }
            if (project.hasTypeScriptAssets) {
                project.generateTsConfig((p) => projects.get(p), this.runConfig.production, this.runConfig.localOnly);
            }
            if (project.hasTypeScriptE2EAssets &&
                CplaceVersion_1.CplaceVersion.isVersionLowerThen23()) {
                if (!this.runConfig.production) {
                    project.generateTsE2EConfig((p) => projects.get(p), false, this.runConfig.localOnly);
                }
            }
        });
        AssetsCompiler.setDependents(projects);
        return projects;
    }
    static getMainRepoPath(repositoryDir, localonly) {
        let mainRepoPath = '';
        if (localonly) {
            utils_1.debug(`(AssetsCompiler) Resolving main repo path [localonly] as: "${repositoryDir}"`);
            mainRepoPath = path.resolve(repositoryDir);
        }
        else {
            utils_1.debug(`(AssetsCompiler) Resolving main repo path from root: "${repositoryDir}"`);
            if (fs.existsSync(path.resolve(path.join(repositoryDir, '..', 'main')))) {
                utils_1.debug(`(AssetsCompiler) Found as "main"`);
                mainRepoPath = path.resolve(path.join(repositoryDir, '..', 'main'));
            }
            else if (fs.existsSync(path.resolve(path.join(repositoryDir, '..', 'cplace')))) {
                utils_1.debug(`(AssetsCompiler) Found as "cplace"`);
                mainRepoPath = path.resolve(path.join(repositoryDir, '..', 'cplace'));
            }
            else {
                utils_1.debug(`(AssetsCompiler) Failed to find main/cplace repository...`);
                return null;
            }
        }
        utils_1.debug(`(AssetsCompiler) main repo resolved to: "${mainRepoPath}"`);
        if (!fs.existsSync(path.join(mainRepoPath, AssetsCompiler.PLATFORM_PLUGIN_NAME))) {
            utils_1.debug(`(AssetsCompiler) Failed to find cf.cplace.platform inside: "${mainRepoPath}"`);
            return null;
        }
        return mainRepoPath;
    }
    static directoryLooksLikePlugin(pluginPath, potentialPluginName) {
        return ((ImlParser_1.ImlParser.doesImlExist(pluginPath, potentialPluginName) ||
            CplacePlugin_1.default.isCplacePluginWithGradleAndContainsPluginDescriptor(pluginPath)) &&
            fs.existsSync(path.join(pluginPath, 'src'))); // path to src directory - release-notes will be excluded
    }
    static addProjectDependenciesRecursively(repositoryDir, projects, repoDependencies, pluginName, pluginPath, runConfig) {
        if (projects.has(pluginName)) {
            return;
        }
        const project = new CplacePlugin_1.default(pluginName, pluginPath, runConfig.production);
        projects.set(pluginName, project);
        project.pluginDescriptor.dependencies.forEach((pluginDescriptor) => {
            if (projects.has(pluginDescriptor.name)) {
                return;
            }
            const pluginPath = this.findPluginPath(repositoryDir, pluginDescriptor.name, repoDependencies);
            this.addProjectDependenciesRecursively(repositoryDir, projects, repoDependencies, pluginDescriptor.name, pluginPath, runConfig);
        });
    }
    static setDependents(projects) {
        for (const plugin of projects.values()) {
            plugin.pluginDescriptor.dependencies
                .map((pluginDescriptor) => projects.get(pluginDescriptor.name))
                .forEach((p) => {
                if (!!p) {
                    p.dependents.push(plugin.pluginDescriptor);
                }
            });
        }
    }
    static getRepoDependencies(repositoryDir) {
        if (path.basename(repositoryDir) === 'main' ||
            path.basename(repositoryDir) === 'cplace') {
            return [];
        }
        const parentReposPath = path.join(repositoryDir, 'parent-repos.json');
        if (!fs.existsSync(parentReposPath)) {
            utils_1.debug(`(AssetsCompiler) could not find parent-repos.json: ${parentReposPath}`);
            return [];
        }
        const parentReposContent = fs.readFileSync(parentReposPath, 'utf8');
        try {
            const parentRepos = JSON.parse(parentReposContent);
            return Object.keys(parentRepos);
        }
        catch (err) {
            console.error(utils_1.cerr `Failed to parse parent-repos.json: ${parentReposPath}`);
            throw err;
        }
    }
    static findPluginPath(repositoryDir, pluginName, repoDependencies) {
        let relativePathToPlugin = pluginName;
        if (AssetsCompiler.isPluginFolder(repositoryDir, pluginName)) {
            return path.join(repositoryDir, relativePathToPlugin);
        }
        for (const repoName of repoDependencies) {
            const pathToRepo = path.resolve(repositoryDir, '..', repoName);
            relativePathToPlugin = path.join('..', repoName, pluginName);
            if (AssetsCompiler.isPluginFolder(pathToRepo, pluginName)) {
                return relativePathToPlugin;
            }
        }
        console.error(utils_1.cerr `Could not locate plugin ${pluginName}`);
        throw Error(`Could not locate plugin ${pluginName}`);
    }
    static isPluginFolder(repoPath, pluginName) {
        if (fs.existsSync(path.join(repoPath, 'build.gradle')) &&
            fs.existsSync(path.join(repoPath, pluginName, 'build.gradle'))) {
            // a plugin in cplace 5.4 and later, with Gradle build files whose existence we just checked
            return true;
        }
        else if (!fs.existsSync(path.join(repoPath, 'build.gradle')) &&
            fs.existsSync(path.join(repoPath, pluginName))) {
            // a potential plugin in cplace 5.3 and earlier, with Ant build files and IML project files; we don't check those however
            return true;
        }
        return false;
    }
    isInCompilationScope(plugin) {
        return !this.runConfig.noParents || plugin.repo === this.repositoryName;
    }
}
exports.AssetsCompiler = AssetsCompiler;
AssetsCompiler.PLATFORM_PLUGIN_NAME = 'cf.cplace.platform';
//# sourceMappingURL=AssetsCompiler.js.map