/* 
 * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include "grtdb/db_helpers.h"
#include "base/string_utilities.h"
#include "new_server_instance_wizard.h"
#include "grt/grt_manager.h"
#include "grtui/grtdb_connection_editor.h"

#include "mforms/uistyle.h"
#include "base/log.h"
DEFAULT_LOG_DOMAIN(DOMAIN_WB_CONTEXT_UI)

#define INTRO_TEXT \
  "This wizard will guide you through the creation of a Server Profile to manage a MySQL server. "\
  "To fully support management of a remote MySQL server, an SSH daemon must be running "\
  "on the target machine. Alternatively, if you are going to manage a Windows server from a "\
  "Windows computer, you can also use native Windows management tools. "\
  "Remote management is used to start and stop a server and do server configuration. "\
  "You may create a Profile without remote management if you do not need that functionality."

static struct {
  const char *pattern;
  const char *os_name;
} platform_strings[] = {
  {"apple-darwin", "MacOS X"}, // For OS X there's an additional check.
  {"-linux", "Linux"},
  {"win64", "Windows"},
  {"win32", "Windows"},
  {NULL, NULL},
};

using namespace base;
using namespace mforms;

//----------------- NewServerInstancePage ----------------------------------------------------------

NewServerInstancePage::NewServerInstancePage(WizardForm *form, const std::string &pageid)
  : WizardPage(form, pageid)
{
}

//--------------------------------------------------------------------------------------------------

NewServerInstanceWizard* NewServerInstancePage::wizard()
{
  return dynamic_cast<NewServerInstanceWizard*>(_form);
}

//----------------- IntroductionPage ---------------------------------------------------------------

IntroductionPage::IntroductionPage(WizardForm *form)
: WizardPage(form, "introduction-page")
{
  set_short_title(_("Introduction"));
  set_title(_("Introduction"));

  mforms::Label *text = mforms::manage(new mforms::Label());
  text->set_text(_("This dialog will help you to set up remote managemment for your connection. At the start "
                "a connection attempt is made to determine server version and operating system of the target "
                "machine. This allows you to validate the connection settings and allows the wizard to pick "
                "a meaningful configuration preset. If this attemmpt fails you can still continue, however.\n\n"
                "Continue to the next page to start the connection. This might take a few moments."));
  text->set_wrap_text(true);
  add(text, false, true);
}

//----------------- TestDatabaseSettingsPage -------------------------------------------------------

TestDatabaseSettingsPage::TestDatabaseSettingsPage(WizardForm* host)
  : WizardProgressPage(host, "test database settings page", true)
{
  set_short_title(_("Test DB Connection"));
  set_title(_("Testing the Database Connection"));

  set_heading(_("The database connection information is being tested. This might take a few "
                "moments depending on your network connection."));


  add_task(_("Open Database Connection"),
                  boost::bind(&TestDatabaseSettingsPage::open_connection, this),
                  _("Connecting to database server..."));

  add_task(_("Get Server Version"),
           boost::bind(&TestDatabaseSettingsPage::get_server_version, this),
           _("Querying server version..."));

  add_task(_("Get Server OS"),
           boost::bind(&TestDatabaseSettingsPage::get_server_platform, this),
           _("Querying server OS type..."));

  /*XXX Improve rights checks.
   add_task(_("Check Account Privileges"),
   boost::bind(&TestDatabaseSettingsPage::get_server_platform, this),
   _("Checking if account being used has enough privileges..."));
   - check the privs the account has and warn if it can't do something:
   - creating and modifying user accts
   - querying for tables
   - check if it has global schema rights and if not, warn dumps will only work for databases it has access to
   - other stuff
   */

  end_adding_tasks(_("Database connection tested successfully."));

  set_status_text("");
}

//--------------------------------------------------------------------------------------------------

bool TestDatabaseSettingsPage::open_connection()
{
  try
  {
    db_mgmt_ConnectionRef conn(db_mgmt_ConnectionRef::cast_from(values().get("connection")));
    add_log_text(strfmt("Connecting to MySQL server %s...", conn->name().c_str()));
    _dbc_conn = sql::DriverManager::getDriverManager()->getConnection(conn);
    add_log_text("Connected.");
  }
  catch (std::exception &exc)
  {
    _message = exc.what();
    add_log_text(_message.c_str());
    throw;
  }

  return true;
}

//--------------------------------------------------------------------------------------------------

void TestDatabaseSettingsPage::tasks_finished(bool success)
{
  if (!success)
    set_status_text(strfmt("Could not connect to MySQL server:\n  %s\nYou may continue if the server is simply not running.",
                           _message.c_str()), true);
}

//--------------------------------------------------------------------------------------------------

bool TestDatabaseSettingsPage::get_server_version()
{
  sql::Statement *pstmt = _dbc_conn->createStatement();
  sql::ResultSet *res = pstmt->executeQuery("SELECT VERSION() as VERSION");
  std::string version;

  if (res && res->next())
  {
    version = res->getString("VERSION");
  }
  delete res;
  delete pstmt;

  if (version.empty())
  {
    current_task()->label.set_text("Server Version: unknown");
    throw std::runtime_error("Error querying version of MySQL server");
  }

  values().gset("server_version", version);

  current_task()->label.set_text("Server Version: " + version);
  add_log_text(strfmt("MySQL server version is %s", version.c_str()));

  
  // check that we're connecting to a known and supported version of the server
  if (!bec::is_supported_mysql_version(version))
  {
    current_task()->label.set_text("Get Server Version: Unsupported Server Version");
    add_log_text(strfmt("Unknown/unsupported server version or connection protocol detected (%s).\nMySQL Workbench is developed and test for MySQL Server versions 5.1, 5.5, 5.6 and 5.7.\nA connection can be established but some MySQL Workbench features may not work properly.", version.c_str()));
    throw std::runtime_error(strfmt("Unknown/unsupported server version or connection protocol detected (%s).\nMySQL Workbench provides connectivity to MySQL Server versions 5.1, 5.5, 5.6 and 5.7.\nA connection can be established but some MySQL Workbench features may not work properly.", version.c_str()));
  }

  return true;
}

//--------------------------------------------------------------------------------------------------

/**
 * This functions attempts to find a clue on which OS this server is running by examining
 * on which is was built. There's usually a good correlation, even though it may not be very precise
 * (e.g. a server built on Win Vista can run on Win 8, similar for OS X etc.).
 */
bool TestDatabaseSettingsPage::get_server_platform()
{
  sql::Statement *pstmt = _dbc_conn->createStatement();
  sql::ResultSet *res = pstmt->executeQuery("SHOW VARIABLES LIKE 'version_compile_%'");
  std::string name, value;
  std::string machine, os;

  while (res && res->next())
  {
    name = res->getString("Variable_name");
    value = res->getString("Value");

    if (name == "version_compile_machine")
      machine = value;
    if (name == "version_compile_os")
      os = value;
  }
  delete res;
  delete pstmt;

  _dbc_conn.reset();

  os = base::tolower(os);
  std::string os_type = "";
  if (base::starts_with(os, "osx"))
    os_type = "MacOS X";
  
  if (os.empty())
  {
    for (int i= 0; platform_strings[i].pattern; i++)
    {
      if (strstr(os.c_str(), platform_strings[i].pattern))
      {
        os_type = platform_strings[i].os_name;
        values().gset("detected_os_type", os_type);
        break;
      }
    }
  }

  if (os_type.empty())
    os_type = "unknown";
  current_task()->label.set_text("Server OS: " + os_type);

  add_log_text(strfmt("MySQL server architecture is %s", machine.empty() ? "unknown" : machine.c_str()));
  add_log_text(strfmt("MySQL server OS is %s", os.empty() ? "unknown" : os.c_str()));

  return true;
}

