text: // Copyright (c) 2018, The UniBitX Developers
  //
  // Please see the included LICENSE file for more information.
  #include "WalletServiceConfiguration.h"
  #include <cxxopts.hpp>
  #include <string>
  
  #include <rapidjson/document.h>
  #include <rapidjson/istreamwrapper.h>
  #include <rapidjson/ostreamwrapper.h>
  #include "rapidjson/stringbuffer.h"
  #include <rapidjson/writer.h>
  #include <rapidjson/prettywriter.h>
  #include <fstream>
  
  #include <config/CliHeader.h>
  #include <config/CryptoNoteConfig.h>
  #include <Logging/ILogger.h>
  
  
  using namespace rapidjson;
  
  namespace PaymentService {
  
    void handleSettings(int argc, char* argv[], WalletServiceConfiguration& config)
    {
      cxxopts::Options options(argv[0], CryptoNote::getProjectCLIHeader());
  
      options.add_options("Core")
        ("h,help", "Display this help message", cxxopts::value<bool>()->implicit_value("true"))
        ("v,version", "Output software version information", cxxopts::value<bool>()->default_value("false")->implicit_value("true"));
  
      options.add_options("Daemon")
        ("daemon-address", "The daemon host to use for node operations",cxxopts::value<std::string>()->default_value(config.daemonAddress), "<ip>")
        ("daemon-port", "The daemon RPC port to use for node operations", cxxopts::value<int>()->default_value(std::to_string(config.daemonPort)), "<port>");
  
      options.add_options("Service")
        ("c,config", "Specify the configuration <file> to use instead of CLI arguments", cxxopts::value<std::string>(), "<file>")
        ("dump-config", "Prints the current configuration to the screen", cxxopts::value<bool>()->default_value("false")->implicit_value("true"))
        ("l,log-file", "Specify log <file> location", cxxopts::value<std::string>()->default_value(config.logFile), "<file>")
        ("log-level", "Specify log level", cxxopts::value<int>()->default_value(std::to_string(config.logLevel)), "#")
        ("server-root", "The service will use this <path> as the working directory", cxxopts::value<std::string>(), "<path>")
        ("save-config", "Save the configuration to the specified <file>", cxxopts::value<std::string>(), "<file>")
        ("init-timeout", "Amount of time in seconds to wait for initial connection", cxxopts::value<int>()->default_value("10"), "<seconds>");
  
      options.add_options("Wallet")
        ("address", "Print the wallet addresses and then exit", cxxopts::value<bool>()->default_value("false")->implicit_value("true"))
        ("w,container-file", "Wallet container <file>", cxxopts::value<std::string>(), "<file>")
        ("p,container-password", "Wallet container <password>", cxxopts::value<std::string>(), "<password>")
        ("g,generate-container", "Generate a new wallet container", cxxopts::value<bool>()->default_value("false")->implicit_value("true"))
        ("view-key", "Generate a wallet container with this secret view <key>", cxxopts::value<std::string>(), "<key>")
        ("spend-key", "Generate a wallet container with this secret spend <key>", cxxopts::value<std::string>(), "<key>")
        ("mnemonic-seed", "Generate a wallet container with this Mnemonic <seed>", cxxopts::value<std::string>(), "<seed>")
        ("scan-height", "Start scanning for transactions from this Blockchain height", cxxopts::value<uint64_t>()->default_value("0"), "#")
        ("SYNC_FROM_ZERO", "Force the wallet to sync from 0", cxxopts::value<bool>()->default_value("false")->implicit_value("true"));
  
      options.add_options("Network")
        ("bind-address", "Interface IP address for the RPC service", cxxopts::value<std::string>()->default_value(config.bindAddress), "<ip>")
        ("bind-port", "TCP port for the RPC service", cxxopts::value<int>()->default_value(std::to_string(config.bindPort)), "<port>");
  
      options.add_options("RPC")
        ("enable-cors", "Adds header 'Access-Control-Allow-Origin' to the RPC responses. Uses the value specified as the domain. Use * for all.",
          cxxopts::value<std::string>(), "<domain>")
        ("rpc-legacy-security", "Enable legacy mode (no password for RPC). WARNING: INSECURE. USE ONLY AS A LAST RESORT.",
          cxxopts::value<bool>()->default_value("false")->implicit_value("true"))
        ("rpc-password", "Specify the <password> to access the RPC server.", cxxopts::value<std::string>(), "<password>");
  
  #ifdef WIN32
      options.add_options("Windows Only")
        ("daemonize", "Run the service as a daemon", cxxopts::value<bool>()->default_value("false")->implicit_value("true"))
        ("register-service", "Registers this program as a Windows service",cxxopts::value<bool>()->default_value("false")->implicit_value("true"))
        ("unregister-service", "Unregisters this program from being a Windows service", cxxopts::value<bool>()->default_value("false")->implicit_value("true"));
  #endif
  
      try
      {
        auto cli = options.parse(argc, argv);
  
        if (cli.count("help") > 0)
        {
          config.help = cli["help"].as<bool>();
        }
  
        if (cli.count("version") > 0)
        {
          config.version = cli["version"].as<bool>();
        }
  
        if (cli.count("config") > 0)
        {
          config.configFile = cli["config"].as<std::string>();
        }
  
        if (cli.count("save-config") > 0)
        {
          config.outputFile = cli["save-config"].as<std::string>();
        }
  
        if (cli.count("dump-config") > 0)
        {
          config.dumpConfig = cli["dump-config"].as<bool>();
        }
  
        if (cli.count("daemon-address") > 0)
        {
          config.daemonAddress = cli["daemon-address"].as<std::string>();
        }
  
        if (cli.count("daemon-port") > 0)
        {
          config.daemonPort = cli["daemon-port"].as<int>();
        }
  
        if (cli.count("init-timeout") > 0)
        {
          config.initTimeout = cli["init-timeout"].as<int>();
        }
  
        if (cli.count("log-file") > 0)
        {
          config.logFile = cli["log-file"].as<std::string>();
        }
  
        if (cli.count("log-level") > 0)
        {
          config.logLevel = cli["log-level"].as<int>();
        }
  
        if (cli.count("container-file") > 0)
        {
          config.containerFile = cli["container-file"].as<std::string>();
        }
  
        if (cli.count("container-password") > 0)
        {
          config.containerPassword = cli["container-password"].as<std::string>();
        }
  
        if (cli.count("bind-address") > 0)
        {
          config.bindAddress = cli["bind-address"].as<std::string>();
        }
  
        if (cli.count("bind-port") > 0)
        {
          config.bindPort = cli["bind-port"].as<int>();
        }
  
        if (cli.count("enable-cors") > 0)
        {
          config.corsHeader = cli["enable-cors"].as<std::string>();
        }
  
        if (cli.count("rpc-legacy-security") > 0)
        {
          config.legacySecurity = cli["rpc-legacy-security"].as<bool>();
        }
  
        if (cli.count("rpc-password") > 0)
        {
          config.rpcPassword = cli["rpc-password"].as<std::string>();
        }
  
        if (cli.count("server-root") > 0)
        {
          config.serverRoot = cli["server-root"].as<std::string>();
        }
  
        if (cli.count("view-key") > 0)
        {
          config.secretViewKey = cli["view-key"].as<std::string>();
        }
  
        if (cli.count("spend-key") > 0)
        {
          config.secretSpendKey = cli["spend-key"].as<std::string>();
        }
  
        if (cli.count("mnemonic-seed") > 0)
        {
          config.mnemonicSeed = cli["mnemonic-seed"].as<std::string>();
        }
  
        if (cli.count("generate-container") > 0)
        {
          config.generateNewContainer = cli["generate-container"].as<bool>();
        }
  
        if (cli.count("daemonize") > 0)
        {
          config.daemonize = cli["daemonize"].as<bool>();
        }
  
        if (cli.count("register-service") > 0)
        {
          config.registerService = cli["register-service"].as<bool>();
        }
  
        if (cli.count("unregister-service") > 0)
        {
          config.unregisterService = cli["unregister-service"].as<bool>();
        }
  
        if (cli.count("address") > 0)
        {
          config.printAddresses = cli["address"].as<bool>();
        }
  
        if (cli.count("SYNC_FROM_ZERO") > 0)
        {
          config.syncFromZero = cli["SYNC_FROM_ZERO"].as<bool>();
        }
  
        if (cli.count("scan-height") > 0)
        {
          config.scanHeight = cli["scan-height"].as<uint64_t>();
        }
  
        if (config.help) // Do we want to display the help message?
        {
          std::cout << options.help({}) << std::endl;
          exit(0);
        }
        else if (config.version) // Do we want to display the software version?
        {
          std::cout << CryptoNote::getProjectCLIHeader() << std::endl;
          exit(0);
        }
      }
      catch (const cxxopts::OptionException& e)
      {
        std::cout << "Error: Unable to parse command line argument options: " << e.what() << std::endl << std::endl
          << options.help({}) << std::endl;
        exit(1);
      }
    }
  
    bool updateConfigFormat(const std::string configFile, WalletServiceConfiguration& config)
    {
      std::ifstream data(configFile);
  
      if (!data.good())
      {
        throw std::runtime_error("The --config-file you specified does not exist, please check the filename and try again.");
      }
      // find key=value pair, respect whitespace before/after "="
      // g0: full match, g1: match key, g2: match value
      static const std::regex cfgItem{R"x(\s*(\S[^ \t=]*)\s*=\s*((\s?\S+)+)\s*$)x"};
  
      // comments, first non space starts with # or ;
      static const std::regex cfgComment{R"x(\s*[;#])x"};
      std::smatch item;
      std::string cfgKey;
      std::string cfgValue;
      bool updated = false;
  
      for (std::string line; std::getline(data, line);)
      {
        if (line.empty() || std::regex_match(line, item, cfgComment))
        {
          continue;
        }
  
        if (std::regex_match(line, item, cfgItem))
        {
          if (item.size() != 4)
          {
            continue;
          }
  
          cfgKey = item[1].str();
          cfgValue = item[2].str();
  
          if (cfgKey.compare("daemon-address") == 0)
          {
            config.daemonAddress = cfgValue;
            updated = true;
          }
          else if (cfgKey.compare("daemon-port")  == 0)
          {
            try
            {
              config.daemonPort = std::stoi(cfgValue);
              updated = true;
            }
            catch(std::exception& e)
            {
              throw std::runtime_error(std::string(e.what()) + " - Invalid value for " + cfgKey );
            }
          }
          else if (cfgKey.compare("init-timeout")  == 0)
          {
            try
            {
              config.initTimeout = std::stoi(cfgValue);
              updated = true;
            }
            catch(std::exception& e)
            {
              throw std::runtime_error(std::string(e.what()) + " - Invalid value for " + cfgKey );
            }
          }
          else if (cfgKey.compare("log-file")  == 0)
          {
            config.logFile = cfgValue;
            updated = true;
          }
          else if (cfgKey.compare("log-level")  == 0)
          {
            try
            {
              config.logLevel = std::stoi(cfgValue);
              updated = true;
            }
            catch(std::exception& e)
            {
              throw std::runtime_error(std::string(e.what()) + " - Invalid value for " + cfgKey );
            }
          }
          else if (cfgKey.compare("container-file")  == 0)
          {
            config.containerFile = cfgValue;
            updated = true;
          }
          else if (cfgKey.compare("container-password")  == 0)
          {
            config.containerPassword = cfgValue;
            updated = true;
          }
          else if (cfgKey.compare("bind-address")  == 0)
          {
            config.bindAddress = cfgValue;
            updated = true;
          }
          else if (cfgKey.compare("bind-port")  == 0)
          {
            try
            {
              config.bindPort = std::stoi(cfgValue);
              updated = true;
            }
            catch(std::exception& e)
            {
              throw std::runtime_error(std::string(e.what()) + " - Invalid value for " + cfgKey );
            }
          }
          else if (cfgKey.compare("enable-cors")  == 0)
          {
            config.corsHeader = cfgValue;
            updated = true;
          }
          else if (cfgKey.compare("rpc-legacy-security")  == 0)
          {
            config.legacySecurity = cfgValue.at(0) == '1' ? true : false;
            updated = true;
          }
          else  if (cfgKey.compare("rpc-password") == 0)
          {
            config.rpcPassword = cfgValue;
            updated = true;
          }
          else if (cfgKey.compare("server-root")  == 0)
          {
            config.serverRoot = cfgValue;
            updated = true;
          }
          else
          {
            for (auto c: cfgKey)
            {
              if (static_cast<unsigned char>(c) > 127)
              {
                  throw std::runtime_error(std::string("Bad/invalid config file"));
              }
            }
            throw std::runtime_error("Unknown option: " + cfgKey);
          }
        }
      }
  
      if (!updated)
      {
        return false;
      }
  
      try
      {
        std::ifstream  orig(configFile, std::ios::binary);
        std::ofstream  backup(configFile + ".ini.bak",   std::ios::binary);
        backup << orig.rdbuf();
      }
      catch(std::exception& e)
      {
        // pass
      }
      return updated;
    }
  
    void handleSettings(const std::string configFile, WalletServiceConfiguration& config)
    {
      std::ifstream data(configFile);
  
      if (!data.good())
      {
        throw std::runtime_error("The --config-file you specified does not exist, please check the filename and try again.");
      }
  
      IStreamWrapper isw(data);
      Document j;
      j.ParseStream(isw);
  
      if (j.HasMember("daemon-address"))
      {
        config.daemonAddress = j["daemon-address"].GetString();
      }
  
      if (j.HasMember("daemon-port"))
      {
        config.daemonPort = j["daemon-port"].GetInt();
      }
  
      if (j.HasMember("init-timeout"))
      {
        config.initTimeout = j["init-timeout"].GetInt();
      }
  
      if (j.HasMember("log-file"))
      {
        config.logFile = j["log-file"].GetString();
      }
  
      if (j.HasMember("log-level"))
      {
        config.logLevel = j["log-level"].GetInt();
      }
  
      if (j.HasMember("container-file"))
      {
        config.containerFile = j["container-file"].GetString();
      }
  
      if (j.HasMember("container-password"))
      {
        config.containerPassword = j["container-password"].GetString();
      }
  
      if (j.HasMember("bind-address"))
      {
        config.bindAddress = j["bind-address"].GetString();
      }
  
      if (j.HasMember("bind-port"))
      {
        config.bindPort = j["bind-port"].GetInt();
      }
  
      if (j.HasMember("enable-cors"))
      {
        config.corsHeader = j["enable-cors"].GetString();
      }
  
      if (j.HasMember("rpc-legacy-security"))
      {
        config.legacySecurity = j["rpc-legacy-security"].GetBool();
      }
  
      if (j.HasMember("rpc-password"))
      {
        config.rpcPassword = j["rpc-password"].GetString();
      }
  
      if (j.HasMember("server-root"))
      {
        config.serverRoot = j["server-root"].GetString();
      }
    }
  
    Document asJSON(const WalletServiceConfiguration& config)
    {
      Document j;
      j.SetObject();
      Document::AllocatorType& alloc = j.GetAllocator();
  
      j.AddMember("daemon-address", config.daemonAddress, alloc);
      j.AddMember("daemon-port", config.daemonPort, alloc);
      j.AddMember("log-file", config.logFile, alloc);
      j.AddMember("log-level", config.logLevel, alloc);
      j.AddMember("init-timeout", config.initTimeout, alloc);
      j.AddMember("container-file", config.containerFile, alloc);
      j.AddMember("container-password", config.containerPassword, alloc);
      j.AddMember("bind-address", config.bindAddress, alloc);
      j.AddMember("bind-port", config.bindPort, alloc);
      j.AddMember("enable-cors", config.corsHeader, alloc);
      j.AddMember("rpc-legacy-security", config.legacySecurity, alloc);
      j.AddMember("rpc-password", config.rpcPassword, alloc);
      j.AddMember("server-root", config.serverRoot, alloc);
  
      return j;
    }
  
    std::string asString(const WalletServiceConfiguration& config)
    {
      StringBuffer strbuf;
      PrettyWriter<StringBuffer> writer(strbuf);
      Document j = asJSON(config);
      j.Accept(writer);
      return strbuf.GetString();
    }
  
    void asFile(const WalletServiceConfiguration& config, const std::string& filename)
    {
      Document j = asJSON(config);
      std::ofstream data(filename);
      OStreamWrapper osw(data);
      PrettyWriter<OStreamWrapper> writer(osw);
      j.Accept(writer);
    }
  }
  