var libPath         = require('path');
var libFS           = require('fs');
var libChildProcess = require('child_process');
var libMySQL        = require('mysql2');
var libReadline     = require('readline');

const gReadline = libReadline.createInterface({
    input: process.stdin,
    output: process.stdout
});


var gCommand              = process.argv[2];
var gExecutableDirectory  = libPath.dirname(libFS.realpathSync(__filename));
var gFSTemplatesDirectory = libPath.join(libPath.join(gExecutableDirectory, "mngr"), "templates");
var gDirectoryFromProject = "";
var gDirectoryFromPackage = "";
var gPackage              = "";
var gLang                 = "en";
process.env.NODE_PATH     = gExecutableDirectory;
require("module").Module._initPaths();
var fcf                   = require('fcf');



var dir = process.cwd();
while(true) {
  var projectFile = libPath.join(dir, "__fcfproject");
  if (libFS.existsSync(projectFile))
    break;
  gDirectoryFromProject = libPath.join(libPath.basename(dir), gDirectoryFromProject);
  if (dir == "/" || (dir.indexOf(":\\") != 0 && dir.length == 3))
    break;
  dir = libPath.dirname(dir);
}

var dir = process.cwd();
while(true) {
  var projectFile = libPath.join(dir, "__fcfproject");
  if (libFS.existsSync(projectFile)){
    gDirectoryFromPackage = "";
    break;
  }

  var files = libFS.readdirSync(dir);
  for(var i = 0; i < files.length; ++i) {
    var ext = libPath.extname(files[i]);
    if (ext === ".package" || ext == ".theme"){
      gPackage = files[i].split('.').slice(0, -1).join('.');
      break;
    }
  }

  if (gPackage != ""){
    break;
  }

  gDirectoryFromPackage = libPath.join(libPath.basename(dir), gDirectoryFromPackage);
  if (dir == "/" || (dir.indexOf(":\\") != 0 && dir.length == 3)) {
    gDirectoryFromPackage = "";
    break;
  }
  dir = libPath.dirname(dir);
}

if (process.platform == "linux"){
  var localeOutput = libChildProcess.execSync("locale").toString();
  var reg   = new RegExp("LANG=([a-z]{2})", "i");
  var match = localeOutput.match(reg);
  if (match !== null)
    gLang = match[1].toLowerCase();
}

var gTranslate = {
  ru: {
    "The template name for created files is not specified":
      "Не указано имя шаблона для создаваемых файлов",
    "Using: fcfmng COMMAND [COMMAND_ARGUMENTS]":
      "Использование: fcfmng КОМАНДА [АРГУМЕНТЫ_КОМАНДЫ]",
    "Performs auxiliary actions for creating and managing the FCF application":
      "Выполняет вспомогательные действия по созданию и управлению приложением FCF",
    "Commands:":
      "Команды:",
    "help|h                           Display this message":
      "help|h                           Выводит инструкцию использования",
    "create|c NAME [ARGUMENTS]        Creates files using the template specified in the NAME parameter":
      "create|c ИМЯ [АРГУМЕНТЫ]         Создает файлы по шаблону указанному в параметре ИМЯ",
    "show-templates|s                 Displays a list of available file templates with a description":
      "show-templates|s                 Выводит перечень доступных шаблонов файлов с описанием",
    "create-db JSAPPSCRIPT [OPTIONS]  Creates a database and user":
      "create-db JSAPPSCRIPT [OPTIONS]  Создает базу данных и пользователя",
    "                                 JSAPPSCRIPT - Javascript application file":
      "                                 JSAPPSCRIPT - Файл приложения Javascript ",
    "                                 OPTIONS - Advenced options:":
      "                                 OPTIONS - Расширенные опции:",
    "                                   --connections CONNECTIONS a string with information about connections":
      "                                   --connections CONNECTIONS строка с информацией о соединениях",
    "                                                             If the connection parameters are not set,":
      "                                                             Если параметры соединения не установлены,",
    "                                                             then a application settings are used":
      "                                                             то исполузуются настройки приложения",
    "                                   Example:":
      "                                   Пример:",


    "Getting application settings...":
      "Получение настроек приложения...",
    "Getting information about databases...":
      "Получение информации о базах данных...",
    "Creating {1} database on the {2} host":
      "Создание дазы данных {1} на хосте {2}",
    "Update users...":
      "Обновление пользователей...",
    "Creating user {1}@{2}":
      "Создание пользователя {1}@{2}",
    "Configuring privileges for {1}@{2}":
      "Настройка прав доступа для {1}@{2}",
    "Update is completed":
      "Обновление завершено",
    "Application failed:":
      "Ошибка приложения:",
    "Please enter user for ":
      "Пожалуйста введите пользователя для ",
    "Please enter password for ":
      "Пожалуйста введите пароль для ",    
    "Error: Invalid '{1}' database type for '{2}'@'{3}'":
      "Ошибка: Недопустимый тип базы данных '{1}' для '{2}'@'{3}'",


  }
}
for(var lang in gTranslate)
  for(var str in gTranslate[lang])
    gTranslate[lang][str.toLowerCase()] = gTranslate[lang][str];