//--------------------------------------------------------------------------------------------------

void TestDatabaseSettingsPage::enter(bool advancing)
{
  if (advancing)
  {
    values().remove("server_version");
    values().remove("detected_os_type");
  }
  WizardProgressPage::enter(advancing);
}

//--------------------------------------------------------------------------------------------------

NewServerInstanceWizard* TestDatabaseSettingsPage::wizard()
{
  return dynamic_cast<NewServerInstanceWizard*>(_form);
}

//----------------- HostAndRemoteTypePage ----------------------------------------------------------

HostAndRemoteTypePage::HostAndRemoteTypePage(WizardForm* host)
  : NewServerInstancePage(host, "os + remote page"),
  _management_type_panel(TitledBoxPanel),
  _management_type_box(false),
  _os_panel(TitledBoxPanel),
  _os_box(false),
  _win_remote_admin(RadioButton::new_id()),
  _ssh_remote_admin(_win_remote_admin.group_id())
{
  set_short_title(_("Management and OS"));

  // Remote management.
  _management_type_panel.set_title(_("Select the type of remote management you want to use:"));
  _management_type_panel.add(&_management_type_box);

  _win_remote_admin.set_text(_("Native Windows remote management (only available on Windows)"));
  scoped_connect(_win_remote_admin.signal_toggled(),boost::bind(&HostAndRemoteTypePage::toggle_remote_admin, this));
#ifndef _WIN32
  _win_remote_admin.set_enabled(false);
#endif
  _ssh_remote_admin.set_text(_("SSH login based management"));
  scoped_connect(_ssh_remote_admin.signal_toggled(),boost::bind(&HostAndRemoteTypePage::toggle_remote_admin, this));

  _management_type_box.add(&_win_remote_admin, false, true);
  _management_type_box.add(&_ssh_remote_admin, false, true);

#ifdef _WIN32
  _win_remote_admin.set_active(true);
#else
  _ssh_remote_admin.set_active(true);
#endif

  _management_type_box.set_spacing(8);
  _management_type_box.set_padding(10);
  add(&_management_type_panel, false, true);

  // OS selection.
  _os_panel.set_title(_("Operating System Selection"));
  _os_panel.add(&_os_box);

  _os_description.set_wrap_text(true);
  _os_description.set_text(_("Select the operating system and the type of database installation "
    "on the target machine. If you configure a Linux target and you are unsure about the type of "
    "database installation select the (Vendor Package) variant. If your specific operating system is not in this list, select "
    "a related variant. It can later be customized, if needed."));
  _os_box.add(&_os_description, false, true);
  
  _params.set_row_count(2);
  _params.set_column_count(2);
  _params.set_row_spacing(MF_TABLE_ROW_SPACING);
  _params.set_column_spacing(MF_TABLE_COLUMN_SPACING);
  
  _os_label.set_text_align(mforms::MiddleRight);
  _os_label.set_text(_("Operating System:"));
  _params.add(&_os_label, 0, 1, 0, 1, mforms::HFillFlag);
  _params.add(&_os_selector, 1, 2, 0, 1, mforms::HFillFlag|mforms::HExpandFlag);
  scoped_connect(_os_selector.signal_changed(),boost::bind(&HostAndRemoteTypePage::refresh_profile_list, this));

  _type_label.set_text_align(mforms::MiddleRight);
  _type_label.set_text(_("MySQL Installation Type:"));
  _params.add(&_type_label, 0, 1, 1, 2, mforms::HFillFlag);
  _params.add(&_type_selector, 1, 2, 1, 2, mforms::HFillFlag|mforms::HExpandFlag);
  
  _os_box.add(&_params, true, true);
  _os_box.set_spacing(10);
  _os_panel.set_padding(8);
  
  add(&_os_panel, false, true);
}

//--------------------------------------------------------------------------------------------------

void HostAndRemoteTypePage::enter(bool advancing)
{
  if (!advancing)
    return;
  
  // force check of admin type
  toggle_remote_admin();

  if (wizard()->is_local())
    set_title(_("Specify the installation type for you target operation system"));
  else
    set_title(_("Specify remote management type and target operation system"));

  int last_selected= _os_selector.get_selected_index();
  
  suspend_layout();
  _os_selector.suspend_layout();
  
  // Refresh platform list on each enter. This way a change on disk can be reflected without
  // restarting the wizard.
  std::string path= wizard()->grtm()->get_data_file_path("mysql.profiles");
  GDir *dir = g_dir_open(path.c_str(), 0, NULL);
  if (dir)
  {
    const gchar *file;
    
    _presets.clear();
    while ((file = g_dir_read_name(dir)))
    {
      if (g_str_has_suffix(file, ".xml"))
      {
        std::string fname= std::string(file, strlen(file)-4);
        std::string label= bec::replace_string(fname, "_", " ");          
        grt::DictRef dict;
        try
        {
          dict= grt::DictRef::cast_from(wizard()->grtm()->get_grt()->unserialize(path+"/"+file));
        }
        catch (std::exception &exc)
        {
          g_warning("Profile %s contains invalid data: %s", path.c_str(), exc.what());
          continue;
        }
        _presets[dict.get_string("sys.system")].push_back(std::make_pair(label, path+"/"+file));
      }
    }
    g_dir_close(dir);
  }  
  else
    g_warning("Opening profiles folder failed.");
  
  //we need to sort the preset list
  for(std::map<std::string, std::vector<std::pair<std::string,std::string> > >::const_iterator it = _presets.begin(); it != _presets.end(); it++)
  {
    std::sort(_presets[it->first].begin(), _presets[it->first].end());
  }

  std::string detected_os_type = values().get_string("detected_os_type");
  if (wizard()->is_local())
  {
    _management_type_panel.show(false);
    if (detected_os_type.empty())
    {
#ifdef _WIN32
      detected_os_type = "Windows";
#elif defined(__APPLE__)
      detected_os_type = "MacOS X";
#else
      detected_os_type = "Linux";
#endif
    }
  }
  else
    _management_type_panel.show(true);

  _os_selector.clear();
  int i = 0;
  for (std::map<std::string, std::vector<std::pair<std::string,std::string> > >::const_iterator it = _presets.begin();
       it != _presets.end(); ++it, ++i)
  {
    _os_selector.add_item(it->first);
    if (advancing)
    {
      if (detected_os_type == it->first)
        last_selected = i;
    }
  }
  
  if (last_selected < 0)
    last_selected= 0;
  _os_selector.set_selected(last_selected);
  
  _os_selector.resume_layout();
  resume_layout();
  
  refresh_profile_list();
}

//--------------------------------------------------------------------------------------------------

void HostAndRemoteTypePage::refresh_profile_list()
{
  wizard()->clear_problem();

  std::string system = _os_selector.get_string_value();
  
  _type_selector.clear();
  std::list<std::string> profiles;
  for (std::vector<std::pair<std::string,std::string> >::const_iterator iter= _presets[system].begin();
       iter != _presets[system].end(); ++iter)
    profiles.push_back(iter->first);

    _type_selector.add_items(profiles);
}

//--------------------------------------------------------------------------------------------------

