//=============================================================================
/**
 *  @file    Locator_Options.cpp
 *
 *  $Id: Locator_Options.cpp 97891 2014-09-12 14:23:55Z mesnier_p $
 *
 *  @author Darrell Brunsch <brunsch@cs.wustl.edu>
 */
//=============================================================================

#include "Locator_Options.h"
#include "ace/Arg_Shifter.h"
#include "orbsvcs/Log_Macros.h"
#include "ace/OS_NS_strings.h"

#if defined (ACE_WIN32)
static const HKEY SERVICE_REG_ROOT = HKEY_LOCAL_MACHINE;
// This string must agree with the one used in Locator_NT_Service.h
static const ACE_TCHAR *SERVICE_REG_PATH =
  ACE_TEXT ("SYSTEM\\CurrentControlSet\\Services\\TAOImR\\Parameters");
#endif /* ACE_WIN32 */

static const int DEFAULT_PING_INTERVAL = 10; // seconds
static const int DEFAULT_START_TIMEOUT = 60; // seconds

Options::Options ()
: repo_mode_ (REPO_NONE)
, erase_repo_ (false)
, debug_ (0)
, multicast_ (false)
, service_ (false)
, ping_external_ (false)
, ping_interval_ (DEFAULT_PING_INTERVAL)
, startup_timeout_ (DEFAULT_START_TIMEOUT)
, readonly_ (false)
, service_command_ (SC_NONE)
, unregister_if_address_reused_ (false)
, imr_type_ (STANDALONE_IMR)
, throw_shutdown_exceptions_  (false)
{
}

