/* eslint-env node */
/* eslint no-console:"off" */

const { dest, parallel, series, src, watch } = require("gulp");

const child_process = require("child_process");
const fs = require("fs");
const os = require("os");
const path = require("path");
const util = require("util");
const stream = require("stream/promises");

const sass = require("gulp-sass")(require("sass"));
const tildeImporter = require("node-sass-tilde-importer");
const rtlcss = require("gulp-rtlcss");
const sourcemaps = require("gulp-sourcemaps");
const autoprefixer = require("gulp-autoprefixer");
const concatPo = require("gulp-concat-po");
const exec = require("gulp-exec");
const merge = require("merge-stream");
const through2 = require("through2");
const Vinyl = require("vinyl");
const args = require("minimist")(process.argv.slice(2), {
    default: { "generate-pot": "always" },
});
const rename = require("gulp-rename");

const STAFF_CSS_BASE = "koha-tmpl/intranet-tmpl/prog/css";
const OPAC_CSS_BASE = "koha-tmpl/opac-tmpl/bootstrap/css";

var CSS_BASE = args.view == "opac" ? OPAC_CSS_BASE : STAFF_CSS_BASE;

var sassOptions = {
    importer: tildeImporter,
    includePaths: [__dirname + "/node_modules", __dirname + "/../node_modules"],
};

// CSS processing for development
function css(css_base) {
    css_base = css_base || CSS_BASE;
    var stream = src(css_base + "/src/**/*.scss", { sourcemaps: true });

    if (args.view == "opac") {
        stream = stream
            .pipe(sass(sassOptions).on("error", sass.logError))
            .pipe(autoprefixer())
            .pipe(dest(css_base))
            .pipe(rtlcss())
            .pipe(
                rename({
                    suffix: "-rtl",
                })
            ) // Append "-rtl" to the filename.
            .pipe(dest(css_base, { sourcemaps: "./maps" }));
    } else {
        stream = stream
            .pipe(sass(sassOptions).on("error", sass.logError))
            .pipe(autoprefixer())
            .pipe(dest(css_base))
            .pipe(rtlcss())
            .pipe(
                rename({
                    suffix: "-rtl",
                })
            ) // Append "-rtl" to the filename.
            .pipe(dest(css_base, { sourcemaps: "./maps" }));
    }

    return stream;
}

// CSS processing for production
function build(css_base) {
    css_base = css_base || CSS_BASE;
    sassOptions.outputStyle = "compressed";
    var stream = src(css_base + "/src/**/*.scss")
        .pipe(sass(sassOptions).on("error", sass.logError))
        .pipe(autoprefixer())
        .pipe(dest(css_base))
        .pipe(rtlcss())
        .pipe(
            rename({
                suffix: "-rtl",
            })
        ) // Append "-rtl" to the filename.
        .pipe(dest(css_base));
    return stream;
}

function opac_css() {
    return css(OPAC_CSS_BASE);
}

function staff_css() {
    return css(STAFF_CSS_BASE);
}

const poTasks = {
    "marc-MARC21": {
        extract: po_extract_marc_marc21,
        create: po_create_marc_marc21,
        update: po_update_marc_marc21,
    },
    "marc-UNIMARC": {
        extract: po_extract_marc_unimarc,
        create: po_create_marc_unimarc,
        update: po_update_marc_unimarc,
    },
    "staff-prog": {
        extract: po_extract_staff,
        create: po_create_staff,
        update: po_update_staff,
    },
    "opac-bootstrap": {
        extract: po_extract_opac,
        create: po_create_opac,
        update: po_update_opac,
    },
    pref: {
        extract: po_extract_pref,
        create: po_create_pref,
        update: po_update_pref,
    },
    messages: {
        extract: po_extract_messages,
        create: po_create_messages,
        update: po_update_messages,
    },
    "messages-js": {
        extract: po_extract_messages_js,
        create: po_create_messages_js,
        update: po_update_messages_js,
    },
    installer: {
        extract: po_extract_installer,
        create: po_create_installer,
        update: po_update_installer,
    },
    "installer-MARC21": {
        extract: po_extract_installer_marc21,
        create: po_create_installer_marc21,
        update: po_update_installer_marc21,
    },
    "installer-UNIMARC": {
        extract: po_extract_installer_unimarc,
        create: po_create_installer_unimarc,
        update: po_update_installer_unimarc,
    },
};