function _t(a_str){
  var key = a_str.toLowerCase();
  return gLang in gTranslate && key in gTranslate[gLang]
    ? gTranslate[gLang][key]
    : a_str;
}

function _f(a_str) {
  for (var k = 1; k < arguments.length; ++k)
    a_str = a_str.replace("{" + k + "}", arguments[k]);
  return a_str;
}


function asyncQuery(a_connection, a_sql, a_act, a_cb){
  a_connection.query(a_sql, function (a_err, a_result) {
    if (a_err){
      a_connection.end();
      a_act.error(a_err);
      return;
    }
    a_cb(a_result);
  });
}

function connectMySQL(a_connection, a_act, a_cb){
  a_connection.connect((a_err) =>{
    if (a_err){
      a_connection.end();
      a_act.error(a_err);
      return;
    }
    a_cb();
  });
}



function commandHelp(){
    console.log(_t("Using: fcfmng COMMAND [COMMAND_ARGUMENTS]"));
    console.log(_t("Performs auxiliary actions for creating and managing the FCF application"));
    console.log(_t(""));
    console.log(_t("Commands:"));
    console.log(_t("help|h                           Display this message"));
    console.log(_t("create|c NAME [ARGUMENTS]        Creates files using the template specified in the NAME parameter"));
    console.log(_t("show-templates|s                 Displays a list of available file templates with a description"));
    console.log(_t("create-db JSAPPSCRIPT            Creates a database and user"));
    console.log(_t("                                 JSAPPSCRIPT - Javascript application file"));
    // console.log(_t("                                 OPTIONS - Advenced options:"));
    // console.log(_t("                                   --connections CONNECTIONS a string with information about connections"));
    // console.log(_t("                                                             If the connection parameters are not set,"));
    // console.log(_t("                                                             then a application settings are used"));
    // console.log(_t("                                   Example:"));
    // console.log(_t("                                   >$ fcfmngr create-db app.js --connections host1:user1:pass1,host2:user2:pass2"));
    console.log(_t(""));
}

function commandShowTemplates(){
  var items = libFS.readdirSync(gFSTemplatesDirectory);
  items.forEach(item => {
    console.log("Files template: " + item);
    var infoFilePath = libPath.join(libPath.join(gFSTemplatesDirectory, item), "info");
    if (libFS.existsSync(infoFilePath)){
      console.log("Info: ");
      var info = libFS.readFileSync(infoFilePath, {encoding: 'utf8'})
      console.log(info);
    }
    console.log("");

  });
}

