const fs = require("fs");
const path = require("path");
const gulp = require("gulp");
const compiler = require("vue-template-compiler");

const utf8Enc = {encoding: "utf8"};
const wrapInFunc = string => {
    return `function () {${string}}`;
};

gulp.task("clean", () => {
    const derivedFilePathSegments = [
        ["add-on", "lib", "standards.js"],
        ["add-on", "lib", "third_party", "sjcl.js"],
        ["add-on", "lib", "third_party", "uri.all.min.js"],
        ["add-on", "config", "js", "third_party", "vue.runtime.min.js"],
    ];

    const derivedFilePaths = derivedFilePathSegments.map(segs => path.join(...segs));

    derivedFilePaths.forEach(filepath => {
        if (!fs.existsSync(filepath)) {
            return;
        }
        fs.unlinkSync(filepath);
    });

    const vueCompiledTemplatesDirPath = path.join("add-on", "config", "js", "vue_compiled_templates");

    if (fs.existsSync(vueCompiledTemplatesDirPath)) {
        fs.readdirSync(vueCompiledTemplatesDirPath).forEach(filepath => {
            const pathToCompiledTemplate = path.join(vueCompiledTemplatesDirPath, filepath);
            fs.unlinkSync(pathToCompiledTemplate);
        });
    }
});

const buildAddonManifestJson = () => {
    // First read the version of the npm project, so that we can use that
    // to set the version in the extension manifest.
    const packageJsonSource = fs.readFileSync("package.json", utf8Enc);
    const packageJsonData = JSON.parse(packageJsonSource);
    const currentVersion = packageJsonData.version;

    // Next, read the source manifest.json file out of the sources directory.
    const sourceManifestJsonPath = path.join("sources", "add-on", "manifest.json");
    const sourceManifestJsonSource = fs.readFileSync(sourceManifestJsonPath, utf8Enc);
    const sourceManifestJsonData = JSON.parse(sourceManifestJsonSource);

    // Then, set the version in the add-on manifest file with the source from
    // the npm package.json file.
    sourceManifestJsonData.version = currentVersion;

    // Finally, write the "compiled" manifest.json version into the add-on
    // directory, or what will be bundled.
    const destMainifestJsonString = JSON.stringify(sourceManifestJsonData, null, "  ");
    const destMainifestJsonPath = path.join("add-on", "manifest.json");
    fs.writeFileSync(destMainifestJsonPath, destMainifestJsonString);
};

/**
 * The default gulp task does all of the following:
 *
 * 1. Compile the Web API standards in sources/standards/*.json into a single
 *    data structure.
 * 2. Prepend that data structure to the stubbed out javascript file in
 *    sources/js/standards.part.js to create add-on/lib/standards.js.
 * 3. Copy several third party libraries from npm into the add-on.  This is
 *    done because Mozilla's add-on review process requires either providing
 *    source of all libraries, or using libraries from npm.
 * 4. Build the extension's manifest by using the version in package.json
 *    to set the "version" key in sources/add-on/manifest.json, and copy that
 *    file to add-on/manifest.json.
 * 5. Compile the Vue.js templates in sources/vue/ into the compiled versions
 *    used by the config page of the extension (and copy those compiled versions
 *    to add-on/config/js/vue_compiled_templates
 */
gulp.task("default", () => {
    const builtScriptComment = "/** This file is automatically generated. Do not edit it!**/\n";
    const standardsDefDir = path.join("sources", "standards");

    // Step #1 from above.
    const combinedStandards = fs.readdirSync(standardsDefDir)
        .reduce((prev, next) => {
            if (next.indexOf(".json") === -1) {
                return prev;
            }

            const fileContents = fs.readFileSync(path.join(standardsDefDir, next), utf8Enc);
            let standardContents;
            try {
                standardContents = JSON.parse(fileContents);
            } catch (e) {
                console.log("Invalid JSON in " + next);
                throw e;
            }

            prev[standardContents.info.id] = standardContents;
            return prev;
        }, {});

    let renderedStandardsModule = builtScriptComment;
    const standardsLibModule = {
        standardsDefinitions: combinedStandards,
    };

    renderedStandardsModule += "window.WEB_API_MANAGER.standardsLib = ";
    renderedStandardsModule += JSON.stringify(standardsLibModule) + ";\n";

    // Step #2 from above.
    const pathToStandardsLibStub = path.join("sources", "js", "standards.part.js");
    renderedStandardsModule += fs.readFileSync(pathToStandardsLibStub, utf8Enc);

    fs.writeFileSync(path.join("add-on", "lib", "standards.js"), renderedStandardsModule);

    // Step #3 from above doc block.
    const thirdPartyConfigDirPath = path.join("add-on", "config", "js", "third_party");
    if (!fs.existsSync(thirdPartyConfigDirPath)) {
        fs.mkdirSync(thirdPartyConfigDirPath);
    }

    const sjclSourcePath = path.join("node_modules", "sjcl", "sjcl.js");
    const sjclDestPath = path.join("add-on", "lib", "third_party", "sjcl.js");
    fs.copyFileSync(sjclSourcePath, sjclDestPath);

    const vueSourcePath = path.join("node_modules", "vue", "dist", "vue.runtime.min.js");
    const vueDestPath = path.join("add-on", "config", "js", "third_party", "vue.runtime.min.js");
    fs.copyFileSync(vueSourcePath, vueDestPath);

    const uriSourcePath = path.join("node_modules", "uri-js", "dist", "es5", "uri.all.min.js");
    const uriDestPath = path.join("add-on", "lib", "third_party", "uri.all.min.js");

    fs.copyFileSync(uriSourcePath, uriDestPath);

    // Step #4 from the above doc block.
    buildAddonManifestJson();

    // Step #5 from above doc block.
    const vueTemplatesSourcePath = path.join("sources", "vue");
    const vueTemplatesDestPath = path.join("add-on", "config", "js", "vue_compiled_templates");
    if (!fs.existsSync(vueTemplatesDestPath)) {
        fs.mkdirSync(vueTemplatesDestPath);
    }

    fs.readdirSync(vueTemplatesSourcePath).forEach(filepath => {
        const absolutePath = path.join(__dirname, vueTemplatesSourcePath, filepath);
        const fileSource = fs.readFileSync(absolutePath, utf8Enc);

        const compilationResult = compiler.compile(fileSource);

        if (compilationResult.errors.length > 0) {
            console.error("Errors when compiling Vue templates: ");
            compilationResult.errors.forEach(console.error);
            process.exit(1);
        }

        if (compilationResult.tips.length > 0) {
            console.log("Suggestions from the Vue compiler: ");
            compilationResult.errors.forEach(console.log);
            process.exit(1);
        }

        const staticFuncsAsStrings = compilationResult.staticRenderFns.map(wrapInFunc);

        const compiledRenderFunction = `
/* This file is derived from sources/vue/${filepath}. */
if (window.WEB_API_MANAGER.vueComponents === undefined) {
    window.WEB_API_MANAGER.vueComponents = {};
}

window.WEB_API_MANAGER.vueComponents["${filepath.replace(".vue.html", "")}"] = {
    render: ${wrapInFunc(compilationResult.render)},
    staticRenderFns: [${staticFuncsAsStrings.join(",")}]
};`;

        const destPath = path.join(vueTemplatesDestPath, filepath + ".js");
        fs.writeFileSync(destPath, compiledRenderFunction);
    });
});