function getPoTasks() {
    let tasks = [];

    let all_tasks = Object.keys(poTasks);

    if (args.task) {
        tasks = [args.task].flat(Infinity);
    } else {
        return all_tasks;
    }

    let invalid_tasks = tasks.filter(function (el) {
        return all_tasks.indexOf(el) < 0;
    });

    if (invalid_tasks.length) {
        console.error("Invalid task");
        return [];
    }

    return tasks;
}
const poTypes = getPoTasks();

function po_extract_marc(type) {
    return src(`koha-tmpl/*-tmpl/*/en/**/*${type}*`, {
        read: false,
        nocase: true,
    })
        .pipe(
            xgettext(
                "misc/translator/xgettext.pl --charset=UTF-8 -F",
                `Koha-marc-${type}.pot`
            )
        )
        .pipe(dest("misc/translator"));
}

function po_extract_marc_marc21() {
    return po_extract_marc("MARC21");
}
function po_extract_marc_unimarc() {
    return po_extract_marc("UNIMARC");
}

function po_extract_staff() {
    const globs = [
        "koha-tmpl/intranet-tmpl/prog/en/**/*.tt",
        "koha-tmpl/intranet-tmpl/prog/en/**/*.inc",
        "koha-tmpl/intranet-tmpl/prog/en/xslt/*.xsl",
        "!koha-tmpl/intranet-tmpl/prog/en/**/*MARC21*",
        "!koha-tmpl/intranet-tmpl/prog/en/**/*UNIMARC*",
        "!koha-tmpl/intranet-tmpl/prog/en/**/*marc21*",
        "!koha-tmpl/intranet-tmpl/prog/en/**/*unimarc*",
    ];

    return src(globs, { read: false, nocase: true })
        .pipe(
            xgettext(
                "misc/translator/xgettext.pl --charset=UTF-8 -F",
                "Koha-staff-prog.pot"
            )
        )
        .pipe(dest("misc/translator"));
}

function po_extract_opac() {
    const globs = [
        "koha-tmpl/opac-tmpl/bootstrap/en/**/*.tt",
        "koha-tmpl/opac-tmpl/bootstrap/en/**/*.inc",
        "koha-tmpl/opac-tmpl/bootstrap/en/xslt/*.xsl",
        "!koha-tmpl/opac-tmpl/bootstrap/en/**/*MARC21*",
        "!koha-tmpl/opac-tmpl/bootstrap/en/**/*UNIMARC*",
        "!koha-tmpl/opac-tmpl/bootstrap/en/**/*marc21*",
        "!koha-tmpl/opac-tmpl/bootstrap/en/**/*unimarc*",
    ];

    return src(globs, { read: false, nocase: true })
        .pipe(
            xgettext(
                "misc/translator/xgettext.pl --charset=UTF-8 -F",
                "Koha-opac-bootstrap.pot"
            )
        )
        .pipe(dest("misc/translator"));
}

const xgettext_options =
    "--from-code=UTF-8 --package-name Koha " +
    "--package-version= -k -k__ -k__x -k__n:1,2 -k__nx:1,2 -k__xn:1,2 " +
    "-k__p:1c,2 -k__px:1c,2 -k__np:1c,2,3 -k__npx:1c,2,3 -kN__ " +
    "-kN__n:1,2 -kN__p:1c,2 -kN__np:1c,2,3 " +
    "-k -k$__ -k$__x -k$__n:1,2 -k$__nx:1,2 -k$__xn:1,2 " +
    "--force-po";

function po_extract_messages_js() {
    const globs = [
        "koha-tmpl/intranet-tmpl/prog/js/vue/**/*.vue",
        "koha-tmpl/intranet-tmpl/prog/js/**/*.js",
        "koha-tmpl/opac-tmpl/bootstrap/js/**/*.js",
    ];

    return src(globs, { read: false, nocase: true })
        .pipe(
            xgettext(
                `xgettext -L JavaScript ${xgettext_options}`,
                "Koha-messages-js.pot"
            )
        )
        .pipe(dest("misc/translator"));
}

