const libChildProcess = require("child_process");

fcf.module({
  name:         "fcf:NSystem/nodeManager.js",
  dependencies: ["fcf:NTools/fs.js", "fcf:NSystem/NPackage/tools.js"],
  lazy:         [],
  module: function(toolsFS, toolsPackages){
    var Namespace = fcf.prepareObject(fcf, "NSystem");

    class NodeManager {
      constructor(){
      }

      async updateDependencies(a_dependencies){
        let self = this;

        fcf.log.log("FCF", `Checking NODEJS dependencies...`)

        toolsFS.prepareDirectorySync(":node_modules");

        let npmOutput = await this._execute("npm list --silent", true);
        if (fcf.empty(npmOutput.stdout))
          throw new fcf.Exception("ERROR_EXECUTE_PROCESS", {command: "npm list --silent", error: "Unknown error", stderr: npmOutput.stderr})
        let installPackages = this._npmOutputToPackages(npmOutput.stdout);

        let needInstall = {};
        fcf.each(a_dependencies, (a_package, a_info)=>{
          if (!installPackages[a_package]){
            needInstall[a_package] = a_info;
            return;
          }

          if (fcf.empty(a_info.version)){
            return;
          }

          let depReqVersion = toolsPackages.getVersionRequirement(a_info.version);
          let cmpVersions   = toolsPackages.cmpVersion(installPackages[a_package], a_info.version);

          if (depReqVersion == "<=" && (cmpVersions == 1)) {
            needInstall[a_package] = a_info;
            return;
          }
          if (depReqVersion == "<" && (cmpVersions == 1 || cmpVersions == 0)) {
            needInstall[a_package] = a_info;
            return;
          }
          if (depReqVersion == ">=" && (cmpVersions == -1)) {
            needInstall[a_package] = a_info;
            return;
          }
          if (depReqVersion == ">" && (cmpVersions == -1 || cmpVersions == 0)) {
            needInstall[a_package] = a_info;
            return;
          }
          if (depReqVersion == "=" && (cmpVersions != 0)) {
            needInstall[a_package] = a_info;
            return;
          }
        });

        await fcf.asyncEach(needInstall, async (a_package, a_info)=>{
          try {
            await self._installPackage(a_package, a_info);
          } catch(e) {
            fcf.log.err("FCF", "");
            fcf.log.err("FCF", "-------------------------------------------------");
            fcf.log.err("FCF", `Failed to install NODEJS ${a_package} package !!!`);
            fcf.log.err("FCF", 'Try running the command "fcfmngr clear-node-modules" and restart the server !!!');
            throw e;
          }
        })

        return !fcf.empty(needInstall);
      }

      async _installPackage(a_package, a_info){
        let version        = a_info.version ? fcf.trim(a_info.version,["=","<", ">"]) : undefined;
        let reqVersion     = toolsPackages.getVersionRequirement(a_info.version);
        let installVersion = undefined;

        fcf.log.log("FCF", `Installing NODEJS ${a_package} package...`)

        if (version) {
          let command         = `npm show "${a_package}" versions`;
          let versions        = [];
          let versionsOutput  = await this._execute(command, true);
          try {
            versions = eval("(" + fcf.trim(versionsOutput.stdout) + ")");
          } catch(e){
            throw new fcf.Exception("ERROR", {error: `Invaid output format for command "${command}". OUTPUT: ${versionsOutput.stdout}. Need sorted JSON!`});
          }

          fcf.each(versions, (a_key, a_version)=>{
            let cmp = toolsPackages.cmpVersion(a_version, version, true);
            if (reqVersion == "=" && cmp == 0){
              installVersion = a_version;
            } else if (reqVersion == "<=" && (cmp == -1 || cmp == 0)){
              installVersion = a_version;
            } else if (reqVersion == "<" && cmp == -1){
              installVersion = a_version;
            } else if (reqVersion == ">=" && (cmp == 1 || cmp == 0)){
              installVersion = a_version;
            } else if (reqVersion == ">" && cmp == 1){
              installVersion = a_version;
            }
          })
        }

        let fullPackName = installVersion ? a_package + "@" + installVersion : a_package;
        let installCommand = `npm --save install "${fullPackName}"`;
        fcf.log.log("FCF", "Execute: ", installCommand);
        let output =  await this._execute(installCommand);
        output.stdout = fcf.trim(output.stdout);
        output.stderr = fcf.trim(output.stderr);
        if (!fcf.empty(output.stdout))
          fcf.log.log("FCF", "Install output: \n", output.stdout);
        if (!fcf.empty(output.stderr))
          fcf.log.log("FCF", "Install output warnings: \n", output.stderr);
      }

      async _execute(a_command, a_hideErrors){
        return fcf.actions()
        .then((a_res, a_act)=>{
          let env = fcf.append({}, process.env, {NODE_PATH: ""});
          libChildProcess.exec(a_command, {env: env}, (a_error, a_stdout, a_stderr)=>{
            if (a_error && !a_hideErrors) {
              a_act.error(new fcf.Exception("ERROR_EXECUTE_PROCESS", {command: a_command, error: a_error, stderr: a_stderr}));
              return;
            }
            a_act.complete({stdout: a_stdout, stderr: a_stderr});
          });
        });
      }

      _npmOutputToPackages(a_str){
        let result  = {};
        let strings = a_str.split("\n");
        fcf.each(strings, (a_key, a_str)=>{
          a_str = fcf.trim(a_str);
          if (fcf.empty(a_str))
            return;
          let match = a_str.match("([^a-z^A-Z^@]*)([@a-zA-Z]*.*)");
          if (fcf.empty(match[2]))
            return;
          let packstr = fcf.trim(match[2]);
          packstr = fcf.findVal(packstr.split(" "), (a_key, a_string)=>{ return a_string.indexOf("@") != -1; });
          if (!packstr)
            return;
          let pos = packstr.lastIndexOf("@");
          if (pos == -1)
            return;
          let packName =  packstr.substring(0, pos);
          let packVersion = packstr.substring(pos+1);
          packVersion = packVersion.match("([^ ]*)")[1];
          result[packstr.substring(0, pos)] = {version: packVersion};
        });
        return result;
      }

    };

    Namespace.nodeManager = new NodeManager();

    return Namespace.nodeManager;
  }
});