int
Options::parse_args (int &argc, ACE_TCHAR *argv[])
{
  ACE_Arg_Shifter shifter (argc, argv);

  // Don't let persistence options co-mingle.
  bool binary_persistence_used = false;
  bool xml_persistence_used = false;
  bool directory_persistence_used = false;

  while (shifter.is_anything_left ())
    {
      if (ACE_OS::strcasecmp (shifter.get_current (),
                              ACE_TEXT ("-c")) == 0)
        {
          shifter.consume_arg ();

          if (!shifter.is_anything_left () || shifter.get_current ()[0] == '-')
            {
              ORBSVCS_ERROR ((LM_ERROR, "Error: -c option needs a command\n"));
              this->print_usage ();
              return -1;
            }

          if (ACE_OS::strcasecmp (shifter.get_current (),
                                  ACE_TEXT ("install")) == 0)
            {
              this->service_command_ = SC_INSTALL;
            }
          else if (ACE_OS::strcasecmp (shifter.get_current (),
                                       ACE_TEXT ("remove")) == 0)
            {
              this->service_command_ = SC_REMOVE;
            }
          else
            {
              ORBSVCS_ERROR ((
                LM_ERROR,
                ACE_TEXT ("Error: Unknown service command : %s\n"),
                shifter.get_current ()));
              this->print_usage ();
              return -1;
            }
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("-d")) == 0)
        {
          shifter.consume_arg ();

          if (!shifter.is_anything_left () || shifter.get_current ()[0] == '-')
            {
              ORBSVCS_ERROR ((LM_ERROR, "Error: -d option needs a debuglevel\n"));
              this->print_usage ();
              return -1;
            }

          this->debug_ = ACE_OS::atoi (shifter.get_current ());
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("-m")) == 0)
        {
          this->multicast_ = true;
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("-o")) == 0)
        {
          shifter.consume_arg ();

          if (!shifter.is_anything_left () || shifter.get_current ()[0] == '-')
            {
              ORBSVCS_ERROR ((LM_ERROR, "Error: -o option needs a filename\n"));
              this->print_usage ();
              return -1;
            }
          this->ior_output_file_ = shifter.get_current();
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("-s")) == 0)
        {
          // Run as a service
          this->service_ = true;
        }
      else if ((ACE_OS::strcasecmp (shifter.get_current (),
                                    ACE_TEXT ("-?")) == 0)
               || (ACE_OS::strcasecmp (shifter.get_current (),
                                       ACE_TEXT ("-h")) == 0))
        {
          this->print_usage ();
          return 1;
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("-l")) == 0)
        {
          this->readonly_ = true;
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("-p")) == 0)
        {
          shifter.consume_arg ();

          if (!shifter.is_anything_left () || shifter.get_current ()[0] == '-')
            {
              ORBSVCS_ERROR ((LM_ERROR, "Error: -p option needs a filename\n"));
              this->print_usage ();
              return -1;
            }

          this->persist_file_name_ = shifter.get_current ();
          this->repo_mode_ = REPO_HEAP_FILE;
          binary_persistence_used = true;
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("-UnregisterIfAddressReused")) == 0)
        {
          this->unregister_if_address_reused_ = true;
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("-r")) == 0)
        {
          this->repo_mode_ = REPO_REGISTRY;
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("-x")) == 0)
        {
          shifter.consume_arg ();

          if (!shifter.is_anything_left () || shifter.get_current ()[0] == '-')
            {
              ORBSVCS_ERROR ((LM_ERROR, "Error: -x option needs a filename\n"));
              this->print_usage ();
              return -1;
            }

          this->persist_file_name_ = shifter.get_current ();
          this->repo_mode_ = REPO_XML_FILE;
          xml_persistence_used = true;
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("--primary")) == 0)
        {
          this->imr_type_ = PRIMARY_IMR;
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("--backup")) == 0)
        {
          this->imr_type_ = BACKUP_IMR;
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("--directory")) == 0)
        {
          shifter.consume_arg ();

          if (!shifter.is_anything_left () || shifter.get_current ()[0] == '-')
            {
              ORBSVCS_ERROR ((LM_ERROR,
                ACE_TEXT ("Error: --directory option needs a filename\n")));
              this->print_usage ();
              return -1;
            }

          this->persist_file_name_ = shifter.get_current ();
          this->repo_mode_ = REPO_SHARED_FILES;

          if (this->persist_file_name_.length() &&
              this->persist_file_name_[this->persist_file_name_.length() - 1] != '/')
            {
              this->persist_file_name_ += '/';
            }
          directory_persistence_used = true;
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("-e")) == 0)
        {
          this->erase_repo_ = true;
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("-t")) == 0)
        {
          shifter.consume_arg ();

          if (!shifter.is_anything_left () || shifter.get_current ()[0] == '-')
            {
              ORBSVCS_ERROR ((LM_ERROR,
                          ACE_TEXT ("Error: -t option needs a value\n")));
              this->print_usage ();
              return -1;
            }
          this->startup_timeout_ =
            ACE_Time_Value (ACE_OS::atoi (shifter.get_current ()));
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("-i")) == 0)
        {
          this->ping_external_ = true;
        }
      else if (ACE_OS::strcasecmp (shifter.get_current (),
                                   ACE_TEXT ("-v")) == 0)
        {
          shifter.consume_arg ();

          if (!shifter.is_anything_left () || shifter.get_current ()[0] == '-')
            {
              ORBSVCS_ERROR ((LM_ERROR,
                          ACE_TEXT ("Error: -v option needs a value\n")));
              this->print_usage ();
              return -1;
            }
          this->ping_interval_ =
            ACE_Time_Value (0, 1000 * ACE_OS::atoi (shifter.get_current ()));
        }
      else
        {
          shifter.ignore_arg ();
          continue;
        }

      shifter.consume_arg ();
    }

  if ((this->imr_type_ == BACKUP_IMR || this->imr_type_ == PRIMARY_IMR) &&
      !directory_persistence_used)
    {
      ORBSVCS_ERROR ((LM_ERROR,
                  "Error: Redundancy is used but the "
                  "--directory option is not passed\n"));
      this->print_usage ();
      return -1;
    }

  if ((binary_persistence_used + directory_persistence_used +
       xml_persistence_used)
      > 1)
    {
      ORBSVCS_ERROR ((LM_ERROR,
                  "Error: Only one persistence option can be used\n"));
      this->print_usage ();
      return -1;
    }

  return 0;
}

int
Options::init (int argc, ACE_TCHAR *argv[])
{
  // Make an initial pass through and grab the arguments that we recognize.
  // This may also run the commands to install or remove the nt service.
  int result = this->parse_args (argc, argv);
  if (result != 0)
    {
      return result;
    }

  for (int i = 0; i < argc; ++i)
    {
      this->cmdline_ += ACE_CString (ACE_TEXT_ALWAYS_CHAR(argv[i])) + ACE_CString (" ");
    }
  return 0;
}