function po_extract_messages() {
    const perlStream = src(["**/*.pl", "**/*.pm"], {
        read: false,
        nocase: true,
    }).pipe(xgettext(`xgettext -L Perl ${xgettext_options}`, "Koha-perl.pot"));

    const ttStream = src(
        [
            "koha-tmpl/intranet-tmpl/prog/en/**/*.tt",
            "koha-tmpl/intranet-tmpl/prog/en/**/*.inc",
            "koha-tmpl/opac-tmpl/bootstrap/en/**/*.tt",
            "koha-tmpl/opac-tmpl/bootstrap/en/**/*.inc",
        ],
        { read: false, nocase: true }
    ).pipe(
        xgettext(
            "misc/translator/xgettext-tt2 --from-code=UTF-8",
            "Koha-tt.pot"
        )
    );

    const headers = {
        "Project-Id-Version": "Koha",
        "Content-Type": "text/plain; charset=UTF-8",
    };

    return merge(perlStream, ttStream)
        .pipe(concatPo("Koha-messages.pot", { headers }))
        .pipe(dest("misc/translator"));
}

function po_extract_pref() {
    return src(
        "koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/*.pref",
        { read: false }
    )
        .pipe(xgettext("misc/translator/xgettext-pref", "Koha-pref.pot"))
        .pipe(dest("misc/translator"));
}

function po_extract_installer() {
    const globs = [
        "installer/data/mysql/en/mandatory/*.yml",
        "installer/data/mysql/en/optional/*.yml",
    ];

    return src(globs, { read: false, nocase: true })
        .pipe(
            xgettext("misc/translator/xgettext-installer", "Koha-installer.pot")
        )
        .pipe(dest("misc/translator"));
}

function po_extract_installer_marc(type) {
    const globs = `installer/data/mysql/en/marcflavour/${type}/**/*.yml`;

    return src(globs, { read: false, nocase: true })
        .pipe(
            xgettext(
                "misc/translator/xgettext-installer",
                `Koha-installer-${type}.pot`
            )
        )
        .pipe(dest("misc/translator"));
}

function po_extract_installer_marc21() {
    return po_extract_installer_marc("MARC21");
}

function po_extract_installer_unimarc() {
    return po_extract_installer_marc("UNIMARC");
}

function po_create_type(type) {
    const access = util.promisify(fs.access);
    const exec = util.promisify(child_process.exec);

    const pot = `misc/translator/Koha-${type}.pot`;

    // Generate .pot only if it doesn't exist or --force-extract is given
    const extract = () => stream.finished(poTasks[type].extract());
    const p =
        args["generate-pot"] === "always"
            ? extract()
            : args["generate-pot"] === "auto"
              ? access(pot).catch(extract)
              : args["generate-pot"] === "never"
                ? Promise.resolve(0)
                : Promise.reject(
                      new Error(
                          "Invalid value for option --generate-pot: " +
                              args["generate-pot"]
                      )
                  );

    return p.then(function () {
        const languages = getLanguages();
        const promises = [];
        for (const language of languages) {
            const locale = language
                .split("-")
                .filter(s => s.length !== 4)
                .join("_");
            const po = `misc/translator/po/${language}-${type}.po`;

            const promise = access(po).catch(() =>
                exec(`msginit -o ${po} -i ${pot} -l ${locale} --no-translator`)
            );
            promises.push(promise);
        }

        return Promise.all(promises);
    });
}

function po_create_marc_marc21() {
    return po_create_type("marc-MARC21");
}
function po_create_marc_unimarc() {
    return po_create_type("marc-UNIMARC");
}
function po_create_staff() {
    return po_create_type("staff-prog");
}
function po_create_opac() {
    return po_create_type("opac-bootstrap");
}
function po_create_pref() {
    return po_create_type("pref");
}
function po_create_messages() {
    return po_create_type("messages");
}
function po_create_messages_js() {
    return po_create_type("messages-js");
}
function po_create_installer() {
    return po_create_type("installer");
}
function po_create_installer_marc21() {
    return po_create_type("installer-MARC21");
}
function po_create_installer_unimarc() {
    return po_create_type("installer-UNIMARC");
}