void HostAndRemoteTypePage::toggle_remote_admin()
{
  wizard()->clear_problem();

  std::string detected_os_type = values().get_string("detected_os_type");
  bool refresh_profiles = false;

  // Hard code Windows for remote Windows management. There is also no profile to choose from
  // as we base the config file location on the managed service.
  if (_win_remote_admin.get_active() && !wizard()->is_local())
  {
    detected_os_type = "Windows";
    _os_panel.show(false);
    _type_selector.set_selected(-1);
  }
  else
  {
    refresh_profiles = true;
    _os_panel.show(true);
    _os_panel.relayout();
    if (detected_os_type.empty() && wizard()->is_local())
    {
#ifdef _WIN32
      detected_os_type = "Windows";
#elif defined(__APPLE__)
      detected_os_type = "MacOS X";
#else
      detected_os_type = "Linux";
#endif
    }

    int i = 0;
    for (std::map<std::string, std::vector<std::pair<std::string,std::string> > >::const_iterator it = _presets.begin();
         it != _presets.end(); ++it, ++i)
    {
      if (detected_os_type == it->first)
      {
        if (_os_selector.get_selected_index() != i)
        {
          _os_selector.set_selected(i);
          if (refresh_profiles)
            refresh_profile_list();
        }
        break;
      }
    }
  }
}

//--------------------------------------------------------------------------------------------------

bool HostAndRemoteTypePage::advance()
{
  std::string system = _os_selector.get_string_value();
  values().gset("os", system);

  bool need_templates = false;
  if (wizard()->is_local())
  {
    values().gset("remoteAdmin", 0);
#ifdef _WIN32
    values().gset("windowsAdmin", 1);
#else
    need_templates = true;
    values().remove("windowsAdmin");
#endif

  }
  else
  {
    if (_ssh_remote_admin.get_active())
    {
      need_templates = true;
      values().remove("windowsAdmin");
      values().gset("remoteAdmin", 1);
    }
    else
    {
      values().gset("windowsAdmin", 1);
      values().gset("remoteAdmin", 0);
    }
  }

  if (need_templates)
  {
    int selected_index = _type_selector.get_selected_index();
    if (selected_index == -1)
    {
      wizard()->set_problem(_("MySQL installation type not selected"));
      return false;
    }
    values().gset("template_path", _presets[system][selected_index].second);
    values().gset("template", _presets[system][selected_index].first);
  }

  wizard()->load_defaults();

  return true;
}

//--------------------------------------------------------------------------------------------------

bool HostAndRemoteTypePage::skip_page()
{
  // Skip this page if this is a local Windows installation.
#ifdef _WIN32
  if (wizard()->is_local())
  {
    values().gset("remoteAdmin", 0);
    values().gset("windowsAdmin", 1);
    values().remove("template_path");
    values().remove("template");

    values().gset("os", "Windows");

    return true;
  }

  return false;
#else
  return false;
#endif
}

//----------------- SSHManagementPage --------------------------------------------------------------

SSHConfigurationPage::SSHConfigurationPage(WizardForm* host)
  : NewServerInstancePage(host, "ssh configuration page"), _indent(false)
{
  set_short_title(_("SSH Configuration"));
  set_title(_("Set remote SSH configuration parameters"));
  
  set_spacing(10);
  _main_description1.set_wrap_text(true);
  _main_description1.set_text(_("In order to remotely configure this database instance an SSH account on this "
                                "host with appropriate privileges is required. This account needs write access "
                                "to the database configuration file, read access to the database logs and "
                                "privileges to start/stop the database service/daemon."));
  add(&_main_description1, false, true);
  
  _ssh_settings_table.set_row_count(6);
  _ssh_settings_table.set_row_spacing(5);
  _ssh_settings_table.set_column_count(5);
  _ssh_settings_table.set_column_spacing(5);
  
  _indent.set_size(20, -1);
  _ssh_settings_table.add(&_indent, 0, 1, 0, 1, HFillFlag);
  
  _host_name_label.set_text(_("Host Name:"));
  _ssh_settings_table.add(&_host_name_label, 1, 2, 0, 1, HFillFlag);
  _ssh_settings_table.add(&_host_name, 2, 3, 0, 1, HFillFlag | HExpandFlag);
  _port_label.set_text(_("Port:"));
  _ssh_settings_table.add(&_port_label, 3, 4, 0, 1, HFillFlag);
  _port.set_size(50, -1);
  _port.set_value("22");
  _ssh_settings_table.add(&_port, 4, 5, 0, 1, HFillFlag);
  
  _username_label.set_text(_("User Name:"));
  _ssh_settings_table.add(&_username_label, 1, 2, 1, 2, HFillFlag);
  _ssh_settings_table.add(&_username, 2, 3, 1, 2, HFillFlag | HExpandFlag);

  _use_ssh_key.set_text(_("Authenticate Using SSH Key"));
  scoped_connect(_use_ssh_key.signal_clicked(),boost::bind(&SSHConfigurationPage::use_ssh_key_changed, this));
  _ssh_settings_table.add(&_use_ssh_key, 1, 5, 4, 5, HFillFlag);
  
  _ssh_path_label.set_text(_("SSH Private Key Path:"));
  _ssh_settings_table.add(&_ssh_path_label, 1, 2, 5, 6, HFillFlag);
  _ssh_settings_table.add(&_ssh_key_path, 2, 3, 5, 6, HFillFlag | HExpandFlag);
  _ssh_settings_table.add(&_ssh_key_browse_button, 3, 4, 5, 6, 0);
  
  _file_selector = mforms::manage(new FsObjectSelector(&_ssh_key_browse_button, &_ssh_key_path));
  std::string homedir =
#ifdef _WIN32
    mforms::Utilities::get_special_folder(mforms::ApplicationData);
#else
    "~";
#endif
  _file_selector->initialize(homedir + "/.ssh/id_rsa", mforms::OpenFile, "", "...", true,
    boost::bind(&WizardPage::validate, this));
  use_ssh_key_changed();
  
  add(&_ssh_settings_table, false, true);
}

//--------------------------------------------------------------------------------------------------

void SSHConfigurationPage::use_ssh_key_changed()
{
  bool value = _use_ssh_key.get_active();
  _ssh_path_label.set_enabled(value);
  _ssh_key_path.set_enabled(value);
  _ssh_key_browse_button.set_enabled(value);
}

//--------------------------------------------------------------------------------------------------

void SSHConfigurationPage::enter(bool advancing)
{
  if (advancing)
  {
    _host_name.set_value(values().get_string("host_name"));

    std::string value = values().get_string("ssh_user_name");
    if (value.empty() && g_get_user_name() != NULL)
      value = g_get_user_name();
    _username.set_value(value.empty() ? "" : value);

    value = values().get_string("ssh_port");
    if (!value.empty())
      _port.set_value(value);

    value = values().get_string("ssh_key_path");
    if (!value.empty())
    {
      _use_ssh_key.set_active(true);
      use_ssh_key_changed();
      _file_selector->set_filename(value);
    }
  }
}

//--------------------------------------------------------------------------------------------------

bool SSHConfigurationPage::advance()
{
  db_mgmt_ServerInstanceRef instance(wizard()->assemble_server_instance());
  
  // Check if we have valid SSH credentials.
  std::string value= base::trim(_host_name.get_string_value());
  if (value.empty())
  {
    Utilities::show_error(_("SSH Host Needed"), _("Please specify the host name or address."), _("OK"));
    return false;
  }
  
  value= base::trim(_username.get_string_value());
  if (value.empty())
  {
    Utilities::show_error(_("SSH User Name Needed"), _("Please specify the user name for the SSH account to be used."), _("OK"));
    return false;
  }
  
  return true;
}

//--------------------------------------------------------------------------------------------------

void SSHConfigurationPage::leave(bool advancing)
{
  if (advancing)
  {
    values().gset("host_name", _host_name.get_string_value());
    values().gset("ssh_port", _port.get_string_value());
    values().gset("ssh_user_name", _username.get_string_value());
    if (_use_ssh_key.get_active())
      values().gset("ssh_key_path", _ssh_key_path.get_string_value());
    else
      values().remove("ssh_key_path");
  }
}

//--------------------------------------------------------------------------------------------------

bool SSHConfigurationPage::skip_page()
{
  return values().get_int("remoteAdmin", 0) != 1;
}

