/**
 * Jdenticon
 * https://github.com/dmester/jdenticon
 * Copyright © Daniel Mester Pirttijärvi
 */
"use strict";

const del = require("del");
const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
const { exec } = require("child_process");
const { promisify } = require("util");
const pack = require("./package.json");

// Gulp dependencies
const gulp = require("gulp");
const rename = require("gulp-rename");
const terser = require("gulp-terser");
const zip = require("gulp-zip");
const replace = require("./build/gulp/replacement").gulp;
const wrapTemplate = require("./build/gulp/wrap-template");
const buble = require("gulp-buble");
const sourcemaps = require("gulp-sourcemaps");
const preMinify = require("./build/gulp/pre-minify");
const removeJsDocImports = require("./build/gulp/remove-jsdoc-imports");
const removeMappedSource = require("./build/gulp/remove-mapped-source");

// Rollup dependencies
const rollup = require("./build/gulp/rollup");
const commonjs = require( "@rollup/plugin-commonjs");
const stripBanner = require("rollup-plugin-strip-banner");
const alias = require("@rollup/plugin-alias");

// Constants
const LICENSE = fs.readFileSync("./LICENSE").toString();
const VARIABLES = [
    [/#version#/g, pack.version],
    [/#year#/g, new Date().getFullYear()],
    [/#date#/g, new Date().toISOString()],

    // Keep line prefix, e.g. " * " for license banners in JavaScript.
    [/(.*)#license#/gm, "$1" + LICENSE.trim().replace(/\n/g, "\n$1")],
];

function umdSrc() {
    return gulp.src("./src/browser-umd.js")
        .pipe(sourcemaps.init())    
        .pipe(rollup({
            output: { format: "cjs" },
            plugins: [
                stripBanner(), 
                alias({
                    entries: [
                        { find: /^(.*[\/\\])global$/, replacement: "$1global.umd" },
                    ]
                }),
            ],
        }))

        .pipe(rename(function (path) { path.basename = "notmapped"; path.extname = ".js" }))

        .pipe(buble())
        .pipe(preMinify())
        .pipe(removeJsDocImports())

        // The UMD template expects a factory function body, so replace export with a return for the factory function.
        .pipe(replace("module.exports = ", "return "))

        .pipe(replace(VARIABLES))
        .pipe(wrapTemplate("./build/template-umd.js", VARIABLES));
}

gulp.task("clean", function () {
    return del(["./~jdenticon.nuspec", "./obj/output"]);
});

gulp.task("build-umd", function () {
    return umdSrc()
        .pipe(rename(function (path) { path.basename = "jdenticon"; path.extname = ".js" }))
        .pipe(gulp.dest("dist"))
        
        .pipe(rename(function (path) { path.basename = "jdenticon-" + pack.version; path.extname = ".js" }))
        .pipe(gulp.dest("obj/output"));
});

gulp.task("build-umd-min", function () {
    return umdSrc()
        .pipe(terser())
        .pipe(wrapTemplate("./build/template-min.js", VARIABLES))

        .pipe(removeMappedSource("notmapped.js"))

        .pipe(rename(function (path) { path.basename = "jdenticon"; path.extname = ".min.js" }))
        .pipe(sourcemaps.write(".", { includeContent: true }))
        .pipe(gulp.dest("dist"))
        
        .pipe(rename(function (path) { path.basename = "jdenticon-" + pack.version; path.extname = ".min.js" }))
        .pipe(sourcemaps.write(".", { includeContent: true }))
        .pipe(gulp.dest("obj/output"));
});

gulp.task("build-cjs", function () {
    return gulp.src("./src/browser-cjs.js")
        .pipe(sourcemaps.init())
        .pipe(rollup({
            output: { format: "cjs" },
            plugins: [ stripBanner() ],
        }))

        .pipe(rename(function (path) { path.basename = "notmapped"; path.extname = ".js" }))
        .pipe(buble())
        .pipe(preMinify())
        .pipe(removeJsDocImports())

        // Replace variables
        .pipe(replace(VARIABLES))
        .pipe(wrapTemplate("./build/template-module.js", VARIABLES))

        .pipe(removeMappedSource("notmapped.js"))
        
        .pipe(rename(function (path) { path.basename = "jdenticon-module"; path.extname = ".js" }))
        .pipe(replace(/[\r\n]*$/, "\n//# sourceMappingURL=jdenticon-module.js.map\n"))
        .pipe(sourcemaps.write("./", { includeContent: true, addComment: false }))
        .pipe(gulp.dest("dist"))
        
        .pipe(rename(function (path) { path.basename = "jdenticon-module-" + pack.version; path.extname = ".js" }))
        .pipe(gulp.dest("obj/output"))
});

gulp.task("build-esm", function () {
    return gulp.src("./src/browser-esm.js")
        .pipe(sourcemaps.init())
        .pipe(rollup({
            output: { format: "esm" },
            plugins: [ stripBanner() ],
        }))

        .pipe(preMinify())
        .pipe(removeJsDocImports())

        // Replace variables
        .pipe(replace(VARIABLES))
        .pipe(wrapTemplate("./build/template-module.js", VARIABLES))

        .pipe(rename(function (path) { path.basename = "jdenticon-module"; path.extname = ".mjs" }))
        .pipe(replace(/[\r\n]*$/, "\n//# sourceMappingURL=jdenticon-module.mjs.map\n"))
        .pipe(sourcemaps.write("./", { includeContent: true, addComment: false }))
        .pipe(gulp.dest("dist"))
        
        .pipe(rename(function (path) { path.basename = "jdenticon-module-" + pack.version; path.extname = ".mjs" }))
        .pipe(gulp.dest("obj/output"))
});

gulp.task("build-node-cjs", function () {
    return gulp.src("./src/node-cjs.js")
        .pipe(sourcemaps.init())
        .pipe(rollup({
            external: [ "canvas-renderer" ],
            plugins: [ stripBanner(), commonjs() ],
            output: { format: "cjs" },
        }))
        
        .pipe(removeJsDocImports())

        .pipe(replace(VARIABLES))
        .pipe(wrapTemplate("./build/template-module.js", VARIABLES))

        .pipe(rename(path => { path.basename = "jdenticon-node"; path.extname = ".js" }))
        .pipe(replace(/[\r\n]*$/, "\n//# sourceMappingURL=jdenticon-node.js.map\n"))
        .pipe(sourcemaps.write("./", { includeContent: true, addComment: false }))
        .pipe(gulp.dest("./dist"))
        
        .pipe(rename(function (path) { path.basename = "jdenticon-node-" + pack.version; path.extname = ".js" }))
        .pipe(gulp.dest("./obj/output"));
});

gulp.task("build-node-esm", function () {
    return gulp.src("./src/node-esm.js")
        .pipe(sourcemaps.init())
        .pipe(rollup({
            external: [ "canvas-renderer" ],
            plugins: [ stripBanner(), commonjs() ],
            output: { format: "esm" },
        }))

        .pipe(removeJsDocImports())

        .pipe(replace(VARIABLES))
        .pipe(wrapTemplate("./build/template-module.js", VARIABLES))

        .pipe(rename(path => { path.basename = "jdenticon-node"; path.extname = ".mjs" }))
        .pipe(replace(/[\r\n]*$/, "\n//# sourceMappingURL=jdenticon-node.mjs.map\n"))
        .pipe(sourcemaps.write("./", { includeContent: true, addComment: false }))

        .pipe(gulp.dest("./dist"))
        
        .pipe(rename(function (path) { path.basename = "jdenticon-node-" + pack.version; path.extname = ".mjs" }))
        .pipe(gulp.dest("./obj/output"));
});

gulp.task("update-license-year", function () {
    return gulp.src("./LICENSE")
        .pipe(replace(/\(c\) 2014-\d+/, "(c) 2014-" + new Date().getFullYear()))
        .pipe(gulp.dest("./"))
});

gulp.task("update-readme", function () {
    return gulp.src("./README.md")
        .pipe(replace([
            [/@\d{1,2}\.\d{1,3}\.\d{1,3}/, "@" + pack.version],
            [/(?<=integrity=\"([^-]+)-).*?(?=\")/, (match, algorithm) => {
                const min = fs.readFileSync("./dist/jdenticon.min.js");
                return crypto.createHash(algorithm).update(min).digest("base64");
            }],
        ]))
        .pipe(gulp.dest("./"))
});

gulp.task("install-jdenticon-test", function () {
    const globs = pack.files
        .map(file => {
            const isDirectory = 
                !/\*/.test(file) && 
                fs.existsSync(file) &&
                fs.lstatSync(file).isDirectory();
            return isDirectory ? path.join(file, "**") : file;
        });
    
    // Simulate an installed Jdenticon package. Cannot use actual npm command, since it won't install Jdenticon in a
    // subfolder to the Jdenticon source package itself.
    return gulp.src(["./package.json", ...globs], { base: "./" })
        .pipe(gulp.dest("./test/node_modules/jdenticon"));
});

gulp.task("build", gulp.series("clean", gulp.parallel(
    "build-umd", "build-umd-min",
    "build-esm", "build-cjs",
    "build-node-cjs", "build-node-esm",
), "install-jdenticon-test"));
 
gulp.task("clean-tests", function () {
    return del(["./obj/test/unit/**"]);
});

gulp.task("build-unit-tests-js", function () {
    return gulp.src("./test/unit/*.js", { base: "./" })
        .pipe(sourcemaps.init())
        .pipe(rollup({
            external: [ "canvas-renderer", "fs", "tap" ],
            plugins: [ commonjs() ],
            output: { format: "cjs" },
        }))
        .pipe(sourcemaps.write("./"))
        .pipe(gulp.dest("./obj"))
});

gulp.task("build-unit-tests", gulp.series("clean-tests", "build-unit-tests-js"));

gulp.task("prepare-release", function () {
    return gulp.src(["./LICENSE", "./README.md"])
        .pipe(replace(VARIABLES))
        .pipe(rename(function (path) { path.extname = ".txt" }))
        .pipe(gulp.dest("obj/output"));
});

gulp.task("prepare-nuget", function () {
    return gulp.src(["./build/jdenticon.nuspec"])
        .pipe(replace(VARIABLES))
        .pipe(rename(function (path) { path.basename = "~" + path.basename }))
        .pipe(gulp.dest("./"));
});

gulp.task("nuget", async function () {
    var command = "\"./build/nuget/nuget.exe\" pack ~jdenticon.nuspec -OutputDirectory releases";

    if (process.platform !== "win32") {
        command = "mono " + command;
    }

    await promisify(exec)(command);

    await del(["./~jdenticon.nuspec"]);
});

gulp.task("create-package", function () {
    return gulp.src(["./obj/output/*"])
        .pipe(zip("jdenticon-" + pack.version + ".zip"))
        .pipe(gulp.dest("releases"));
});

gulp.task("release", gulp.series(
    "build",
    "update-license-year", "update-readme",
    "prepare-release",
    "create-package",
    "prepare-nuget", "nuget",
));
