"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const ChildProcess = tslib_1.__importStar(require("child_process"));
const Path = tslib_1.__importStar(require("path"));
const FSE = tslib_1.__importStar(require("fs-extra"));
const get_port_1 = tslib_1.__importDefault(require("get-port"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const uuid_1 = tslib_1.__importDefault(require("uuid"));
const v = tslib_1.__importStar(require("villa"));
const node_shared_1 = require("../../../node-shared");
const _utils_1 = require("../../@utils");
const workspace_1 = require("../workspace");
const _git_services_1 = require("./@git-services");
const _workspace_files_1 = require("./@workspace-files");
function PROJECT_CONFIG_PATH(config, workspace, project) {
    return Path.join(config.dir, 'workspaces', workspace.id, project.name, 'remote-workspace.json');
}
exports.PROJECT_CONFIG_PATH = PROJECT_CONFIG_PATH;
class Daemon {
    constructor(config, storage) {
        this.config = config;
        this.storage = storage;
        this.workspaceFiles = new _workspace_files_1.WorkspaceFiles(this.config);
        this.dockerComposeUpPromise = Promise.resolve();
        this.update().catch(console.error);
    }
    get workspaces() {
        return this.storage
            .list('workspaces')
            .map(({ port, ...raw }) => new workspace_1.Workspace(raw, port, this.config));
    }
    async isWorkspaceReady(id) {
        return FSE.stat(Path.join(this.config.dir, 'workspaces', id, '.ready')).then(() => true, () => false);
    }
    async getWorkspaceStatuses() {
        let gitHostToServiceConfigMap = this.config.gitHostToServiceConfigMap;
        let workspaces = await v.map(this.storage.list('workspaces'), async (metadata) => {
            return {
                ...metadata,
                projects: await v.map(metadata.projects, async (project) => {
                    return {
                        ...project,
                        ...(await this.getInPlaceProjectConfig(metadata, project)),
                    };
                }),
                ready: await this.isWorkspaceReady(metadata.id),
            };
        });
        let projectInfos = lodash_1.default.compact(lodash_1.default.uniqWith(lodash_1.default.compact(lodash_1.default.flattenDeep(workspaces.map(workspace => workspace.projects.map(({ git: { url } }) => _utils_1.parseGitURL(url))))), lodash_1.default.isEqual).map(info => {
            let service = gitHostToServiceConfigMap.get(info.host);
            return (service && {
                ...info,
                service,
            });
        }));
        let pullMergeRequestInfos = await _git_services_1.listPullMergeRequests(projectInfos);
        return workspaces.map(workspace => {
            return {
                ...workspace,
                projects: workspace.projects.map(project => {
                    let { url, branch: targetBranch = 'master', newBranch: sourceBranch = targetBranch, } = project.git;
                    let urlInfo = _utils_1.parseGitURL(url);
                    if (!urlInfo || targetBranch === sourceBranch) {
                        return project;
                    }
                    let { host, project: projectPath } = urlInfo;
                    let pullMergeRequestInfo = pullMergeRequestInfos.find(info => info.host === host &&
                        info.project === projectPath &&
                        info.targetBranch === targetBranch &&
                        info.sourceBranch === sourceBranch);
                    if (pullMergeRequestInfo) {
                        let { id, url, state } = pullMergeRequestInfo;
                        return {
                            ...project,
                            git: {
                                ...project.git,
                                pullMergeRequest: {
                                    text: `#${id}`,
                                    url,
                                    state,
                                },
                            },
                        };
                    }
                    let serviceConfig = gitHostToServiceConfigMap.get(host);
                    let createPullMergeRequestInfo = serviceConfig &&
                        _git_services_1.generateCreatePullMergeRequestInfo(host, projectPath, sourceBranch, targetBranch, serviceConfig);
                    return {
                        ...project,
                        git: {
                            ...project.git,
                            pullMergeRequest: createPullMergeRequestInfo,
                        },
                    };
                }),
            };
        });
    }
    async createWorkspace(options) {
        let id = uuid_1.default();
        let workspacePortSet = new Set(this.workspaces.map(workspace => workspace.port));
        let port;
        // tslint:disable-next-line: no-conditional-assignment
        while ((port = await get_port_1.default())) {
            if (!workspacePortSet.has(port)) {
                break;
            }
        }
        this.storage.push('workspaces', {
            id,
            port,
            ...options,
        });
        await this.update();
        return id;
    }
    async updateWorkspace(workspace) {
        this.storage.pull('workspaces', ({ id }) => id === workspace.id);
        this.storage.push('workspaces', workspace);
        await this.update();
    }
    async deleteWorkspace(id) {
        this.storage.pull('workspaces', metadata => metadata.id === id);
        await this.update();
    }
    async retrieveWorkspaceLog(id) {
        let logProcess = ChildProcess.spawn('docker', [
            'logs',
            '--timestamps',
            `${this.config.name}_${id}_1`,
        ]);
        let log = '';
        logProcess.stdout.on('data', data => {
            log += data;
        });
        logProcess.stderr.on('data', data => {
            log += data;
        });
        await v.awaitable(logProcess);
        return log
            .split('\n')
            .sort()
            .join('\n');
    }
    async getInPlaceProjectConfig(workspace, project) {
        let configFilePath = PROJECT_CONFIG_PATH(this.config, workspace, project);
        return node_shared_1.gracefulReadJSONFile(configFilePath);
    }
    async update() {
        let workspaces = this.workspaces;
        return (this.dockerComposeUpPromise = this.dockerComposeUpPromise
            .then(async () => {
            console.info('Updating workspace files...');
            await this.workspaceFiles.update(workspaces);
            await this.dockerComposeUp();
            await this.workspaceFiles.prune(workspaces);
            console.info('Done.');
        })
            .catch(console.error));
    }
    async dockerComposeUp() {
        let config = this.config;
        console.info('Updating containers...');
        let composeProcess = ChildProcess.spawn('docker-compose', ['--project-name', config.name, 'up', '--detach', '--remove-orphans'], {
            cwd: config.dir,
        });
        composeProcess.stdout.pipe(process.stdout);
        composeProcess.stderr.pipe(process.stderr);
        await v.awaitable(composeProcess);
    }
}
exports.Daemon = Daemon;
//# sourceMappingURL=daemon.js.map