//----------------- WindowsManagementPage ----------------------------------------------------------

WindowsManagementPage::WindowsManagementPage(WizardForm* host, wb::WBContext* context)
  : NewServerInstancePage(host, "windows management page"), _indent(false)
{
  _context = context;

  set_short_title(_("Windows Management"));

  //set_spacing(10);
  _layout_table.set_row_count(6);
  _layout_table.set_column_count(5);
  _layout_table.set_row_spacing(6);
  _layout_table.set_column_spacing(4);

  _indent.set_size(10, -1);
  _layout_table.add(&_indent, 0, 1, 1, 2, 0);

  _main_description1.set_wrap_text(true);
  _main_description1.set_text(_("Remote Windows management requires a user account on the remote machine "
    "which is allowed to connect remotely and also has the required privileges to query system status and "
    "to control services. For configuration file manipulation read/write access is needed to the file. "
    "Depending on your environment several ways of accessing that file are possible.\n\nExamples are mapped drives, "
    "network shares and administrative shares:"));
  _layout_table.add(&_main_description1, 0, 4, 0, 1, HFillFlag | VFillFlag);

  _main_description2.set_wrap_text(true);
  _main_description2.set_style(BoldStyle);
  _main_description2.set_text(_("M:\\<path to file>\\my.ini\n"
    "\\\\<server>\\<share>\\<path to file>\\my.ini\n"
    "\\\\<server>\\C$\\Program Files\\MySQL\\MySQL Server 5.1\\my.ini\n"));
  _layout_table.add(&_main_description2, 1, 4, 1, 2, HFillFlag | VFillFlag);

  _progress_label.set_text(_("Initializing WMI, please wait..."));
  _layout_table.add(&_progress_label, 0, 4, 2, 3, HFillFlag | VFillFlag);

  _service_label.set_wrap_text(true);
  _service_label.set_text(_("Select the service to manage from the list below. It will also help to "
    "find the configuration file."));
  _layout_table.add(&_service_label, 0, 4, 3, 4, HFillFlag | VFillFlag);

  scoped_connect(_service_selector.signal_changed(),boost::bind(&WindowsManagementPage::refresh_config_path, this));
  _layout_table.add(&_service_selector, 1, 4, 4, 5, HFillFlag);

  _config_path_label.set_text(_("Path to Configuration File:"));
  _config_path_label.set_text_align(MiddleRight);
  _layout_table.add(&_config_path_label, 1, 2, 5, 6, HFillFlag);
  _layout_table.add(&_config_path, 2, 4, 5, 6, HFillFlag | HExpandFlag);
  _layout_table.add(&_browse_button, 4, 5, 5, 6, HFillFlag);

  // Setup for configuration file browsing.
  _file_selector= mforms::manage(new FsObjectSelector(&_browse_button, &_config_path));
  _file_selector->initialize("", mforms::OpenFile, "", "...", true, boost::bind(&WizardPage::validate, this));

  add(&_layout_table, false, true);
}

//--------------------------------------------------------------------------------------------------

void WindowsManagementPage::refresh_config_path()
{
  if (_service_selector.get_selected_index() >= 0 && _service_selector.get_selected_index() < (int) _config_paths.size())
    _config_path.set_value(_config_paths[_service_selector.get_selected_index()]);
  else
    _config_path.set_value("");
}

//--------------------------------------------------------------------------------------------------

void WindowsManagementPage::leave(bool advancing)
{
  // If we're going back, reset the progress label. We can't set it right before
  // performing the slow operation, because it blocks the UI.
  if (!advancing)
  {
     _progress_label.set_text(_("Initializing WMI, please wait..."));
  }
}

//--------------------------------------------------------------------------------------------------

void WindowsManagementPage::enter(bool advancing)
{
  if (advancing)
  {
    wizard()->clear_problem();
    _config_paths.clear();
    _service_names.clear();
    _service_selector.clear();

    std::string host = values().get_string("host_name");

    std::string user;
    std::string password;

    bool local_connection = wizard()->is_local();
    if (!local_connection)
    {
      set_title(_("Set remote Windows configuration parameters for host " + host));

      _main_description1.set_text(_("Remote Windows management requires a user account on the remote machine "
        "which is allowed to connect remotely and also has the required privileges to query system status and "
        "to control services. For configuration file manipulation read/write access is needed to the file. "
        "Depending on your environment several ways of accessing that file are possible.\n\nExamples are mapped drives, "
        "network shares and administrative shares:"));
      _main_description2.show(true);

      user = values().get_string("wmi_user_name", "");

      std::string title = strfmt(_("Remote Windows Login (%s)"), host.c_str()) + "|";
      title += _("Please enter a Windows user login and password for the remote server with rights to WMI");
      
      bool result = false;
      try
      {
        result = Utilities::credentials_for_service(title, "wmi@" + host, user, false, password);
      }
      catch(std::exception &exc)
      {
        log_warning("Exception caught when clearning the password: %s", exc.what());
        mforms::Utilities::show_error("Clear Password", 
                                      base::strfmt("Could not clear password: %s", exc.what()),
                                      "OK");
      }
      
      if (!result)
      {
        _progress_label.set_text(_("Need valid user credentials to connect to server."));
        wizard()->set_problem(_("Need valid user credentials to connect to server."));
        return;
      }

      values().gset("wmi_user_name", user);
    }
    else
    {
      host = ""; // Empty host name for local machine.

      _main_description1.set_text(_("Windows management requires a user account on this machine "
        "which has the required privileges to query system status and to control services. "
        "For configuration file manipulation read/write access to the file is needed. "));
      _main_description2.show(false);

      set_title(_("Set Windows configuration parameters for this machine"));
    }

    grt::GRT* grt = _context->get_grt();
    grt::Module* module = grt->get_module("Workbench");

    try
    {
      grt::ValueRef wmi_session;

      {
        grt::StringListRef arguments(grt);
        arguments.ginsert(grt::StringRef(host));
        arguments.ginsert(grt::StringRef(user));
        arguments.ginsert(grt::StringRef(password));

        // This can take a few seconds.
        wmi_session = module->call_function("wmiOpenSession", arguments);
      }

      grt::ValueRef wmi_result;
      {
        grt::BaseListRef arguments(grt);
        arguments.ginsert(wmi_session);
        arguments.ginsert(grt::StringRef("select * from Win32_Service where (Name like \"%mysql%\" or DisplayName like \"%mysql%\")"));

        wmi_result = module->call_function("wmiQuery", arguments);

        module->call_function("wmiCloseSession", arguments); // Only the first parameter of arguments is used here.
      }
      grt::DictListRef entries = grt::DictListRef::cast_from(wmi_result);

      size_t count = entries->count();
      if (count > 0)
      {
        for (size_t i = 0; i < count; i++)
        {
          grt::DictRef entry(entries[i]);

          std::string service_name = entry.get_string("Name", "invalid");
          std::string service_display_name = entry.get_string("DisplayName", "invalid");
          std::string path = entry.get_string("PathName", "invalid");
          std::string state = entry.get_string("State", "unknown");
          std::string start_mode = entry.get_string("StartMode", "unknown");

          std::string config_file = base::extract_option_from_command_line("--defaults-file", path);
          if (!local_connection)
          {
            if (config_file.empty())
              config_file = "C$\\";
            config_file = "\\\\" + host + "\\" + config_file;
            base::replace(config_file, ":", "$");
          }
          _config_paths.push_back(config_file);
          _service_names.push_back(service_name);
          std::string selector_text = service_display_name + " (" + state + ", Start mode: " + start_mode + ")";
          _service_selector.add_item(selector_text);
        }
        _progress_label.set_text("");
      }
      else
      {
        _progress_label.set_text("No MySQL service found.");
        wizard()->set_problem(_("In order to manage a MySQL server it must be installed as a service. "
          "The wizard could not find any MySQL service on the target machine, hence the server instance "
          "cannot be created."));
      }

      refresh_config_path();
    }
    catch (std::runtime_error e)
    {
      _progress_label.set_text(base::strfmt(_("Could not set up connection: %s"), e.what()));
      wizard()->set_problem(e.what());

      // In case this error was caused by wrong credentials (we cannot be sure from the error message)
      // we remove the already stored password to make it possible to (re) enter the current one.
      try
      {
        Utilities::forget_password("wmi@" + host, user);
      }
      catch (std::exception &exc)
      {
        log_warning("Exception caught when clearning the password: %s", exc.what());
        mforms::Utilities::show_error("Clear Password", 
                                      base::strfmt("Could not clear password: %s", exc.what()),
                                      "OK");
      }
      
      values().gset("wmi_user_name", "");
    }
  }
}