function commandCreateDB(a_jsScript, a_connectionsString){
  var auth = {};
  a_connectionsString.split(",").forEach((str) => {
    var host;
    str.split(":").forEach((value, key) => {
      if (key == 0)       { host = value; auth[host] = {host: host}; }
      else if (key == 1)  { auth[host].user = value; }
      else if (key == 2)  { auth[host].pass = value; }
    });
  });

  console.log(_t("Getting application settings..."));
  var fcfnodePath   = libPath.join(gExecutableDirectory, "fcfnode");
  var settings      = libChildProcess.execSync(fcfnodePath + " \""+ a_jsScript + "\" --get-settings").toString();
  var jsonSettings  = fcf.trim(settings.substring(settings.indexOf("FCF-SETTINGS:") + 13, settings.indexOf("FCF-ENDSETTINGS")));
  var settings      = JSON.parse(jsonSettings);
  var dbConnectionSettings = settings.dataClient && settings.dataClient.connections
                                  ? settings.dataClient.connections
                                  : {};


  var databasesWaitingCreated = {};
  var actions = fcf.actions();


  console.log(_t("Getting information about databases..."));

  actions.each(dbConnectionSettings, (a_cikey, a_ci, a_res, a_act) => {
    if (a_ci.type != "mysql" && a_ci.type != "mariadb") {
      console.error(_f(_t("Error: Invalid '{1}' database type for '{2}'@'{3}'"), a_ci.type,  a_ci.db, a_ci.host))
      process.exit(1);
    }
    a_act.complete();
  });
  

  actions.each(dbConnectionSettings, (a_cikey, a_ci, a_res, a_act) => {
    var ai = a_ci.host in auth ? auth[a_ci.host] : a_ci;

    function clAction(){
      if (a_ci.type == "mysql" || a_ci.type == "mariadb") {
        var connectionInfo = {host: ai.host};
        if (ai.user != "")
          connectionInfo.user = ai.user
        if (ai.pass != "")
          connectionInfo.password = ai.pass

        var connection = libMySQL.createConnection(connectionInfo);
        connectMySQL(connection, a_act, ()=>{
          asyncQuery(connection, "SHOW DATABASES", a_act, (a_result) =>{
            var found = fcf.find(a_result, (k, v)=>{ return v.Database == a_ci.db; }) != undefined;
            if (!found)
              databasesWaitingCreated[a_cikey] = a_ci;
            connection.end();
            a_act.complete();
          });
        });
      } else {
        a_act.complete();
      }
    }

    if (!(a_ci.host in auth)){
      auth[a_ci.host] = fcf.append({}, a_ci);
      var property = {name: "user", message: _t("Please enter user for ") + a_ci.db + "@" + a_ci.host + _t(" database") };
      gReadline.question(_t("Please enter user for ") + a_ci.db + "@" + a_ci.host + _t(" database") + ": ", (a_user)=>{
        auth[a_ci.host].user = a_user;
        var property = {name: "pass", message: _t("Please enter password for ") + a_ci.db + "@" + a_ci.host + _t(" database") };
        gReadline.question(_t("Please enter password for ") + a_ci.db + "@" + a_ci.host + _t(" database") + ": ", (a_pass)=>{
          auth[a_ci.host].pass = a_pass;
          ai = auth[a_ci.host];
          gReadline.close();
          clAction();
        })
      });
    }


  })

  // Creation of databases
  actions.each(()=>{return databasesWaitingCreated}, (a_cikey, a_ci, a_res, a_act) => {
    var ai = a_ci.host in auth ? auth[a_ci.host] : a_ci;
    if (a_ci.type == "mysql" || a_ci.type == "mariadb") {
      var connection = libMySQL.createConnection({ host: ai.host, user: ai.user, password: ai.pass });
      connectMySQL(connection, a_act, ()=>{
        console.log(_f(_t("Creating {1} database on the {2} host"), a_ci.db, a_ci.host));
        var sql = _f("CREATE DATABASE `{1}` CHARACTER SET utf8 COLLATE utf8_unicode_ci", a_ci.db);
        console.log("SQL>",sql);
        asyncQuery(connection, sql, a_act, (a_result) =>{
          connection.end();
          a_act.complete();
        });
      });
    } else {
      a_act.complete();
    }
  })

  // Update users
  .then(()=>{
    console.log(_t("Update users..."));
    return fcf.actions().each(dbConnectionSettings, (a_cikey, a_ci, a_res, a_act) => {
      var ai = a_ci.host in auth ? auth[a_ci.host] : a_ci;
      if (a_ci.type == "mysql" || a_ci.type == "mariadb") {
        var connection = libMySQL.createConnection({ host: ai.host, user: ai.user, password: ai.pass });
        connectMySQL(connection, a_act, ()=>{
          asyncQuery(connection, _f("select user, host from mysql.user"), a_act, (a_result) =>{
            var subActions = fcf.actions();

            if (fcf.find(a_result, (k,v)=>{ return a_ci.user == v.user && a_ci.host == v.host && a_ci.type == "mysql" }) === undefined) {
              subActions.then((a_res, a_subact) => {
                console.log(_f(_t("Creating user {1}@{2}"), a_ci.user, a_ci.host));
                var sql = _f("CREATE USER '{1}'@'{2}' IDENTIFIED BY '{3}'", a_ci.user, a_ci.host, a_ci.pass);
                console.log("SQL>",sql);
                asyncQuery(connection, sql, a_subact, (a_result) =>{
                  a_subact.complete();
                });
              });
            }

            if (fcf.find(a_result, (k,v)=>{ return a_ci.user == v.user && a_ci.host == v.host && a_ci.type == "mysql" }) === undefined ||
                fcf.find(databasesWaitingCreated, (k,v)=>{return a_ci.host == v.host && a_ci.db == v.db && a_ci.type == "mysql"}) !== undefined) {
              subActions.then((a_res, a_subact) => {
                console.log(_f(_t("Configuring privileges for {1}@{2}"), a_ci.user, a_ci.host));
                var sql = _f("GRANT ALL PRIVILEGES ON `{1}`.* TO '{2}'@'{3}';", a_ci.db, a_ci.user, a_ci.host);
                console.log("SQL>",sql);
                asyncQuery(connection, sql, a_subact, (a_result) =>{
                  a_subact.complete();
                });
              });
            }

            subActions.then(() => {
              connection.end();
              a_act.complete();
            });

            subActions.catch((a_error)=>{
              a_act.error(a_error);
            })
          });
        });
      } else {
        a_act.complete();
      }
    });
  })


  .then(()=>{
    console.log(_t("Update is completed"));
  })

  .catch((a_error)=>{
    console.error("");
    console.error(_t("Application failed:"));
    console.error(a_error.message);
    process.exit(1);
  })




}


