/****************************************************************************
**
** Copyright (C) 2015 Jake Petroules.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qbs.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms and
** conditions see http://www.qt.io/terms-conditions. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

var FileInfo = loadExtension("qbs.FileInfo");
var ModUtils = loadExtension("qbs.ModUtils");
var Process = loadExtension("qbs.Process");

function findTscVersion(compilerFilePath, nodejsPath) {
    var p = new Process();
    try {
        if (nodejsPath)
            p.setEnv("PATH", nodejsPath);
        p.exec(compilerFilePath, ["--version"]);
        var re = /^(?:message TS6029: )?Version (([0-9]+(?:\.[0-9]+){1,3})(?:-(.+?))?)$/m;
        var match = p.readStdOut().trim().match(re);
        if (match !== null)
            return match;
    } finally {
        p.close();
    }
}

function tscArguments(product, inputs) {
    var i;
    var args = [];

    if (ModUtils.moduleProperty(product, "warningLevel") === "pedantic") {
        args.push("--noImplicitAny");
    }

    var targetVersion = ModUtils.moduleProperty(product, "targetVersion");
    if (targetVersion) {
        args.push("--target");
        args.push(targetVersion);
    }

    var moduleLoader = ModUtils.moduleProperty(product, "moduleLoader");
    if (moduleLoader) {
        args.push("--module");
        args.push(moduleLoader);
    }

    if (ModUtils.moduleProperty(product, "stripComments")) {
        args.push("--removeComments");
    }

    if (ModUtils.moduleProperty(product, "generateDeclarations")) {
        args.push("--declaration");
    }

    if (ModUtils.moduleProperty(product, "generateSourceMaps")) {
        args.push("--sourcemap");
    }

    // User-supplied flags
    var flags = ModUtils.moduleProperty(product, "compilerFlags");
    for (i in flags) {
        args.push(flags[i]);
    }

    if (supportsModernFeatures(product)) {
        args.push("--rootDir", product.sourceDirectory);
    }

    args.push("--outDir", product.buildDirectory);

    if (ModUtils.moduleProperty(product, "singleFile")) {
        args.push(outOption(product),
                  FileInfo.joinPaths(product.destinationDirectory, product.targetName) + ".js");
    }

    if (inputs.typescript_declaration) {
        for (i = 0; i < inputs.typescript_declaration.length; ++i) {
            args.push(inputs.typescript_declaration[i].filePath);
        }
    }

    if (inputs.typescript) {
        for (i = 0; i < inputs.typescript.length; ++i) {
            args.push(inputs.typescript[i].filePath);
        }
    }

    if (inputs["typescript.typescript-internal"]) {
        for (i = 0; i < inputs["typescript.typescript-internal"].length; ++i) {
            args.push(inputs["typescript.typescript-internal"][i].filePath);
        }
    }

    return args;
}

function outputArtifacts(product, inputs) {
    if (!supportsModernFeatures(product)) {
        console.warn("Qbs does not properly support TypeScript versions prior to 1.5 due to " +
                     "severe limitations in dependency tracking. This is TypeScript version " +
                     ModUtils.moduleProperty(product, "version") + ". It is strongly recommended " +
                     "that you upgrade TypeScript, or continue at your own risk.");
        return legacyOutputArtifacts(product, inputs);
    }

    var process;
    try {
        process = new Process();
        process.setEnv("NODE_PATH", [
            ModUtils.moduleProperty(product, "toolchainInstallPath"),
            product.moduleProperty("nodejs", "packageManagerRootPath")
        ].join(product.moduleProperty("qbs", "pathListSeparator")));
        process.exec(product.moduleProperty("nodejs", "interpreterFilePath"),
                     [FileInfo.joinPaths(product.buildDirectory,
                                         ".io.qt.qbs.internal.typescript",
                                         "qbs-tsc-scan.js")]
                     .concat(tscArguments(product, inputs)), true);
        var artifacts = JSON.parse(process.readStdOut());

        // Find and tag the "main" output file
        var applicationFile = product.moduleProperty("nodejs", "applicationFile");
        if (applicationFile) {
            var i, appIndex = -1;
            if (product.moduleProperty("typescript", "singleFile")) {
                for (i = 0; i < artifacts.length; ++i) {
                    if (artifacts[i].fileTags.contains("compiled_typescript")) {
                        appIndex = i;
                        break;
                    }
                }
            } else {
                var expected = FileInfo.relativePath(product.sourceDirectory, applicationFile);
                if (!expected.endsWith(".ts"))
                    // tsc doesn't allow this anyways, so it's a perfectly reasonable restriction
                    throw "TypeScript source file name '" + applicationFile +
                            "' does not end with .ts";

                expected = expected.slice(0, -2) + "js";

                for (i = 0; i < artifacts.length; ++i) {
                    if (expected === FileInfo.relativePath(product.buildDirectory,
                                                           artifacts[i].filePath)) {
                        appIndex = i;
                        break;
                    }
                }
            }

            if (appIndex === -1 || !artifacts[appIndex].fileTags.contains("compiled_typescript"))
                throw "nodejs.applicationFile was set, but Qbs couldn't find the compiled " +
                        "JavaScript file corresponding to '" + applicationFile + "'";

            artifacts[appIndex].fileTags = artifacts[appIndex].fileTags.concat(["application_js"]);
        }

        return artifacts;
    } finally {
        if (process)
            process.close();
    }
}

function legacyOutputArtifacts(product, inputs) {
    var artifacts = [];

    if (!inputs.typescript) {
        return artifacts;
    }

    var jsTags = ["js", "compiled_typescript"];
    var filePath = FileInfo.joinPaths(product.destinationDirectory, product.targetName);
    if (product.moduleProperty("typescript", "singleFile")) {
        // We could check
        // if (product.moduleProperty("nodejs", "applicationFile") === inputs.typescript[i].filePath)
        // but since we're compiling to a single file there's no need to state it explicitly
        jsTags.push("application_js");

        artifacts.push({fileTags: jsTags,
                        filePath: FileInfo.joinPaths(
                                      product.moduleProperty("nodejs",
                                                             "compiledIntermediateDir"),
                                      product.targetName + ".js")});

        if (product.moduleProperty("typescript", "generateDeclarations")) {
            artifacts.push({fileTags: ["typescript_declaration"],
                            filePath: filePath + ".d.ts"});
        }

        if (product.moduleProperty("typescript", "generateSourceMaps")) {
            artifacts.push({fileTags: ["source_map"],
                            filePath: filePath + ".js.map"});
        }
    } else {
        for (var i = 0; i < inputs.typescript.length; ++i) {
            jsTags = ["js", "compiled_typescript"];
            if (product.moduleProperty("nodejs", "applicationFile") === inputs.typescript[i].filePath)
                jsTags.push("application_js");

            var intermediatePath = FileInfo.path(FileInfo.relativePath(
                                                     product.sourceDirectory,
                                                     inputs.typescript[i].filePath));

            var baseName = FileInfo.baseName(inputs.typescript[i].fileName);
            filePath = FileInfo.joinPaths(product.destinationDirectory,
                                          intermediatePath,
                                          baseName);

            artifacts.push({fileTags: jsTags,
                            filePath: FileInfo.joinPaths(
                                          product.moduleProperty("nodejs",
                                                                 "compiledIntermediateDir"),
                                          intermediatePath,
                                          baseName + ".js")});

            if (product.moduleProperty("typescript", "generateDeclarations")) {
                artifacts.push({fileTags: ["typescript_declaration"],
                                filePath: filePath + ".d.ts"});
            }

            if (product.moduleProperty("typescript", "generateSourceMaps")) {
                artifacts.push({fileTags: ["source_map"],
                                filePath: filePath + ".js.map"});
            }
        }
    }

    return artifacts;
}

function outOption(product) {
    var compilerVersionMajor = ModUtils.moduleProperty(product, "versionMajor");
    if (compilerVersionMajor === 1) {
        if (ModUtils.moduleProperty(product, "versionMinor") < 6) {
            return "--out";
        }
    }

    return "--outFile";
}

function supportsModernFeatures(product) {
    var compilerVersionMajor = ModUtils.moduleProperty(product, "versionMajor");
    if (compilerVersionMajor === 1) {
        if (ModUtils.moduleProperty(product, "versionMinor") >= 5) {
            return true;
        }
    }

    return compilerVersionMajor > 1;
}