//--------------------------------------------------------------------------------------------------

bool WindowsManagementPage::advance()
{
  if (_service_names.size() == 0 || _service_selector.get_selected_index() < 0)
    return false;

  values().gset("ini_path", _config_path.get_string_value());
  values().gset("ini_section", "mysqld");
  values().gset("service_name", _service_names[_service_selector.get_selected_index()]); 

  return true;
}

//--------------------------------------------------------------------------------------------------

bool WindowsManagementPage::skip_page()
{
  // Provide native Windows (WMI) management for local and remote Windows boxes.
  // Remote Windows boxes which are managed via SSH use the SSH config page instead, though.

  bool local_wmi;
#ifdef _WIN32
  local_wmi = true;
#else
  local_wmi = false;
#endif
  bool remote_wmi = values().get_int("windowsAdmin", 0) != 0;

  if (dynamic_cast<NewServerInstanceWizard*>(_form)->is_local())
    return !local_wmi;
  else
    return !remote_wmi;
}

//----------------- TestHostMachineSettingsPage ----------------------------------------------------

TestHostMachineSettingsPage::TestHostMachineSettingsPage(WizardForm* host)
  : WizardProgressPage(host, "test host machine settings page", true)
{
  set_short_title(_("Test Settings"));
  set_title(_("Testing Host Machine Settings"));

  set_heading(_("The connection to the host machine is being tested. This might take a few "
    "moments depending on your network connection."));
  _connect_task= add_task(_("Connect to host machine"),
    boost::bind(&TestHostMachineSettingsPage::connect_to_host, this),
    _("Trying to find host machine and connecting to it..."));

  _commands_task = add_async_task(_("Check location of start/stop commands"),
    boost::bind(&TestHostMachineSettingsPage::check_admin_commands, this),
    _("Checking if commands to start and stop server are in the expected location..."));

  add_async_task(_("Check MySQL configuration file"),
    boost::bind(&TestHostMachineSettingsPage::find_config_file, this),
    _("Looking for the configuration file of the database server..."));

  end_adding_tasks(_("Testing host machine settings is done."));

  set_status_text("");
}

//--------------------------------------------------------------------------------------------------

void TestHostMachineSettingsPage::enter(bool advance)
{
  reset_tasks();

  db_mgmt_ServerInstanceRef instance(wizard()->assemble_server_instance());
  _connect_task->set_enabled(values().get_int("remoteAdmin", 0) == 1);
  _commands_task->set_enabled(values().get_int("windowsAdmin", 0) == 0);

  WizardProgressPage::enter(advance);
}

//--------------------------------------------------------------------------------------------------

bool TestHostMachineSettingsPage::connect_to_host()
{
  // This will require the ssh or SSH key password, so it needs to be called from main thread.
  wizard()->test_setting_grt(wizard()->grtm()->get_grt(), "connect_to_host");

  return true;
}

//--------------------------------------------------------------------------------------------------

bool TestHostMachineSettingsPage::find_config_file()
{
  // Native remote Windows management uses a direct URI for the files.
  bool use_local = wizard()->is_local() || values().get_int("windowsAdmin", 0) == 1;
  execute_grt_task(boost::bind(&NewServerInstanceWizard::test_setting_grt, wizard(), _1,
                              use_local ? "find_config_file/local" : "find_config_file"), false);
  return true;
}

//--------------------------------------------------------------------------------------------------

bool TestHostMachineSettingsPage::find_error_files()
{
  bool use_local = wizard()->is_local() || values().get_int("windowsAdmin", 0) == 1;
  execute_grt_task(boost::bind(&NewServerInstanceWizard::test_setting_grt, wizard(), _1,
                              use_local ? "find_error_files/local" : "find_error_files"), false);
  return true;
}

//--------------------------------------------------------------------------------------------------

bool TestHostMachineSettingsPage::check_admin_commands()
{
  execute_grt_task(boost::bind(&NewServerInstanceWizard::test_setting_grt, wizard(), _1, 
                              wizard()->is_local() ? "check_admin_commands/local" : "check_admin_commands"), false);
  return true;
}

//--------------------------------------------------------------------------------------------------

bool TestHostMachineSettingsPage::skip_page()
{
  return !(wizard()->is_admin_enabled());
}

//--------------------------------------------------------------------------------------------------

void TestHostMachineSettingsPage::tasks_finished(bool success)
{
  values().gset("host_tests_succeeded", success);
}

//--------------------------------------------------------------------------------------------------

void TestHostMachineSettingsPage::leave(bool advancing)
{
  if (advancing)
  {
    bool require_review = false;
    if (values().get_int("host_tests_succeeded") == 1)
    {
      require_review = Utilities::show_message(_("Review settings"),
        _("Checks succeeded for Connection and Configuration Settings for this new Server Instance."),
        _("Continue"), "", _("I'd like to review the settings again"))
        == mforms::ResultOther;
    }
    else
      require_review = true;
    values().gset("review_required", require_review);

    if (!require_review)
      wizard()->create_instance();
  }
}

//--------------------------------------------------------------------------------------------------

NewServerInstanceWizard* TestHostMachineSettingsPage::wizard()
{
  return dynamic_cast<NewServerInstanceWizard*>(_form);
}

//----------------- ReviewPage ----------------------------------------------------------------------

ReviewPage::ReviewPage(WizardForm* host)
  : NewServerInstancePage(host, "review"), _text(VerticalScrollBar)
{
  set_short_title(_("Review Settings"));
  set_title(_("Review Remote Management Settings"));
  
  _label.set_text(_("Below is a list of all settings collected so far. This includes also values taken "
    "from templates or default values. Check if they match your actual settings and toggle 'Change Parameters' "
    "if you need to make any changes to default values. For any other change go back to the appropriate wizard page.\n\n"
    "Pay special attention if you run more than one instance of MySQL on the same machine."));
  _label.set_wrap_text(true);

  _text.set_read_only(true);
  
  add(&_label, false, true);
  add(&_text, true, true);
  _customize_check.set_text("Change Parameters");
  scoped_connect(_customize_check.signal_clicked(), boost::bind(&ReviewPage::customize_changed, this));
  add(&_customize_check, false, true);
}

//--------------------------------------------------------------------------------------------------

void ReviewPage::customize_changed()
{
  values().gset("customize", _customize_check.get_active());
  wizard()->update_buttons();
}

//--------------------------------------------------------------------------------------------------