int
Options::init_from_registry (void)
{
  this->load_registry_options ();
  return 0;
}


void
Options::print_usage (void) const
{
  ORBSVCS_ERROR ((LM_ERROR,
    ACE_TEXT ("Usage:\n")
    ACE_TEXT ("\n")
    ACE_TEXT ("ImplRepo_Service [-c cmd] [-d 0|1|2] [-e] [-m] [-o file]\n")
    ACE_TEXT (" [-r|-p file|-x file|--directory dir [--primary|--backup] ]\n")
    ACE_TEXT (" [-s] [-t secs] [-v msecs]\n")
    ACE_TEXT ("  -c command      Runs nt service commands ('install' or 'remove')\n")
    ACE_TEXT ("  -d level        Sets the debug level (default 1)\n")
    ACE_TEXT ("  -e              Erase the persisted repository at startup\n")
    ACE_TEXT ("  -l              Lock the database\n")
    ACE_TEXT ("  -m              Turn on multicast\n")
    ACE_TEXT ("  -o file         Outputs the ImR's IOR to a file\n")
    ACE_TEXT ("  -p file         Use file for storing/loading settings\n")
    ACE_TEXT ("  -x file         Use XML file for storing/loading settings\n")
    ACE_TEXT ("  --directory dir Use individual XML files for storing/loading\n")
    ACE_TEXT ("                  settings in the provided directory\n")
    ACE_TEXT ("  --primary       Replicate the ImplRepo as the primary ImR\n")
    ACE_TEXT ("  --backup        Replicate the ImplRepo as the backup ImR\n")
    ACE_TEXT ("  -r              Use the registry for storing/loading settings\n")
    ACE_TEXT ("  -s              Run as a service\n")
    ACE_TEXT ("  -t secs         Server startup timeout.(Default = 60s)\n")
    ACE_TEXT ("  -v msecs        Server verification interval.(Default = 10000ms)\n")
    ACE_TEXT ("  -i              Ping servers without activators too. (Default=false)\n")
              ));
}

int
Options::save_registry_options ()
{
#if defined (ACE_WIN32)
  HKEY key = 0;
  // Create or open the parameters key
  LONG err = ACE_TEXT_RegCreateKeyEx (SERVICE_REG_ROOT,
                             SERVICE_REG_PATH,
                             0,
                             const_cast<ACE_TCHAR*> (ACE_TEXT ("")), // class
                             REG_OPTION_NON_VOLATILE,
                             KEY_ALL_ACCESS,
                             0,
                             &key,
                             0
                             );
  if (err != ERROR_SUCCESS)
    {
      return -1;
    }
  err = ACE_TEXT_RegSetValueEx (key, ACE_TEXT ("ORBInitOptions"), 0, REG_SZ,
    (LPBYTE) this->cmdline_.c_str (), this->cmdline_.length () + 1);
  ACE_ASSERT (err == ERROR_SUCCESS);

  err = ACE_TEXT_RegSetValueEx (key, ACE_TEXT ("IORFile"), 0, REG_SZ,
    (LPBYTE) ior_output_file_.c_str (), ior_output_file_.length () + 1);
  ACE_ASSERT (err == ERROR_SUCCESS);

  err = ACE_TEXT_RegSetValueEx (key, ACE_TEXT ("DebugLevel"), 0, REG_DWORD,
    (LPBYTE) &debug_ , sizeof (debug_));
  ACE_ASSERT(err == ERROR_SUCCESS);

  err = ACE_TEXT_RegSetValueEx (key, ACE_TEXT ("PersistFile"), 0, REG_SZ,
    (LPBYTE) this->persist_file_name_.c_str (), this->persist_file_name_.length () + 1);
  ACE_ASSERT (err == ERROR_SUCCESS);

  DWORD tmp = this->ping_external_ ? 1 : 0;
  err = ACE_TEXT_RegSetValueEx (key, ACE_TEXT ("PingExternals"), 0, REG_DWORD,
    (LPBYTE) &tmp, sizeof (DWORD));
  ACE_ASSERT (err == ERROR_SUCCESS);

  tmp = this->ping_interval_.msec ();
  err = ACE_TEXT_RegSetValueEx (key, ACE_TEXT ("PingInterval"), 0, REG_DWORD,
    (LPBYTE) &tmp, sizeof (DWORD));
  ACE_ASSERT (err == ERROR_SUCCESS);

  tmp = this->readonly_ ? 1 : 0;
  err = ACE_TEXT_RegSetValueEx (key, ACE_TEXT ("Lock"), 0, REG_DWORD,
    (LPBYTE) &tmp, sizeof (DWORD));
  ACE_ASSERT (err == ERROR_SUCCESS);

  tmp = this->repo_mode_;
  err = ACE_TEXT_RegSetValueEx (key, ACE_TEXT ("PersistType"), 0, REG_DWORD,
    (LPBYTE) &tmp, sizeof (DWORD));
  ACE_ASSERT (err == ERROR_SUCCESS);

  tmp = static_cast<DWORD> (this->startup_timeout_.sec());
  err = ACE_TEXT_RegSetValueEx (key, ACE_TEXT ("Timeout"), 0, REG_DWORD,
    (LPBYTE) &tmp, sizeof (DWORD));
  ACE_ASSERT (err == ERROR_SUCCESS);

  tmp = this->multicast_ ? 1 : 0;
  err = ACE_TEXT_RegSetValueEx (key, ACE_TEXT ("Multicast"), 0, REG_DWORD,
    (LPBYTE) &tmp, sizeof (DWORD));
  ACE_ASSERT (err == ERROR_SUCCESS);

  err = ACE_TEXT_RegSetValueEx (key, ACE_TEXT ("ImrType"), 0, REG_DWORD,
    (LPBYTE) &this->imr_type_ , sizeof (this->imr_type_));
  ACE_ASSERT (err == ERROR_SUCCESS);

  err = ::RegCloseKey (key);
  ACE_ASSERT (err == ERROR_SUCCESS);
#endif
  return 0;
}