function po_update_type(type) {
    const access = util.promisify(fs.access);
    const exec = util.promisify(child_process.exec);

    const pot = `misc/translator/Koha-${type}.pot`;

    // Generate .pot only if it doesn't exist or --force-extract is given
    const extract = () => stream.finished(poTasks[type].extract());
    const p =
        args["generate-pot"] === "always"
            ? extract()
            : args["generate-pot"] === "auto"
              ? access(pot).catch(extract)
              : args["generate-pot"] === "never"
                ? Promise.resolve(0)
                : Promise.reject(
                      new Error(
                          "Invalid value for option --generate-pot: " +
                              args["generate-pot"]
                      )
                  );

    return p.then(function () {
        const languages = getLanguages();
        const promises = [];
        for (const language of languages) {
            const po = `misc/translator/po/${language}-${type}.po`;
            promises.push(
                exec(
                    `msgmerge --backup=off --no-wrap --quiet -F --update ${po} ${pot}`
                )
            );
        }

        return Promise.all(promises);
    });
}

function po_update_marc_marc21() {
    return po_update_type("marc-MARC21");
}
function po_update_marc_unimarc() {
    return po_update_type("marc-UNIMARC");
}
function po_update_staff() {
    return po_update_type("staff-prog");
}
function po_update_opac() {
    return po_update_type("opac-bootstrap");
}
function po_update_pref() {
    return po_update_type("pref");
}
function po_update_messages() {
    return po_update_type("messages");
}
function po_update_messages_js() {
    return po_update_type("messages-js");
}
function po_update_installer() {
    return po_update_type("installer");
}
function po_update_installer_marc21() {
    return po_update_type("installer-MARC21");
}
function po_update_installer_unimarc() {
    return po_update_type("installer-UNIMARC");
}

/**
 * Gulp plugin that executes xgettext-like command `cmd` on all files given as
 * input, and then outputs the result as a POT file named `filename`.
 * `cmd` should accept -o and -f options
 */
function xgettext(cmd, filename) {
    const filenames = [];

    function transform(file, encoding, callback) {
        filenames.push(path.relative(file.cwd, file.path));
        callback();
    }

    function flush(callback) {
        fs.mkdtemp(path.join(os.tmpdir(), "koha-"), (err, folder) => {
            const outputFilename = path.join(folder, filename);
            const filesFilename = path.join(folder, "files");
            fs.writeFile(filesFilename, filenames.join(os.EOL), err => {
                if (err) return callback(err);

                const command = `${cmd} -o ${outputFilename} -f ${filesFilename}`;
                child_process.exec(command, err => {
                    if (err) return callback(err);

                    fs.readFile(outputFilename, (err, data) => {
                        if (err) return callback(err);

                        const file = new Vinyl();
                        file.path = path.join(file.base, filename);
                        file.contents = data;
                        callback(null, file);
                        fs.rmSync(folder, { recursive: true });
                    });
                });
            });
        });
    }

    return through2.obj(transform, flush);
}

/**
 * Return languages selected for PO-related tasks
 *
 * This can be either languages given on command-line with --lang option, or
 * all the languages found in misc/translator/po otherwise
 */
function getLanguages() {
    if (Array.isArray(args.lang)) {
        return args.lang;
    }

    if (args.lang) {
        return [args.lang];
    }

    const filenames = fs
        .readdirSync("misc/translator/po/")
        .filter(filename => filename.endsWith("-installer.po"))
        .filter(filename => !filename.startsWith("."));

    const re = new RegExp("-installer.po");
    languages = filenames.map(filename => filename.replace(re, ""));

    return Array.from(new Set(languages));
}

exports.build = function (next) {
    build();
    next();
};
exports.css = function (next) {
    css();
    next();
};
exports.opac_css = opac_css;
exports.staff_css = staff_css;
exports.watch = function () {
    watch(OPAC_CSS_BASE + "/src/**/*.scss", series("opac_css"));
    watch(STAFF_CSS_BASE + "/src/**/*.scss", series("staff_css"));
};

if (args["_"][0].match("po:") && !fs.existsSync("misc/translator/po")) {
    console.log(
        "misc/translator/po does not exist. You should clone koha-l10n there. See https://wiki.koha-community.org/wiki/Translation_files for more details."
    );
    process.exit(1);
}

exports["po:create"] = parallel(...poTypes.map(type => poTasks[type].create));
exports["po:update"] = parallel(...poTypes.map(type => poTasks[type].update));
exports["po:extract"] = parallel(...poTypes.map(type => poTasks[type].extract));