void ReviewPage::enter(bool advancing)
{
  if (advancing)
  {
    std::string summary;

    grt::DictRef serverInfo(wizard()->assemble_server_instance()->serverInfo());
    
    bool ssh_management = values().get_int("remoteAdmin") != 0;
    bool wmi_management = values().get_int("windowsAdmin") != 0;
    
    std::string host_name = values().get_string("host_name", "localhost");
    if (ssh_management)
    {
      std::string ssh_port = values().get_string("ssh_port", "22");
      std::string ssh_user_name = values().get_string("ssh_user_name");
      std::string ssh_key_path = values().get_string("ssh_key_path");

      summary.append(_("SSH Based Adminstration enabled\n"));
      summary.append(strfmt(_("    SSH host:  %s:%s\n"), host_name.c_str(), ssh_port.c_str()));
      summary.append(strfmt(_("    SSH user:  %s\n"), ssh_user_name.c_str()));
      summary.append(strfmt(_("    SSH key file:  %s\n"), ssh_key_path.empty() ? "not set" : ssh_key_path.c_str()));
    }
    else
      if (wmi_management)
      {
        std::string user_name = values().get_string("wmi_user_name");
        std::string service_name = values().get_string("service_name");

        summary.append(_("Native Windows Adminstration enabled\n"));
        summary.append(strfmt(_("    Windows host:  %s\n"), host_name.c_str()));
        if (!wizard()->is_local())
          summary.append(strfmt(_("    Windows user name:  %s\n"), user_name.c_str()));
        summary.append(strfmt(_("    MySQL service name:  %s\n"), service_name.c_str()));
      }

    summary.append("\n");
    std::string os_title= serverInfo.get_string("sys.system", "Unknown");
    std::string ini_path= serverInfo.get_string("sys.config.path");
    std::string ini_section= serverInfo.get_string("sys.config.section");
    std::string mysql_version = serverInfo.get_string("serverVersion");
    summary.append(_("MySQL Configuration\n"));
    summary.append(strfmt(_("    MySQL Version:  %s\n"), mysql_version.empty() ? "Unknown" : mysql_version.c_str()));
    summary.append(strfmt(_("    Settings Template:  %s\n"), serverInfo.get_string("sys.preset").c_str()));
    summary.append(strfmt(_("    Path to Configuration File:  %s\n"), ini_path.c_str()));
    summary.append(strfmt(_("    Instance Name in Configuration File:  %s\n"), ini_section.c_str()));
    summary.append("\n");

    if (!wmi_management)
    {
      std::string start= serverInfo.get_string("sys.mysqld.start");
      std::string stop= serverInfo.get_string("sys.mysqld.stop");
      bool use_sudo= serverInfo.get_int("sys.usesudo") != 0;
      summary.append(_("Commands for MySQL Management\n"));
      summary.append(strfmt(_("    Start MySQL:  %s\n"), start.c_str()));
      summary.append(strfmt(_("    Stop MySQL:  %s\n"), stop.c_str()));
      if (os_title != "Windows")
        summary.append(strfmt(_("    Use sudo:  %s\n"), use_sudo ? _("Yes") : _("No")));
    }
    
    _text.set_value(summary);
  }
}

//--------------------------------------------------------------------------------------------------

bool ReviewPage::skip_page()
{
  return values().get_int("review_required", 0) == 0;
}

//--------------------------------------------------------------------------------------------------

bool ReviewPage::next_closes_wizard()
{
  return !_customize_check.get_active();
}

//--------------------------------------------------------------------------------------------------

void ReviewPage::leave(bool advancing)
{
  if (advancing && !_customize_check.get_active())
    wizard()->create_instance();
}

//----------------- PathsPage ----------------------------------------------------------------------

PathsPage::PathsPage(WizardForm* host, wb::WBContext* context)
  : NewServerInstancePage(host, "paths page")
{
  _context= context;
  
  set_short_title(_("MySQL Config File"));
  set_title(_("Information about MySQL configuration"));
  
  set_padding(10);
  set_spacing(20);
  _description.set_text(_("In order to manage the settings of the MySQL Server it is necessary to "
                          "know where its configuration file resides.\n\n"
                          "The configuration file may consist of several sections, each of them "
                          "belonging to a different tool or server instance. Hence it is also "
                          "necessary to know which section belongs to the server we are managing.\n\n"
                          "Please specify this information below."));
  _description.set_wrap_text(true);
  add(&_description, false, true);
  
  _content.set_column_count(4);
  _content.set_column_spacing(8);
  _content.set_row_count(5);
  _content.set_row_spacing(8);
  
  _version_label.set_text(_("MySQL Server Version:"));
  _version_label.set_text_align(MiddleRight);
  _content.add(&_version_label, 0, 1, 0, 1, HFillFlag);
  _content.add(&_version, 1, 2, 0, 1, HFillFlag);
  
  _config_path_label.set_text(_("Path to Configuration File:"));
  _config_path_label.set_text_align(MiddleRight);
  _content.add(&_config_path_label, 0, 1, 1, 2, HFillFlag);
  _content.add(&_config_path, 1, 3, 1, 2, HFillFlag);
  _content.add(&_browse_button, 3, 4, 1, 2, HFillFlag);
  
  // Setup for local config file browsing. This will be adjusted if we are at a remote location.
  _file_selector= mforms::manage(new FsObjectSelector(&_browse_button, &_config_path));
  _file_selector->initialize("", mforms::OpenFile, "", "...", true, boost::bind(&WizardPage::validate, this));

  _test_config_path_button.set_text(_("Check Path"));
  scoped_connect(_test_config_path_button.signal_clicked(),boost::bind(&PathsPage::test_path, this));
  _content.add(&_test_config_path_button, 1, 2, 2, 3, HFillFlag);
  _test_config_path_description.set_text(_("Click to test if your path is correct."));
  _content.add(&_test_config_path_description, 2, 3, 2, 3, HFillFlag | HExpandFlag);
  
  _section_name_label.set_text(_("Section of the Server Instance:"));
  _section_name_label.set_text_align(MiddleRight);
  _content.add(&_section_name_label, 0, 1, 3, 4, HFillFlag);
  _content.add(&_section_name, 1, 3, 3, 4, HFillFlag);
  
  _test_section_button.set_text(_("Check Name"));
  scoped_connect(_test_section_button.signal_clicked(),boost::bind(&PathsPage::test_section, this));
  _content.add(&_test_section_button, 1, 2, 4, 5, HFillFlag);
  _test_section_description.set_text(_("Click to test if your instance name is correct."));
  _content.add(&_test_section_description, 2, 3, 4, 5, HFillFlag | HExpandFlag);
  
  add(&_content, true, true);
}

//--------------------------------------------------------------------------------------------------

bool PathsPage::skip_page()
{
  return !(wizard()->is_admin_enabled()) || !values().get_int("customize");
}

//--------------------------------------------------------------------------------------------------

void PathsPage::enter(bool advancing)
{
  _test_config_path_description.set_color("#000000");
  _test_config_path_description.set_text(_("Click to test if your path is correct."));
  _test_section_description.set_color("#000000");
  _test_section_description.set_text(_("Click to test if your section is correct."));
 
  if (advancing)
  {
    // Prefill values from defaults.
    _version.set_value(wizard()->get_server_info("serverVersion"));
    _config_path.set_value(wizard()->get_server_info("sys.config.path"));
    _section_name.set_value(wizard()->get_server_info("sys.config.section"));
  }

  bool ssh_management = values().get_int("remoteAdmin", 0) != 0;
  if (ssh_management)
  {
    // Setup for remote browsing.
    _file_selector->set_browse_callback(boost::bind(&PathsPage::browse_remote_config_file, this));
  }
  
}

//--------------------------------------------------------------------------------------------------