int
Options::load_registry_options ()
{
#if defined (ACE_WIN32)
  HKEY key = 0;
  // Create or open the parameters key
  LONG err = ACE_TEXT_RegOpenKeyEx (SERVICE_REG_ROOT,
                             SERVICE_REG_PATH,
                             0,
                             KEY_READ,
                             &key
                             );
  if (err != ERROR_SUCCESS)
    {
      // If there aren't any saved parameters, then that's ok.
      return 0;
    }
  ACE_TCHAR tmpstr[4096];
  DWORD sz = sizeof (tmpstr);
  DWORD type = 0;
  err = ACE_TEXT_RegQueryValueEx (key, ACE_TEXT ("ORBInitOptions"), 0, &type,
    (LPBYTE) tmpstr, &sz);
  if (err == ERROR_SUCCESS)
    {
      ACE_ASSERT (type == REG_SZ);
      tmpstr[sz - 1] = '\0';
      this->cmdline_ = ACE_TEXT_ALWAYS_CHAR(tmpstr);
    }

  sz = sizeof(tmpstr);
  err = ACE_TEXT_RegQueryValueEx (key, ACE_TEXT ("IORFile"), 0, &type,
    (LPBYTE) tmpstr, &sz);
  if (err == ERROR_SUCCESS)
    {
      ACE_ASSERT (type == REG_SZ);
      tmpstr[sz - 1] = '\0';
      this->ior_output_file_ = tmpstr;
    }

  sz = sizeof(debug_);
  err = ACE_TEXT_RegQueryValueEx (key, ACE_TEXT ("DebugLevel"), 0, &type,
    (LPBYTE) &this->debug_ , &sz);
  if (err == ERROR_SUCCESS)
    {
      ACE_ASSERT (type == REG_DWORD);
    }

  DWORD tmp = 0;
  sz = sizeof(tmp);
  err = ACE_TEXT_RegQueryValueEx (key, ACE_TEXT ("PingExternal"), 0, &type,
    (LPBYTE) &tmp, &sz);
  if (err == ERROR_SUCCESS)
    {
      ACE_ASSERT (type == REG_DWORD);
      ping_external_ = tmp != 0;
    }

  tmp = 0;
  sz = sizeof(tmp);
  err = ACE_TEXT_RegQueryValueEx (key, ACE_TEXT ("PingInterval"), 0, &type,
    (LPBYTE) &tmp, &sz);
  if (err == ERROR_SUCCESS)
    {
      ACE_ASSERT (type == REG_DWORD);
      ping_interval_.msec (static_cast<long> (tmp));
    }

  tmp = 0;
  sz = sizeof(tmp);
  err = ACE_TEXT_RegQueryValueEx (key, ACE_TEXT ("Lock"), 0, &type,
    (LPBYTE) &tmp, &sz);
  if (err == ERROR_SUCCESS)
    {
      ACE_ASSERT (type == REG_DWORD);
      readonly_ = tmp != 0;
    }

  sz = sizeof(this->repo_mode_);
  err = ACE_TEXT_RegQueryValueEx (key, ACE_TEXT ("PersistType"), 0, &type,
    (LPBYTE) &this->repo_mode_, &sz);
  if (err == ERROR_SUCCESS)
    {
      ACE_ASSERT (type == REG_DWORD);
    }

  tmp = 0;
  sz = sizeof(tmp);
  err = ACE_TEXT_RegQueryValueEx (key, ACE_TEXT ("Timeout"), 0, &type,
    (LPBYTE) &tmp, &sz);
  if (err == ERROR_SUCCESS)
    {
      ACE_ASSERT (type == REG_DWORD);
      this->startup_timeout_.sec (tmp);
    }

  tmp = 0;
  sz = sizeof(tmp);
  err = ACE_TEXT_RegQueryValueEx (key, ACE_TEXT ("Multicast"), 0, &type,
    (LPBYTE) &tmp, &sz);
  if (err == ERROR_SUCCESS)
    {
      ACE_ASSERT (type == REG_DWORD);
      this->multicast_ = tmp != 0;
    }

  sz = sizeof(tmpstr);
  err = ACE_TEXT_RegQueryValueEx (key, ACE_TEXT ("PersistFile"), 0, &type,
    (LPBYTE) tmpstr, &sz);
  if (err == ERROR_SUCCESS)
    {
      ACE_ASSERT (type == REG_SZ);
      tmpstr[sz - 1] = '\0';
      this->persist_file_name_ = tmpstr;
    }

  sz = sizeof(imr_type_);
  err = ACE_TEXT_RegQueryValueEx (key, ACE_TEXT ("ImrType"), 0, &type,
    (LPBYTE) &this->imr_type_ , &sz);
  if (err == ERROR_SUCCESS)
    {
      ACE_ASSERT (type == REG_DWORD);
    }

  err = ::RegCloseKey (key);
  ACE_ASSERT (err == ERROR_SUCCESS);
#endif
  return 0;
}

