/* 
 * Copyright (c) 2009, 2012, 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 "stdafx.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"

#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"},
{"osx10.6", "MacOS X"}, // <-- became this in MySQL 5.5
{"-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);
};

//----------------- HostMachinePage ----------------------------------------------------------------

HostMachinePage::HostMachinePage(WizardForm* host, wb::WBContext *wb)
  : NewServerInstancePage(host, "host machine page"), _context(wb), _top_box(false), 
  _top_border(BorderedPanel),
  _localhost_button(mforms::RadioButton::new_id()),
  _networkhost_button(_localhost_button.group_id()),
  _dbhost_button(_localhost_button.group_id()),
  _ip_address_block(true), _dbhost_block(true)
{
  set_short_title(_("Specify Host Machine"));
  set_title(_("Specify the Host Machine the Database Server is running on"));

  set_spacing(12);

  _intro_label.set_wrap_text(true);
  _intro_label.set_text(INTRO_TEXT);
  add(&_intro_label, false, true);
  
  _host_description.set_wrap_text(true);
  _host_description.set_text(_("If your database server is running on the same machine as this application "
    "select localhost. Otherwise please specify the TCP/IP address or the network "
    "name of the remote machine. You may also pick an existing database connection."));
  _top_box.add(&_host_description, false, true);

  _localhost_button.set_text(_("localhost"));
  scoped_connect(_localhost_button.signal_toggled(),boost::bind(&HostMachinePage::host_selection_changed, this));
  _top_box.add(&_localhost_button, false, true);
  _networkhost_button.set_text(_("Remote Host"));
  _networkhost_button.set_active(true);
  scoped_connect(_networkhost_button.signal_toggled(),boost::bind(&HostMachinePage::host_selection_changed, this));
  _top_box.add(&_networkhost_button, false, true);
  
  Label *indent;

  indent = manage(new Label(""));
#ifdef _WIN32
  indent->set_size(20, 23); // Need a minimum height or the drop down will be cropped.
#else
  indent->set_size(20, -1);
#endif
  _dbhost_block.add(indent, false, true);
  _dbhost_block.add(&_dbhost_selector, true, true);

  indent = manage(new Label(""));
  indent->set_size(20, -1);
  _ip_address_block.add(indent, false, true);
  _ip_address_label.set_text(_("Address:"));
  _ip_address_block.add(&_ip_address_label, false, true);
  _ip_address_block.add(&_ip_address, true, true);
  _ip_address_help.set_text(_("Either IP Address or Hostname"));
  _ip_address_help.set_style(mforms::SmallHelpTextStyle);
  _ip_address_block.add(&_ip_address_help, false, true);
  _ip_address_block.set_spacing(8);
  _top_box.add(&_ip_address_block, false, true);
  
  _dbhost_button.set_text(_("Take Parameters from Existing Database Connection"));
  _dbhost_button.set_active(true);
  scoped_connect(_dbhost_button.signal_toggled(),boost::bind(&HostMachinePage::host_selection_changed, this));
  _top_box.add(&_dbhost_button, false, true);
  _top_box.add(&_dbhost_block, false, true);
  
  _top_box.add(manage(new Label("")), false, true);
  
  _localhost_button.set_active(true);
  host_selection_changed();
  
  _top_box.set_spacing(8);
  _top_box.set_padding(8);
  _top_border.set_padding(MF_PANEL_PADDING);
  _top_border.add(&_top_box);

  add(&_top_border, false, true);
  
  refresh_connections();
}

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

void HostMachinePage::host_selection_changed()
{
  // Mark a local connection only if the user selected localhost. This way we can allow
  // an IP address like 127.0.0.1 to be used like a remote one (e.g. for port forwarding).
  values().gset("isLocal", _localhost_button.get_active() ? 1 : 0);

  bool flag= _networkhost_button.get_active();
  _ip_address_block.set_enabled(flag);

  flag = _dbhost_button.get_active();
  _dbhost_block.set_enabled(flag);  
}

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

void HostMachinePage::refresh_connections()
{
  grt::ListRef<db_mgmt_Connection> connections(_context->get_root()->rdbmsMgmt()->storedConns());
  
  _dbhost_selector.clear();
  for (grt::ListRef<db_mgmt_Connection>::const_iterator end = connections.end(),
       inst = connections.begin(); inst != end; ++inst)
  {
    grt::DictRef dict((*inst)->parameterValues());
    std::string tunnel;
    
    if ((*inst)->driver().is_valid())
    {
      if ((*inst)->driver()->name() == "MysqlNativeSSH")
        tunnel = "(ssh tunnel)";
      
      std::string text = strfmt("%s (User: %s  Host: %s:%i%s)",
                                (*inst)->name().c_str(),
                                dict.get_string("userName").c_str(),
                                dict.get_string("hostName").c_str(),
                                (int) dict.get_int("port"),
                                tunnel.c_str());
      
      _dbhost_selector.add_item(text);
    }
  } 
  
  _dbhost_button.set_enabled(connections.count() > 0);
}

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

bool HostMachinePage::advance()
{
  // Localhost doesn't need further attention, but general network name does.
  std::string machine;
  if (_networkhost_button.get_active())
  {
    machine= base::trim(_ip_address.get_string_value());
    if (machine.size() == 0)
    {
      Utilities::show_error(_("Invalid host name"), _("Target machine name or address is missing."), _("OK"));
      return false;
    }
    
    values().set("connection", grt::ValueRef());
  }
  else if (_dbhost_button.get_active())
  {
    int i = _dbhost_selector.get_selected_index();
    if (i < 0 || i >= (int)_context->get_root()->rdbmsMgmt()->storedConns().count())
    {
      Utilities::show_error(_("Invalid connection"), _("Please select a valid DB connection."), _("OK"));
      return false;
    }

    db_mgmt_ConnectionRef connection(_context->get_root()->rdbmsMgmt()->storedConns()[i]);
     
    if (connection->driver()->name() == "MysqlNativeSSH")
    {
      machine = connection->parameterValues().get_string("sshHost");
      if (machine.find(':') != std::string::npos)
        machine = machine.substr(0, machine.find(':'));
    }
    else if (connection->driver()->name() == "MysqlNativeSocket")
      machine = "localhost";
    else
      machine = connection->parameterValues().get_string("hostName");
    
    values().set("connection", connection);
  }
  else
  {
    values().set("connection", grt::ValueRef());
    machine = "localhost";
  }

  values().gset("host_name", machine);

  return true;
}


//----------------- DatabaseServerPage -------------------------------------------------------------

DatabaseServerPage::DatabaseServerPage(WizardForm* host, wb::WBContext* context)
: NewServerInstancePage(host, "database server page"), _panel(false)
{
  _context= context;
  
  set_short_title(_("Database Connection"));
  set_title(_("Set the Database Connection values"));
  
  _conn_name = 0;
  
  add(&_panel, true, true);
}

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

bool DatabaseServerPage::skip_page()
{
  return values().get("connection").is_valid();
}

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

void DatabaseServerPage::enter(bool advancing)
{
  wizard()->clear_problem();

  if (advancing)
  {
    if (!_conn_name)
    {
      db_mgmt_ConnectionRef conn(_context->get_grt());
      conn->name(values().get_string("host_name", "new_connection"));
      conn->parameterValues().gset("hostName", values().get_string("host_name", "new_connection"));

      _panel.init(_context->get_root()->rdbmsMgmt(), conn);
    
      _conn_name = _panel.get_name_entry();
      _conn_name->set_value(conn->name());    
    }
  }
}

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

bool DatabaseServerPage::advance()
{
  // Check if the instance name already exists.
  grt::ListRef<db_mgmt_Connection> connections = _context->get_root()->rdbmsMgmt()->storedConns();
  std::string name = base::trim(_conn_name->get_string_value());
  GRTLIST_FOREACH(db_mgmt_Connection, connections, connection)
  {
    if ((*connection)->name() == name)
    {
      wizard()->set_problem(strfmt(_("A connection with the name %s exists already. Please choose another one."), name.c_str()));
      return false;
    }
  }
  return true;
}

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

void DatabaseServerPage::leave(bool advancing)
{
  if (advancing)
  {
    _panel.get_be()->save_changes();

    db_mgmt_ConnectionRef connection(_panel.get_connection());
    std::string name = base::trim(_conn_name->get_string_value());
    connection->name(name);
    values().set("connection", connection);

    std::string machine;
    if (connection->driver()->name() == "MysqlNativeSSH")
    {
      machine = connection->parameterValues().get_string("sshHost");
      if (machine.find(':') != std::string::npos)
        machine = machine.substr(0, machine.find(':'));
    }
    else if (connection->driver()->name() == "MysqlNativeSocket")
      machine = "localhost";
    else
      machine = connection->parameterValues().get_string("hostName");    
    values().gset("host_name", machine);
    
    // insert the connection to the conn list
  }
}

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

TestDatabaseSettingsPage::TestDatabaseSettingsPage(WizardForm* host)
: WizardProgressPage(host, "test database settings page")
{
  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."));
  
  grtui::WizardProgressPage::TaskRow *task;
  
  task = 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(true, _("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("Get Server Version: Failed");
    throw std::runtime_error("Error querying version of MySQL server");
  }
  
  values().gset("server_version", version);
  
  current_task()->label.set_text("Get Server Version: "+version);
  add_log_text(strfmt("MySQL server version is %s", version.c_str()));
  
  int major, minor;
  if (sscanf(version.c_str(), "%i.%i", &major, &minor) == 2 && major < 5)
  {
    current_task()->label.set_text("Get Server Version: Unsupported Server Version");
    add_log_text(strfmt("MySQL %i.%i is older than 5.0 and is not supported by MySQL Workbench", major, minor));
    throw std::runtime_error(strfmt("MySQL %i.%i is older than 5.0 and is not supported by Workbench", major, minor));
  }
  
  return true;
}

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

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();
  
  std::string os_type = "unknown";
  
  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;
    }
  }
  
  current_task()->label.set_text("Get 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),
  _no_remote_admin(RadioButton::new_id()),
  _win_remote_admin(_no_remote_admin.group_id()),
  _ssh_remote_admin(_no_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);

  _no_remote_admin.set_text(_("Do not use remote management"));
  scoped_connect(_no_remote_admin.signal_toggled(),boost::bind(&HostAndRemoteTypePage::toggle_remote_admin, this));
  _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(&_no_remote_admin, false, true);
  _management_type_box.add(&_win_remote_admin, false, true);
  _management_type_box.add(&_ssh_remote_admin, false, true);

  _no_remote_admin.set_active(true);

  _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_panel.show(false); // Initially hidden to avoid it flashing when switching to the page and before enter is called.

  _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 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.");
  
  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();
  for (std::vector<std::pair<std::string,std::string> >::const_iterator iter= _presets[system].begin();
       iter != _presets[system].end(); ++iter)
    _type_selector.add_item(iter->first);
}

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

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

  // Clear out any OS value if we don't use remote management.
  if (_no_remote_admin.get_active() && !wizard()->is_local())
  {
    _os_panel.show(false);
  }
  else
  {
    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 (_no_remote_admin.get_active())
    {
      values().remove("template_path");
      values().remove("template");
      // Don't remove the options otherwise the profile wont work in older WB versions.
      values().gset("remoteAdmin", 0);
      values().gset("windowsAdmin", 0);
    }
    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 my.cnf database config file, read access to the database logs and "
                                "privileges to start/stop the database 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"));
    const char *username = g_get_user_name();
    _username.set_value(username ? username : "");
  }
}

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

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");
      if (!Utilities::credentials_for_service(title, "wmi@" + host, user, false, password))
      {
        _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.
      Utilities::forget_password("wmi@" + host, user);
      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")
{
  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(true, _("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);
  }
}

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

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 Server Instance 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");
  add(&_customize_check, false, true);
}

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

void ReviewPage::leave(bool advancing)
{
  if (advancing)
    values().gset("customize", _customize_check.get_active());
}

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

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()));
      }
      else
        summary.append(_("Remote management is disabled\n"));

    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(_("    OS Type:  %s\n"), os_title.c_str()));
    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");
      std::string status= serverInfo.get_string("sys.mysqld.status");
      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()));
      summary.append(strfmt(_("    Check status:  %s\n"), status.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;
}

//----------------- 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();
  grt::Module *module = grt->get_module("WbAdmin");
  if (module)
  {
    db_mgmt_ServerInstanceRef instance(wizard()->assemble_server_instance());

    grt::BaseListRef args(grt);
    args.ginsert(instance);
    
    try
    {
      grt::StringRef selection = grt::StringRef::cast_from(module->call_function("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, check its status and so on.\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(5);
  _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);
  
  _status_label.set_text(_("Status check command:"));
  _status_label.set_text_align(MiddleRight);
  _content.add(&_status_label, 0, 1, 2, 3, HFillFlag);
  _content.add(&_status_command, 1, 2, 2, 3, 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, 4, 5, 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"));
    _status_command.set_value(wizard()->get_server_info("sys.mysqld.status"));
    _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("command_status", base::trim(_status_command.get_string_value()));
  values().gset("use_sudo", _use_sudo.get_active());

  return true;
}

//----------------- CompleteSetupPage --------------------------------------------------------------

CompleteSetupPage::CompleteSetupPage(WizardForm* host, wb::WBContext* context)
  : NewServerInstancePage(host, "complete setup page"), _context(context), _instance_name_box(true)
{
  set_short_title(_("Complete Setup"));
  set_title(_("Create the Instance Profile"));

  _description.set_wrap_text(true);
  _description.set_text(_("Please enter a name for this server instance and click Next. "
    "A new Server Instance Profile entry will be created for managing this MySQL server."));
  add(&_description, false, true);

  _instance_name_box.set_spacing(8);
  _instance_name_label.set_text(_("Server Instance Name:"));
  _instance_name_box.add(&_instance_name_label, false, true);
  _instance_name_box.add(&_instance_name, true, true);
  
  add(&_instance_name_box, false, true);
}

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

bool CompleteSetupPage::advance()
{
  // Check if the instance name already exists.
  grt::ListRef<db_mgmt_ServerInstance> instances= _context->get_root()->rdbmsMgmt()->storedInstances();
  std::string name= base::trim(_instance_name.get_string_value());

  if (name.empty())
  {
    std::string text= _("Please specify a proper name for the new server instance.");
    Utilities::show_error(_("Empty instance name"), text, _("OK"));
    return false;
  }

  GRTLIST_FOREACH(db_mgmt_ServerInstance, instances, inst)
  {
    if ((*inst)->name() == name)
    {
      std::string text= strfmt(_("An instance with the name %s exists already. Please choose another one."), name.c_str());
      Utilities::show_error(_("Duplicate name"), text, _("OK"));
      return false;
    }
  }
  return true;
}

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

void CompleteSetupPage::enter(bool advancing)
{
  if (advancing)
  {
    db_mgmt_ServerInstanceRef instance(wizard()->assemble_server_instance());
        
    if (wizard()->is_admin_enabled())
      _instance_name.set_value(strfmt("%s@%s",
                                      instance->serverInfo().get_string("sys.config.section").c_str(), 
                                      values().get_string("host_name").c_str()));
    else
      _instance_name.set_value(instance->connection()->name());
  }
}

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

void CompleteSetupPage::leave(bool advancing)
{
  if (advancing)
  {
    values().gset("instance_name", _instance_name.get_string_value());
    create_instance();
  }
}

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

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

  db_mgmt_ServerInstanceRef instance= wizard()->assemble_server_instance();
  instances.insert(instance);
  
  if (rdbms->storedConns().get_index(instance->connection()) == grt::BaseListRef::npos)
    rdbms->storedConns().insert(instance->connection());
}

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

NewServerInstanceWizard::NewServerInstanceWizard(wb::WBContext* context)
  : WizardForm(context->get_grt_manager()), _instance(context->get_grt())
{
  set_title(_("Create New Server Instance Profile"));
    
  _context = context;

  _host_machine_page= new HostMachinePage(this, context);
  add_page(manage(_host_machine_page));
  
  _database_server_page= new DatabaseServerPage(this, _context);
  add_page(manage(_database_server_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));
  
  _complete_setup_page= new CompleteSetupPage(this, _context);
  add_page(manage(_complete_setup_page));
  
}

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

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

extern std::string get_admin_script_for_os(const std::string &os);

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

/**
 * Creates a server instance object from the current values.
 */
db_mgmt_ServerInstanceRef NewServerInstanceWizard::assemble_server_instance()
{

  _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= values().get_string("instance_name", "new 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"));
  
  _instance->serverInfo().gset("sys.script", get_admin_script_for_os(os));
  
  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");
    std::string status= values().get_string("command_status");
    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.mysqld.status", status);
    _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(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()
{
  // A local connection is not defined by an IP address (as port forwarding could be active)
  // but by the user himself, as he should know if it is a local host or not.
  int isLocal = values().get_int("isLocal");
  return isLocal != 0;
}

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