bool PathsPage::advance()
{
  std::string version= base::trim(_version.get_string_value());
  int a, b, c;
  if (version.empty() || sscanf(version.c_str(), "%i.%i.%i", &a, &b, &c) < 2
      || a < 4)
  {
    Utilities::show_error(_("Invalid version"), _("The MySQL server version number provided appears to be invalid."), _("OK"));
    return false;
  }
  
  std::string path= base::trim(_config_path.get_string_value());
  if (path.empty())
  {
    Utilities::show_error(_("Empty path"), _("The path to the configuration must not be empty."), _("OK"));
    return false;
  }
  std::string section= base::trim(_section_name.get_string_value());
  if (section.empty())
  {
    Utilities::show_error(_("Empty section"), _("A section must be given which belongs to the given server."), _("OK"));
    return false;
  }
  values().gset("server_version", version);
  values().gset("ini_path", path);
  values().gset("ini_section", section);
  return true;
}

//--------------------------------------------------------------------------------------------------

/**
 * Triggers the remote file open dialog.
 */
void PathsPage::browse_remote_config_file()
{
  grt::GRT *grt = _context->get_grt();
  db_mgmt_ServerInstanceRef instance(wizard()->assemble_server_instance());

  grt::BaseListRef args(grt);
  args.ginsert(values().get("connection"));
  args.ginsert(instance);

  try
  {
    grt::StringRef selection = grt::StringRef::cast_from(grt->call_module_function("WbAdmin", "openRemoteFileSelector", args));
    if (selection.is_valid() && !selection.empty())
      _config_path.set_value(selection);
  }
  catch (const std::exception &exc)
  {
    grt->send_error("Error in remote file browser", exc.what());
  }
}

//--------------------------------------------------------------------------------------------------

void PathsPage::test_path()
{
  std::string detail;

  values().gset("ini_path", _config_path.get_string_value());
  bool success;
  try
  {
    if (values().get_int("windowsAdmin", 0) != 0 || wizard()->is_local())
      success= wizard()->test_setting("check_config_path/local", detail);
    else
      success= wizard()->test_setting("check_config_path", detail);
  }
  catch (std::exception)
  {
    success= false;
  }
  if (success)
  {
    _test_config_path_description.set_color("#00A000");
    _test_config_path_description.set_text(_("The config file path is valid."));
  }
  else
  {
    _test_config_path_description.set_color("#A00000");
    _test_config_path_description.set_text(_("The config file could not be found."));
  }
}

//--------------------------------------------------------------------------------------------------

void PathsPage::test_section()
{
  std::string detail;
  
  values().gset("ini_path", _config_path.get_string_value());
  values().gset("ini_section", _section_name.get_string_value());
  bool success;
  try
  {
    if (values().get_int("windowsAdmin", 0) != 0 || wizard()->is_local())
      success= wizard()->test_setting("check_config_section/local", detail);
    else
      success= wizard()->test_setting("check_config_section", detail);
  }
  catch (std::exception)
  {
    success= false;
  }

  if (success)
  {
    _test_section_description.set_color("#00A000");
    _test_section_description.set_text(_("The config file section is valid."));
  }
  else
  {
    _test_section_description.set_color("#A00000");
    _test_section_description.set_text(_("The config file section is invalid."));
  }
}

//----------------- CommandsPage ----------------------------------------------------------------------

CommandsPage::CommandsPage(WizardForm* host)
  : NewServerInstancePage(host, "commands page")
{
  set_short_title(_("Specify Commands"));
  set_title(_("Specify commands to be used to manage the MySQL server."));
  
  set_spacing(20);
  set_padding(8);
  _description.set_text(_("The values on this page comprise rather low level commands, which are used "
                          "to control the MySQL server instance (start or stop it) and others.\n\n"
                          "If you are unsure what these values mean leave them untouched. The defaults "
                          "are usually a good choice already (for single server machines)."));
  _description.set_wrap_text(true);
  add(&_description, false, true);
  
  _content.set_column_count(2);
  _content.set_column_spacing(8);
  _content.set_row_count(4);
  _content.set_row_spacing(5);
  
  _start_label.set_text(_("Command to start the MySQL server:"));
  _start_label.set_text_align(MiddleRight);
  _content.add(&_start_label, 0, 1, 0, 1, HFillFlag);
  _content.add(&_start_command, 1, 2, 0, 1, HFillFlag | HExpandFlag);
  
  _stop_label.set_text(_("Command to stop the MySQL server:"));
  _stop_label.set_text_align(MiddleRight);
  _content.add(&_stop_label, 0, 1, 1, 2, HFillFlag);
  _content.add(&_stop_command, 1, 2, 1, 2, HFillFlag | HExpandFlag);
    
  _use_sudo.set_text(_("Check this box if you want or need the above commands \n"
                       "to be executed with elevated Operating System Privileges."));
  _content.add(&_use_sudo, 1, 2, 3, 4, HFillFlag);
  
  add(&_content, false, true);
}

//--------------------------------------------------------------------------------------------------

bool CommandsPage::skip_page()
{
  return !(wizard()->is_admin_enabled()) || !values().get_int("customize");
}

//--------------------------------------------------------------------------------------------------

void CommandsPage::enter(bool advancing)
{
  if (advancing)
  {
    // Prefill values from defaults.
    _start_command.set_value(wizard()->get_server_info("sys.mysqld.start"));
    _stop_command.set_value(wizard()->get_server_info("sys.mysqld.stop"));
    _use_sudo.set_active(wizard()->get_server_info("sys.usesudo") != "0");
  }
}

//--------------------------------------------------------------------------------------------------

bool CommandsPage::advance()
{
  values().gset("command_start", base::trim(_start_command.get_string_value()));
  values().gset("command_stop", base::trim(_stop_command.get_string_value()));
  values().gset("use_sudo", _use_sudo.get_active());

  return true;
}

//--------------------------------------------------------------------------------------------------

void CommandsPage::leave(bool advancing)
{
  if (advancing)
    wizard()->create_instance();
}

//----------------- NewServerInstanceWizard ---------------------------------------------------------

NewServerInstanceWizard::NewServerInstanceWizard(wb::WBContext* context, db_mgmt_ConnectionRef connection)
  : WizardForm(context->get_grt_manager()), _instance(context->get_grt())
{
  set_name("new_instance_wizard");
  _context = context;
  _connection = connection;
  values().set("connection", connection);

  if (is_local())
    set_title(_("Configure Local Management"));
  else
    set_title(_("Configure Remote Management"));

  // Fill in some values from the connection that are used by the wizard pages.
  grt::DictRef parameter_values = _connection->parameterValues();
  std::string host = parameter_values.get_string("sshHost"); // SSH tunnel host name.
  if (host.empty())
    host = parameter_values.get_string("hostName"); // MySQL server host name.
  std::vector<std::string> host_parts = base::split(host, ":"); // Separate host name and port.

  if (host_parts.size() > 1)
  {
    // We come here usually only for SSH connection.
    values().gset("host_name", host_parts[0]);
    values().gset("ssh_port", host_parts[1]);
    values().gset("ssh_user_name", parameter_values.get_string("sshUserName"));
    std::string key_path = parameter_values.get_string("sshKeyFile");
    if (!key_path.empty())
      values().gset("ssh_key_path", key_path);
  }
  else
    values().gset("host_name", host);

  // Set up page structure of the wizard.
  _introduction_page = new IntroductionPage(this);
  add_page(manage(_introduction_page));

  _test_database_settings_page = new TestDatabaseSettingsPage(this);
  add_page(manage(_test_database_settings_page));

  _os_page= new HostAndRemoteTypePage(this);
  add_page(manage(_os_page));

  _ssh_configuration_page = new SSHConfigurationPage(this);
  add_page(manage(_ssh_configuration_page));
    
  _windows_connection_page = new WindowsManagementPage(this, _context);
  add_page(manage(_windows_connection_page));

  _test_host_machine_settings_page = new TestHostMachineSettingsPage(this);
  add_page(manage(_test_host_machine_settings_page));

  _review_page = new ReviewPage(this);
  add_page(manage(_review_page));
  
  _paths_page = new PathsPage(this, _context);
  add_page(manage(_paths_page));
  
  _commands_page = new CommandsPage(this);
  add_page(manage(_commands_page));
 
}