function commandCreateTemplate(a_templateName, a_templateArgs){
  var templateDirectory = libPath.join(gFSTemplatesDirectory, a_templateName);

  function replaceContext(a_data){
    var reg   = new RegExp("__aFCFGENARG_([0-9A-Z_]+)z__", "g");
    var match = a_data.match(reg);
    if (match !== null){
      var keys = {};
      match.forEach(substr => {
        var key = substr.substr("__aFCFGENARG_".length, substr.length - "__aFCFGENARG_z__".length);
        keys[key] = true;
      });

      for(var k in keys){
        var replaceSubstr = "__aFCFGENARG_" + k + "z__";
        if (!isNaN(Number.parseInt(k))){
          var argValue = a_templateArgs[Number.parseInt(k)-1];
        } else if (k == "DIRECTORY") {
          var argValue = gPackage == "" ? gDirectoryFromProject : gDirectoryFromPackage;
        } else if (k == "DIRECTORY_BEFORE_SLASH") {
          var argValue = gPackage == "" ? gDirectoryFromProject : gDirectoryFromPackage;
          argValue = argValue != "" ? "/" + argValue : argValue;
        } else if (k == "DIRECTORY_SLASH") {
          var argValue = gPackage == "" ? gDirectoryFromProject : gDirectoryFromPackage;
              argValue = argValue == "" ? "" : argValue + "/";
        } else if (k == "PACKAGE") {
          var argValue = gPackage;
        } else if (k == "PACKAGE_COLON") {
          var argValue = gPackage != "" ? gPackage + ":" : "";
        } else if (k == "ROOTOBJECTPATH") {
          var argValue = gPackage != "" ? "packages/" + gPackage : "root";
        } else {
          continue;
        }
        a_data  = a_data.split(replaceSubstr).join(argValue);
      }
    }

    return a_data;
  }

  function copyFile(a_relativePath, a_relativeDir) {
    var realFilePath    = libPath.join(templateDirectory, a_relativePath);
    var dstFilePath     = replaceContext(a_relativePath);
    var dstDir          = replaceContext(a_relativeDir);
    var data            = replaceContext(libFS.readFileSync(realFilePath, {encoding: 'utf8'}));

    if (dstDir !== "" && !libFS.existsSync(dstDir)) {
      libFS.mkdirSync(dstDir, { recursive: true });
    }

    libFS.writeFileSync(dstFilePath, data);
  }

  function readDirectory(a_dir, a_root) {
    var realDir = a_dir == "" ? templateDirectory
                              : libPath.join(templateDirectory, a_dir);
    var items = libFS.readdirSync(realDir);
    items.forEach(element => {
      if (a_root && element=="info")
        return;
      var elementPath = libPath.join(realDir, element);
      var elementStat = libFS.lstatSync(elementPath);
      if (elementStat.isDirectory()){
        readDirectory(libPath.join(a_dir, element));
      } else {
        copyFile(libPath.join(a_dir, element), a_dir);
      }
    });
  }

  readDirectory("", true);
}

try {
  if (gCommand == "create" || gCommand == "--create" || gCommand == "-c" || gCommand == "c"){
    if (!process.argv[3]){
      console.error(_t("The template name for created files is not specified"));
      process.exit(1);
    }
    commandCreateTemplate(process.argv[3], process.argv.slice(4));
  } else if (gCommand == "show-templates" || gCommand == "--show-templates" || gCommand == "-s" || gCommand == "s"){
    commandShowTemplates();
  } else if (gCommand == "create-db" || gCommand == "--create-db"){
    var connectionsArgKey = fcf.inc(fcf.find(process.argv, "--connections"));
    var connections       = fcf.empty(process.argv[connectionsArgKey]) ? "" : process.argv[connectionsArgKey];
    commandCreateDB(process.argv[3], connections);
  } else if (gCommand == "help" || gCommand == "--help" || gCommand == "-h" || gCommand == "h"){
    commandHelp();
  } else {
    commandHelp();
    process.exit(1);
  }
} catch(e){
  console.error(e.message);
  process.exit(1);
}
