"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TempDb = exports.Databases = void 0;
const child_process_1 = require("child_process");
const path_1 = __importDefault(require("path"));
const fs_1 = require("./fs");
const which_1 = require("./which");
const connection_info_1 = require("./connection-info");
const myPackage = "node-tempdb";
const pbPackage = "PeanutButter.TempDb.Runner";
var Databases;
(function (Databases) {
    Databases["mysql"] = "mysql";
    Databases["sqlite"] = "sqlite";
    Databases["localdb"] = "localdb";
})(Databases = exports.Databases || (exports.Databases = {}));
class TempDb {
    constructor(type) {
        this._type = type !== null && type !== void 0 ? type : Databases.mysql;
    }
    static async create(type) {
        const result = new TempDb(type);
        await result.start();
        return result;
    }
    get type() {
        return this._type;
    }
    get config() {
        return this._connectionInfo
            ? this._connectionInfo.clone().config
            : undefined;
    }
    get knexConfig() {
        return this._connectionInfo
            ? this._connectionInfo.clone().knexConfig
            : undefined;
    }
    get connectionInfo() {
        return !this._connectionInfo
            ? undefined
            : this._connectionInfo.clone();
    }
    get isRunning() {
        return !!this._process;
    }
    /**
     * Starts the instance, returning the connection string once started
     */
    async start() {
        if (this._process) {
            throw new Error("Already started! Stop me first! If you want connection info, observe the connectionInfo property");
        }
        const runner = await this.findRunner();
        const process = this._process = child_process_1.spawn(runner, ["-e", this._type]);
        return new Promise((resolve, reject) => {
            let resolved = false;
            const stderr = [];
            process.stdin.setDefaultEncoding("utf8");
            process.stdout.on("data", d => {
                const line = d.toString().replace(/\n$/, "").replace(/\r$/, "");
                if (line.toLowerCase().startsWith("connection string:")) {
                    const connectionstring = line.split(":").slice(1).join(":").trim();
                    this._connectionInfo = new connection_info_1.ConnectionInfo(connectionstring, this._type);
                    if (!resolved) {
                        resolve(this._connectionInfo);
                        resolved = true;
                    }
                }
            });
            process.stderr.on("data", d => {
                stderr.push(d.toString());
            });
            process.on("close", code => {
                if (code && !resolved) {
                    reject(`Unable to start up ${runner}:\n${stderr.join("\n")}`);
                }
                if (code && this._stopReject) {
                    this._stopReject(code);
                }
                else if (this._stopResolve) {
                    this._stopResolve();
                }
                this._process = undefined;
                this._stopResolve = undefined;
                this._stopReject = undefined;
            });
        });
    }
    async stop() {
        return new Promise((resolve, reject) => {
            if (this._process === undefined) {
                return reject(`Not running`);
            }
            if (this._stopResolve) {
                return reject(`Busy trying to stop...`);
            }
            this._stopResolve = resolve;
            this._stopReject = reject;
            this._process.stdin.write("stop\n");
        });
    }
    findRunner() {
        // find the package folder
        if (process.platform === "win32") {
            return this.findRunnerForWindows();
        }
        else {
            return this.findRunnerForNix();
        }
    }
    async findRunnerForWindows() {
        // look for lib/net4* on windows
        const tempDbBase = await this.findFrameworkPackageFolder(), tempDbContents = await fs_1.fs.readdir(tempDbBase), search = `${pbPackage}.exe`, lsearch = search.toLowerCase(), launcher = tempDbContents.find(p => p.toLowerCase() === lsearch);
        if (!launcher) {
            throw new Error(`Could not find launcher ${search} in ${tempDbBase}`);
        }
        return path_1.default.join(tempDbBase, launcher);
    }
    async findNetCorePackageFolder() {
        const winner = await this.findPackageLibFolder(f => path_1.default.basename(f).startsWith("netcore"));
        if (!winner) {
            throw new Error(`Can't find netcore libdir under ${await this.findPackageFolder()}`);
        }
        return winner;
    }
    async findFrameworkPackageFolder() {
        const winner = await this.findPackageLibFolder(f => path_1.default.basename(f).startsWith("net4"));
        if (!winner) {
            throw new Error(`Can't find ${pbPackage} net4x libdir`);
        }
        return winner;
    }
    async findPackageLibFolder(matching) {
        const packageFolder = await this.findTempDbPackageFolder(), libDir = path_1.default.join(packageFolder, "lib"), contents = await fs_1.fs.readdir(libDir), contentsFullPaths = contents.map(f => path_1.default.join(libDir, f));
        return contentsFullPaths.find(matching);
    }
    async findTempDbPackageFolder() {
        const packageFolder = await this.findPackageFolder(), immediateContents = await fs_1.fs.readdir(packageFolder), searchPackageFolder = pbPackage.toLowerCase(), tempDbPackageFolder = immediateContents.find(p => p.toLowerCase().startsWith(searchPackageFolder));
        if (tempDbPackageFolder) {
            return path_1.default.join(packageFolder, tempDbPackageFolder);
        }
        throw new Error(`Could not find ${pbPackage} package folder under "${packageFolder}"`);
    }
    async findRunnerForNix() {
        const dotnet = await which_1.which("dotnet");
        if (dotnet) {
            return this.findDotNetRunnerForNix();
        }
        const mono = await which_1.which("mono");
        if (mono) {
            return this.findMonoRunnerForNix();
        }
        throw new Error(`node-tempdb levers off of ${pbPackage} You either need 'dotnet' or 'mono' to spin it up.`);
    }
    async findDotNetRunnerForNix() {
        return this.findShellScriptLauncher(await this.findNetCorePackageFolder());
    }
    async findMonoRunnerForNix() {
        return this.findShellScriptLauncher(await this.findFrameworkPackageFolder());
    }
    async findShellScriptLauncher(folder) {
        const contents = await fs_1.fs.readdir(folder), scriptname = "start-tempdb.sh", shellscript = contents.find(s => s.toLowerCase() === scriptname);
        if (!shellscript) {
            throw new Error(`Can't find launcher stub ${scriptname} in ${folder}`);
        }
        const result = path_1.default.join(folder, shellscript);
        // ensure it's executable!
        await fs_1.fs.chmod(result, "755");
        return result;
    }
    async findPackageFolder() {
        let current = __dirname, last = current;
        do {
            last = current;
            const test = path_1.default.join(current, "package.json");
            if (await fs_1.fs.fileExists(test)) {
                try {
                    const contents = await fs_1.fs.readTextFile(test), pkg = JSON.parse(contents), isMine = pkg.name === myPackage;
                    if (isMine) {
                        return current;
                    }
                }
                catch (e) {
                    // suppress: if we can't read & parse it, it might as well not exist
                }
            }
            current = path_1.default.dirname(current);
        } while (last !== current);
        throw new Error(`Can't find package base dir for "${myPackage}"`);
    }
}
exports.TempDb = TempDb;
