/// <reference path="../typings/modules/request/index.d.ts"/>
"use strict";
var fs = require("fs");
var nodeutil = require("./nodeutil");
var path = require("path");
var request = require("request");
var U = pxt.Util;
var npm = nodeutil.runNpmAsyncWithCwd;
var npmCmd = nodeutil.addCmd("npm");
var pxtElectronPath;
var pxtElectronSrcPath;
var targetProductJson;
var targetNpmPackageName;
var isInit = false;
var targetDir = process.cwd();
var outDir = path.join(targetDir, "electron-out");
function errorOut(msg) {
    console.error("ERROR: " + msg);
    process.exit(1);
    return Promise.resolve();
}
function electronAsync(parsed) {
    // Ensure there is a subcommand
    var subcommand = parsed.arguments[0];
    if (!subcommand) {
        errorOut("Please specify a subcommand");
    }
    // Validate current target
    var needsCurrentTarget = subcommand !== "publish" && (subcommand !== "package" || !parsed.flags["release"]);
    if (needsCurrentTarget && (!fs.existsSync("pxtarget.json") || !fs.existsSync("package.json"))) {
        errorOut("This command requires to be in a valid target directory (pxtarget.json and package.json required)");
    }
    if (parsed.flags["release"]) {
        var match = /^(.*?)(?:@|$)/.exec(parsed.flags["release"]);
        if (!match || !match[1]) {
            errorOut("The specified released target is not valid. Required format: --release <target_npm_name>[@<target_npm_version>]");
        }
        targetNpmPackageName = match[1];
    }
    else {
        targetNpmPackageName = JSON.parse(fs.readFileSync("package.json", "utf8")).name;
    }
    // Find root of PXT Electron app sources
    if (parsed.flags["appsrc"]) {
        pxtElectronPath = parsed.flags["appsrc"];
        if (!fs.existsSync(pxtElectronPath)) {
            errorOut("Cannot find the specified PXT Electron app: " + pxtElectronPath);
        }
    }
    else {
        pxtElectronPath = path.join("..", "lrf_pxt", "electron");
        if (!fs.existsSync(pxtElectronPath)) {
            errorOut("Please specify --pxtElectron path; cannot find PXT Electron at default location: " + pxtElectronPath);
        }
    }
    pxtElectronPath = path.resolve(pxtElectronPath);
    pxtElectronSrcPath = path.resolve(pxtElectronPath, "src");
    // Ensure there is a product.json
    if (subcommand !== "init") {
        if (parsed.flags["product"]) {
            targetProductJson = parsed.flags["product"];
            if (!fs.existsSync(targetProductJson)) {
                errorOut("Cannot find the specified product.json file: " + targetProductJson);
            }
        }
        else {
            targetProductJson = path.join("electron", "product.json");
            if (!fs.existsSync(targetProductJson)) {
                errorOut("Please specify --product path; cannot find product.json at default location: " + targetProductJson);
            }
        }
        targetProductJson = path.resolve(targetProductJson);
    }
    // Other initializations
    var linkedTarget = path.join(pxtElectronSrcPath, "node_modules", targetNpmPackageName);
    var linkPath = fs.existsSync(linkedTarget) ? finalLinkPath(linkedTarget) : null;
    isInit = linkPath && path.resolve(linkPath) === path.resolve(process.cwd());
    if (parsed.flags["release"]) {
        setReleaseDirs();
    }
    // Invoke subcommand
    switch (subcommand) {
        case "init":
            return initAsync();
        case "run":
            return runAsync(parsed);
        case "package":
            return packageAsync(parsed);
        case "publish":
            return publishAsync(parsed);
        default:
            return errorOut("Unknown subcommand: " + subcommand);
    }
}
exports.electronAsync = electronAsync;
function setReleaseDirs() {
    targetDir = path.join(pxtElectronPath, "src", "node_modules", targetNpmPackageName);
    outDir = path.join(pxtElectronPath, "electron-out");
}
function initAsync() {
    return npm(pxtElectronSrcPath, "prune")
        .then(function () { return nodeutil.spawnWithPipeAsync({
        cmd: npmCmd,
        "args": ["-v"]
    }); })
        .then(function (buf) { return buf.toString(); })
        .then(function (v) {
        if (/^3\./.test(v)) {
            return errorOut("'pxt electron init/run' only works in NPM 2 due to a package linking bug in NPM 3. You can still use 'pxt electron package', however.");
        }
        return npm(pxtElectronPath, "install");
    })
        .then(function () { return npm(pxtElectronSrcPath, "link", process.cwd()); })
        .then(function () { return npm(pxtElectronPath, "run", "rebuildnative"); })
        .then(function () { return console.log("\nWARNING: 'pxt electron init' can break 'pxt serve'. If you have problems with 'pxt serve', delete all node modules and reinstall them (for both the target and pxt-core)."); });
}
function runAsync(parsed) {
    if (!isInit) {
        return errorOut("Current target not linked in Electron app; did you run 'pxt electron init'?");
    }
    var compilePromise = Promise.resolve();
    if (!parsed.flags["just"]) {
        compilePromise = compilePromise.then(function () { return electronGulpTask("compile"); });
    }
    return compilePromise
        .then(function () { return npm(pxtElectronPath, "run", "debug"); });
}
function packageAsync(parsed) {
    var installPromise = Promise.resolve();
    if (!parsed.flags["just"]) {
        installPromise = installPromise.then(function () { return npm(pxtElectronSrcPath, "prune"); });
        if (parsed.flags["release"]) {
            installPromise = installPromise.then(function () { return npm(pxtElectronSrcPath, "install", parsed.flags["release"]); });
        }
        else {
            installPromise = installPromise.then(function () { return installLocalTargetAsync(); });
        }
        installPromise = installPromise
            .then(function () { return npm(pxtElectronPath, "install"); })
            .then(function () { return npm(pxtElectronPath, "run", "rebuildnative"); });
    }
    return installPromise
        .then(function () { return electronGulpTask("package"); })
        .then(function () {
        if (parsed.flags["installer"]) {
            return buildInstallerAsync();
        }
        return Promise.resolve();
    });
}
function buildInstallerAsync() {
    return electronGulpTask("dist");
}
function publishAsync(parsed) {
    var builtRepoName = process.env["PXT_RELEASE_REPO_NAME"];
    var builtRepoOrg = process.env["PXT_RELEASE_REPO_ORG"];
    var accessToken = process.env["GITHUB_ACCESS_TOKEN"];
    var currentOs = process.platform;
    var builtRepoLocalPath;
    var latestTag;
    var releaseInfo;
    targetNpmPackageName = process.env["PXT_ELECTRON_TARGET"];
    if (!builtRepoName) {
        return errorOut("PXT_RELEASE_REPO_NAME not specified");
    }
    if (!builtRepoOrg) {
        return errorOut("PXT_RELEASE_REPO_ORG not specified");
    }
    if (!accessToken) {
        return errorOut("GITHUB_ACCESS_TOKEN not specified");
    }
    if (!targetNpmPackageName) {
        return errorOut("PXT_ELECTRON_TARGET not specified");
    }
    if (fs.existsSync(builtRepoName)) {
        builtRepoLocalPath = builtRepoName;
    }
    else {
        if (fs.existsSync(path.join("..", builtRepoName))) {
            builtRepoLocalPath = path.join("..", builtRepoName);
        }
    }
    if (!builtRepoLocalPath) {
        return errorOut("Release repo not cloned locally");
    }
    return nodeutil.gitInfoAsync(["--no-pager", "log", "--tags", "--simplify-by-decoration", "--pretty=\"format:%ai %d\""], builtRepoLocalPath, /*silent*/ true)
        .then(function (output) {
        // Output is a list of tags sorted in reverse chronological order, example of a line: 2016-08-16 07:46:43 -0700  (tag: v0.3.30)
        var tagVersionRegex = /tag: (v\d+\.\d+\.\d+\.*?)[,\)]/;
        var execResult = tagVersionRegex.exec(output);
        if (!execResult) {
            return errorOut("Unable to determine latest tag of built repo");
        }
        latestTag = execResult[1];
        return getOrCreateGHRelease(builtRepoOrg, builtRepoName, accessToken, latestTag);
    })
        .then(function (r) {
        releaseInfo = r;
        var appAlreadyExists = releaseInfo.assets.some(function (a) {
            return a.name.indexOf(currentOs) !== -1;
        });
        if (appAlreadyExists) {
            console.log("Electron app already published for this version");
            return Promise.resolve();
        }
        parsed.flags["release"] = targetNpmPackageName + "@" + latestTag.substring(1);
        parsed.flags["installer"] = true;
        setReleaseDirs();
        return packageAsync(parsed)
            .then(function () { return new Promise(function (resolve, reject) {
            var uploadAssetPath;
            fs.readdirSync(outDir).forEach(function (p) {
                var itemPath = path.join(outDir, p);
                if (!fs.statSync(itemPath).isDirectory() && itemPath.indexOf(currentOs) !== -1) {
                    uploadAssetPath = itemPath;
                }
            });
            if (!uploadAssetPath) {
                errorOut("Could not find asset to upload");
            }
            else {
                var req = request.post(releaseInfo["upload_url"].replace(/{\?name,label}/, "") + "?name=" + path.basename(uploadAssetPath) + "&access_token=" + accessToken, function (err, resp, body) {
                    if (err || resp.statusCode !== 201) {
                        errorOut("Error in POST request while uploading app to GitHub release");
                    }
                    else {
                        resolve();
                    }
                });
                var form = req.form();
                form.append(uploadAssetPath, fs.createReadStream(uploadAssetPath));
            }
        }); })
            .then(function () {
            console.log("App successfully published to GitHub");
        });
    });
}
function getOrCreateGHRelease(builtRepoOrg, builtRepoName, accessToken, latestTag) {
    return U.httpGetJsonAsync("https://api.github.com/repos/" + builtRepoOrg + "/" + builtRepoName + "/releases/tags/" + latestTag + "?access_token=" + accessToken)
        .catch(function (e) {
        var createRelease = {
            tag_name: latestTag,
            name: latestTag
        };
        return U.requestAsync({
            method: "POST",
            url: "https://api.github.com/repos/" + builtRepoOrg + "/" + builtRepoName + "/releases?access_token=" + accessToken,
            data: createRelease,
            allowHttpErrors: true
        })
            .then(function (resp) {
            if (resp.statusCode !== 201) {
                return Promise.reject({ statusCode: resp.statusCode });
            }
            return Promise.resolve(resp.json);
        });
    })
        .catch(function (e) {
        // Creating the release failed, error out
        return errorOut("Bad status while creating release: " + e.statusCode);
    });
}
function installLocalTargetAsync() {
    var packageJsonPath = path.join(process.cwd(), "package.json");
    var packageJsonBackupFolder = "package-json-backup";
    var packageJsonBackupPath = path.join(process.cwd(), packageJsonBackupFolder, "package.json");
    console.log("installLocalTargetAsync - backing up package.json");
    nodeutil.cp(packageJsonPath, packageJsonBackupFolder);
    console.log("installLocalTargetAsync - modifying existing package.json to remove dependencies");
    var packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), "package.json"), "utf8"));
    delete packageJson.dependencies;
    console.log("installLocalTargetAsync - writing new package.json");
    fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson), "utf8");
    return nodeutil.spawnWithPipeAsync({
        cmd: nodeutil.addCmd("npm"),
        args: [
            "pack",
            process.cwd()
        ],
        cwd: pxtElectronSrcPath
    })
        .then(function (packedTargetBuffer) {
        console.log("installLocalTargetAsync - removing package.json");
        fs.unlinkSync(packageJsonPath);
        console.log("installLocalTargetAsync - restoring package.json from backup");
        nodeutil.cp(packageJsonBackupPath, process.cwd());
        console.log("installLocalTargetAsync - removing package.json backup folder");
        fs.unlinkSync(packageJsonBackupPath);
        fs.rmdirSync(path.join(process.cwd(), packageJsonBackupFolder));
        var packedTargetPath = path.join(pxtElectronSrcPath, packedTargetBuffer.toString());
        return npm(pxtElectronSrcPath, "install", packedTargetPath)
            .then(function () {
            //fs.unlinkSync(packedTargetPath);
            // If pxt-core is linked inside the current target, install the linked pxt-core in the app instead of the published one
            var pxtCorePath = path.join(process.cwd(), "node_modules", "pxt-core");
            var pxtCoreTruePath = finalLinkPath(pxtCorePath);
            /*
            if (pxtCorePath !== pxtCoreTruePath) {
                return reinstallLocalPxtCoreAsync(pxtCoreTruePath);
            }
            */
            return Promise.resolve(pxtCoreTruePath);
        })
            .then(function (pxtCoreTruePath) {
            return nodeutil.spawnWithPipeAsync({
                cmd: nodeutil.addCmd("npm"),
                args: [
                    "pack",
                    pxtCoreTruePath
                ],
                cwd: pxtElectronSrcPath
            });
        })
            .then(function (packedCoreBuffer) {
            var packedCorePath = path.join(pxtElectronSrcPath, packedCoreBuffer.toString());
            return npm(path.join(pxtElectronSrcPath, "node_modules", "pxt-lrf"), "install", packedCorePath);
        });
    });
}
function reinstallLocalPxtCoreAsync(pxtCoreTruePath) {
    // The location where we need to run npm install for pxt-core depends on NPM 2 vs NPM 3, so we first launch a small node script to determine where to install from
    var pxtCoreLocation;
    return nodeutil.spawnWithPipeAsync({
        cmd: "node",
        args: [
            "-e",
            ("console.log(require(\"" + targetNpmPackageName + "\").pxtCoreDir)")
        ],
        cwd: pxtElectronSrcPath
    })
        .then(function (pxtCoreLocationBuffer) {
        pxtCoreLocation = path.join(pxtElectronSrcPath, "node_modules", "pxt-lrf"); // = path.join(pxtCoreLocationBuffer.toString(), "..", "..");
        console.log("pxtCoreLocation is " + pxtCoreLocation);
        return npm(pxtCoreLocation, "uninstall", "pxt-core");
    })
        .then(function () {
        return nodeutil.spawnWithPipeAsync({
            cmd: nodeutil.addCmd("npm"),
            args: [
                "pack",
                pxtCoreTruePath
            ],
            cwd: pxtElectronSrcPath
        });
    })
        .then(function (packedCoreBuffer) {
        var packedCorePath = path.join(pxtElectronSrcPath, packedCoreBuffer.toString());
        return npm(pxtCoreLocation, "install", packedCorePath, "--no-save")
            .then(function () {
            return npm(path.join(pxtElectronSrcPath, "node_modules", "pxt-lrf", "pxt-core"), "install");
            //fs.unlinkSync(packedCorePath);
        });
    });
}
function electronGulpTask(taskName) {
    var gulpPath = path.join(pxtElectronPath, "node_modules", ".bin", "gulp");
    return nodeutil.spawnAsync({
        cmd: nodeutil.addCmd(gulpPath),
        args: [
            taskName,
            "--" + targetProductJson,
            "--" + targetDir,
            "--" + outDir
        ],
        cwd: pxtElectronPath
    });
}
function finalLinkPath(link) {
    var foundFinal = false;
    var target = link;
    while (!foundFinal) {
        try {
            target = fs.readlinkSync(target);
        }
        catch (e) {
            foundFinal = true;
        }
    }
    return target;
}