bool
Options::service (void) const
{
  return this->service_;
}

unsigned int
Options::debug (void) const
{
  return this->debug_;
}

const ACE_TString&
Options::ior_filename (void) const
{
  return this->ior_output_file_;
}

bool
Options::multicast (void) const
{
  return this->multicast_;
}

Options::SERVICE_COMMAND
Options::service_command(void) const
{
  return this->service_command_;
}

const char*
Options::cmdline(void) const {
  return this->cmdline_.c_str ();
}

const ACE_TString&
Options::persist_file_name(void) const {
  return this->persist_file_name_;
}

ACE_Time_Value
Options::startup_timeout (void) const
{
  return this->startup_timeout_;
}

bool
Options::ping_external (void) const
{
  return this->ping_external_;
}

ACE_Time_Value
Options::ping_interval (void) const
{
  return this->ping_interval_;
}

LiveCheck *
Options::pinger (void) const
{
  return this->pinger_;
}

void
Options::pinger (LiveCheck *p)
{
  this->pinger_ = p;
}

Options::RepoMode
Options::repository_mode (void) const
{
  return this->repo_mode_;
}

bool
Options::repository_erase (void) const
{
  return this->erase_repo_;
}

bool
Options::readonly (void) const
{
  return this->readonly_;
}

bool
Options::unregister_if_address_reused (void) const
{
  return this->unregister_if_address_reused_;
}

bool
Options::throw_shutdown_exceptions (void) const
{
  return this->throw_shutdown_exceptions_;
}

Options::ImrType
Options::imr_type (void) const
{
  return this->imr_type_;
}