//--------------------------------------------------------------------------------------------------

NewServerInstanceWizard::~NewServerInstanceWizard()
{
  // Pages are freed by the WizardForm ancestor.
  // disconnect the test SSH session.
  std::string s;
  test_setting("disconnect", s);
}

//--------------------------------------------------------------------------------------------------

/**
 * Creates a server instance object from the current values.
 */
db_mgmt_ServerInstanceRef NewServerInstanceWizard::assemble_server_instance()
{
  db_mgmt_ConnectionRef conn(db_mgmt_ConnectionRef::cast_from(values().get("connection")));

  _instance->owner(_context->get_root()->rdbmsMgmt());
  
  std::string os = values().get_string("os");
  _instance->serverInfo().gset("sys.system", os);

  bool ssh_management= values().get_int("remoteAdmin", 0) != 0;
  _instance->serverInfo().gset("remoteAdmin", ssh_management);
  if (ssh_management)
  {
    _instance->loginInfo().gset("ssh.userName", values().get_string("ssh_user_name", ""));
    _instance->loginInfo().gset("ssh.hostName", values().get_string("host_name", "localhost"));
  }

  bool win_management= values().get_int("windowsAdmin", 0) != 0;
  _instance->serverInfo().gset("windowsAdmin", win_management);
  if (win_management)
  {
    _instance->loginInfo().gset("wmi.userName", values().get_string("wmi_user_name", ""));
    _instance->loginInfo().gset("wmi.hostName", values().get_string("host_name", "localhost"));
  }

  std::string instance_name = *conn->name() + " instance";
  std::string ssh_port= values().get_string("ssh_port", "22");
  std::string ssh_key_path= values().get_string("ssh_key_path");
  
  _instance->name(instance_name);
  _instance->loginInfo().gset("ssh.port", ssh_port);
  _instance->loginInfo().gset("ssh.useKey", !ssh_key_path.empty());
  if (!ssh_key_path.empty())
    _instance->loginInfo().gset("ssh.key", ssh_key_path);
  
  std::string version = values().get_string("server_version", "");
  if (!version.empty())
    _instance->serverInfo().gset("serverVersion", version);
  
  if (values().get_int("customize", 0))
    _instance->serverInfo().gset("sys.preset", "Custom");
  else
    _instance->serverInfo().gset("sys.preset", values().get_string("template"));
    
  if (win_management || (get_active_page_number() >= 7
      && values().get_int("customize"))) // don't overwrite defaults unless user has had time to enter them
  {
    std::string ini_path= values().get_string("ini_path");
    std::string ini_section= values().get_string("ini_section");
    std::string service_name= values().get_string("service_name");
  
    _instance->serverInfo().gset("sys.config.path", ini_path);
    _instance->serverInfo().gset("sys.config.section", ini_section);
    _instance->serverInfo().gset("sys.mysqld.service_name", service_name);
  }
  if (get_active_page_number() >= 8
      && values().get_int("customize")) // don't overwrite defaults unless user has had time to enter them
  {
    std::string start= values().get_string("command_start");
    std::string stop= values().get_string("command_stop");
    bool use_sudo= values().get_int("use_sudo") != 0;
  
    _instance->serverInfo().gset("sys.mysqld.start", start);
    _instance->serverInfo().gset("sys.mysqld.stop", stop);
    _instance->serverInfo().gset("sys.usesudo", use_sudo);
  }

  _instance->connection(db_mgmt_ConnectionRef::cast_from(values().get("connection")));
  
  return _instance;
}

//--------------------------------------------------------------------------------------------------

grt::ValueRef NewServerInstanceWizard::test_setting_grt(grt::GRT *grt, const std::string &name)
{
  std::string detail;
  if (!test_setting(name, detail))
    throw std::runtime_error(detail);
  return grt::ValueRef();
}

//--------------------------------------------------------------------------------------------------

bool NewServerInstanceWizard::test_setting(const std::string &name, std::string &detail)
{
  grt::Module *module= grtm()->get_grt()->get_module("WbAdmin");
  if (module)
  {
    grt::BaseListRef args(grtm()->get_grt());
    grt::ValueRef ret;
    args.ginsert(grt::StringRef(name));
    args.ginsert(values().get("connection"));
    args.ginsert(assemble_server_instance());

    try
    {
      ret = module->call_function("testInstanceSettingByName", args);
      if (ret.is_valid() && grt::StringRef::can_wrap(ret))
      {
        std::string s = *grt::StringRef::cast_from(ret);
        
        if (g_str_has_prefix(s.c_str(), "OK"))
        {
          if (s.size() > 3 && s[2] == ' ')
            detail = s.substr(3);
          return true;
        }
        
        // ERROR
        if (s.size() > 6 && s[5] == ' ')
          detail = s.substr(6);
        
        return false;
      }
    }
    catch (std::exception &exc)
    {
      detail = exc.what();
      return false;      
    }
  }
  return false;
}

//--------------------------------------------------------------------------------------------------

/**
 * Loads all default values for a given instance.
 */
void NewServerInstanceWizard::load_defaults()
{
  std::string template_file = values().get_string("template_path");
  if (!template_file.empty())
  {
    grt::DictRef dict;
    try
    {
      dict= grt::DictRef::cast_from(_grtm->get_grt()->unserialize(template_file));
    }
    catch (std::exception &exc)
    {
      g_warning("Instance %s contains invalid data: %s", template_file.c_str(), exc.what());
      return;
    }
    grt::merge_contents(_instance->serverInfo(), dict, true);
    _instance->serverInfo().gset("sys.preset", values().get_string("template"));
  }
}

//--------------------------------------------------------------------------------------------------

/**
 * Returns the current value for the server info at the specified key. Might be the default
 * value or one set by assemble_server_instance().
 */
std::string NewServerInstanceWizard::get_server_info(const std::string& key)
{
  grt::ValueRef value = _instance->serverInfo().get(key);
  
  if (!value.is_valid())
    return "";
  if (grt::StringRef::can_wrap(value))
    return grt::StringRef::cast_from(value);
  return value.repr();
}

//--------------------------------------------------------------------------------------------------

bool NewServerInstanceWizard::is_admin_enabled()
{
  return (values().get_int("remoteAdmin", 0) == 1) || (values().get_int("windowsAdmin", 0) == 1)  ||
    is_local();
}

//--------------------------------------------------------------------------------------------------

/**
 * Returns true if the currently selected host is the local machine.
 */
bool NewServerInstanceWizard::is_local()
{
  std::string driver = _connection->driver().is_valid() ? _connection->driver()->name() : "";
  if (driver != "MysqlNativeSSH")
  {
    std::string hostname = _connection->parameterValues().get_string("hostName");
    if (hostname == "localhost" || hostname.empty() || hostname == "127.0.0.1")
      return true;
  }
  return false;
}

//--------------------------------------------------------------------------------------------------

void NewServerInstanceWizard::create_instance()
{
  db_mgmt_ManagementRef rdbms(_context->get_root()->rdbmsMgmt());
  grt::ListRef<db_mgmt_ServerInstance> instances = rdbms->storedInstances();

  // Remove any previously defined instance for the given connection and set the new one.
  db_mgmt_ServerInstanceRef instance = assemble_server_instance();
  for (grt::ListRef<db_mgmt_ServerInstance>::const_iterator iterator = instances.begin();
    iterator != instances.end(); iterator++)
  {
    if ((*iterator)->connection() == _connection)
    {
      instances->remove(*iterator);
      break;
    }
  }

  instances.insert(instance);
}

//--------------------------------------------------------------------------------------------------

