// ng-annotate.js
// MIT licensed, see LICENSE file
// Copyright (c) 2013-2016 Olov Lassus <olov.lassus@gmail.com>

"use strict";

const t0 = Date.now();
const fs = require("fs");
const fmt = require("simple-fmt");
const tryor = require("tryor");
const ngAnnotate = require("./ng-annotate-main");
const version = require("./package.json").version;
const optimist = require("optimist")
    .usage("ng-annotate v" + version + "\n\nUsage: ng-annotate OPTIONS <file>\n\n" +
        "provide - instead of <file> to read from stdin\n" +
        "use -a and -r together to remove and add (rebuild) annotations in one go")
    .options("a", {
        alias: "add",
        boolean: true,
        describe: "add dependency injection annotations where non-existing",
    })
    .options("r", {
        alias: "remove",
        boolean: true,
        describe: "remove all existing dependency injection annotations",
    })
    .options("o", {
        describe: "write output to <file>. output is written to stdout by default",
    })
    .options("sourcemap", {
        boolean: true,
        describe: "generate an inline sourcemap"
    })
    .options("sourceroot", {
        describe: "set the sourceRoot property of the generated sourcemap"
    })
    .options("single_quotes", {
        boolean: true,
        describe: "use single quotes (') instead of double quotes (\")",
    })
    .options("regexp", {
        describe: "detect short form myMod.controller(...) iff myMod matches regexp",
    })
    .options("rename", {
        describe: "rename declarations and annotated references\n" +
            "oldname1 newname1 oldname2 newname2 ...",
        default: ""
    })
    .options("plugin", {
        describe: "use plugin with path (experimental)",
    })
    .options("enable", {
        describe: "enable optional with name",
    })
    .options("list", {
        describe: "list all optional names",
        boolean: true,
    })
    .options("stats", {
        boolean: true,
        describe: "print statistics on stderr (experimental)",
    });

const argv = optimist.argv;

function exit(msg) {
    if (msg) {
        process.stderr.write(msg);
        process.stderr.write("\n");
    }
    process.exit(-1);
}

// special-case for --list
if (argv.list) {
    const list = ngAnnotate("", {list: true}).list;
    if (list.length >= 1) {
        process.stdout.write(list.join("\n") + "\n");
    }
    process.exit(0);
}

// validate options
if (argv._.length !== 1) {
    optimist.showHelp();
    exit("error: no input file provided");
}

if (!argv.add && !argv.remove) {
    optimist.showHelp();
    exit("error: missing option --add and/or --remove");
}

const filename = argv._.shift();

(filename === "-" ? slurpStdin : slurpFile)(runAnnotate);


function slurpStdin(cb) {
    let buf = "";

    process.stdin.setEncoding("utf8");
    process.stdin.on("data", function(d) {
        buf += d;
    });
    process.stdin.on("end", function() {
        cb(null, buf);
    });
    process.stdin.resume();
}

function slurpFile(cb) {
    if (!fs.existsSync(filename)) {
        cb(new Error(fmt('error: file not found {0}', filename)));
    }

    fs.readFile(filename, cb);
}

function runAnnotate(err, src) {
    if (err) {
        exit(err.message);
    }

    src = String(src);

    const config = tryor(function() {
        return JSON.parse(String(fs.readFileSync("ng-annotate-config.json")));
    }, {});

    if (filename !== "-") {
        config.inFile = filename;
    }

    ["add", "remove", "o", "regexp", "rename", "single_quotes", "plugin", "enable", "stats"].forEach(function(opt) {
        if (opt in argv) {
            config[opt] = argv[opt];
        }
    });

    if (argv.sourcemap) {
        config.map = { inline: true, sourceRoot: argv.sourceroot };
        if (filename !== "-") {
            config.map.inFile = filename;
        }
    };

    if (config.enable && !Array.isArray(config.enable)) {
        config.enable = [config.enable];
    }

    if (config.plugin) {
        if (!Array.isArray(config.plugin)) {
            config.plugin = [config.plugin];
        }
        config.plugin = config.plugin.map(function(path) {
            const absPath = tryor(fs.realpathSync.bind(fs, path), null);
            if (!absPath) {
                exit(fmt('error: plugin file not found {0}', path));
            }
            // the require below may throw an exception on parse-error
            try {
                return require(absPath);
            } catch (e) {
                // node will already print file:line and offending line to stderr
                exit(fmt("error: couldn't require(\"{0}\")", absPath));
            }
        });
    }

    const trimmedRename = config.rename && config.rename.trim();
    if (trimmedRename) {
        const flattenRename = trimmedRename.split(" ");
        const renameArray = [];
        for (let i = 0; i < flattenRename.length; i = i + 2) {
            renameArray.push({
                "from": flattenRename[i],
                "to": flattenRename[i + 1],
            });
        }
        config.rename = renameArray;
    } else {
        config.rename = null;
    }

    const run_t0 = Date.now();
    const ret = ngAnnotate(src, config);
    const run_t1 = Date.now();

    if (ret.errors) {
        exit(ret.errors.join("\n"));
    }

    const stats = ret._stats;
    if (config.stats && stats) {
        const t1 = Date.now();
        const all = t1 - t0;
        const run_parser = stats.parser_parse_t1 - stats.parser_parse_t0;
        const all_parser = run_parser + (stats.parser_require_t1 - stats.parser_require_t0);
        const nga_run = (run_t1 - run_t0) - run_parser;
        const nga_init = all - all_parser - nga_run;

        const pct = function(n) {
            return Math.round(100 * n / all);
        }

        process.stderr.write(fmt("[{0} ms] parser: {1}, nga init: {2}, nga run: {3}\n", all, all_parser, nga_init, nga_run));
        process.stderr.write(fmt("[%] parser: {0}, nga init: {1}, nga run: {2}\n", pct(all_parser), pct(nga_init), pct(nga_run)));
    }

    if (ret.src && config.o) {
        try {
            fs.writeFileSync(config.o, ret.src);
        } catch (e) {
            exit(e.message);
        }
    } else if (ret.src) {
        process.stdout.write(ret.src);
    }
}
