/*
 * Copyright (c) 2008, 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 <glib/gstdio.h>

#include "wb_context_ui.h"

#include "base/file_functions.h"
#include "base/file_utilities.h"
#include "base/string_utilities.h"
#include "base/log.h"

#include "server_instance_editor.h"
#include "grtui/grtdb_connection_editor.h"
#include "new_server_instance_wizard.h"
#include "new_connection_wizard.h"
#include "edit_sql_script_wizard.h"
#include "edit_table_data_wizard.h"
#include "select_option_dialog.h"


#define MODEL_AUTOSAVED_ICON_FILE "db_model_warning.png"
#define SQLEDITOR_AUTOSAVED_ICON_FILE "db_connection_warning.png"

DEFAULT_LOG_DOMAIN("WBContextUI");

using namespace bec;
using namespace wb;
using namespace base;

/**
 * Helper method to construct a human-readable server description.
 */
std::string get_server_info(db_mgmt_ServerInstanceRef instance)
{
  std::string text;
  std::string system = instance->serverInfo().get_string("sys.system");

  if (instance->serverInfo().get_int("remoteAdmin"))
    text = strfmt("Host: %s  Type: %s", instance->loginInfo().get_string("ssh.hostName").c_str(),
      system.c_str());
  else
  {
    if (instance->serverInfo().get_int("windowsAdmin")/* || system == "Windows"*/)
    {
      std::string host = instance->loginInfo().get_string("wmi.hostName");
      if (host == "localhost" || host.empty() || host == "127.0.0.1")
      {
        text = "Local  Type: Windows";
      }
      else
        text = "Host: " + host + "  Type: Windows";
    }
    else
    {
      std::string host = instance->connection().is_valid() ?
        instance->connection()->parameterValues().get_string("hostName") : "Invalid";

      if (host == "localhost" || host.empty() || host == "127.0.0.1")
      {
        text = strfmt("Local  Type: %s", system.c_str());
      }
      else
        text = strfmt("Host: %s  Type: DB Only", host.c_str());
    }
  }

  return text;
}

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

static void save_state(WBContextUI *owner, HomeScreen *home)
{
  if (!home->get_wb_central_expanded())
    owner->get_wb()->save_state("WBCentralCollapsedInVersion", "home",
                   strfmt("%i.%i.%i", APP_MAJOR_NUMBER, APP_MINOR_NUMBER, APP_RELEASE_NUMBER));
  else
    owner->get_wb()->save_state("WBCentralCollapsedInVersion", "home", std::string(""));  
  
  owner->get_wb()->save_state("WBHomeGroupStateAdministration", "home", home->get_collapsed_group_data(HomeScreenAdministrationList));  
  owner->get_wb()->save_state("WBHomeGroupStateSQLDev", "home", home->get_collapsed_group_data(HomeScreenSQLDevList));  
}

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


  template <class T>
  void get_groups_for_movement(grt::ListRef<T> items, const grt::ValueRef &object, std::vector<std::string> &groups)
  {
    grt::Ref<T> item = grt::Ref<T>::cast_from(object);

    std::string item_name = item->name();
    size_t current_group_separator_position = item_name.find("/");

    std::string current_group = "";
    
    if (current_group_separator_position != std::string::npos)
    {
      current_group = item_name.substr(0, current_group_separator_position);
      groups.push_back("*Ungrouped*");
    }

    for (typename grt::ListRef<T>::const_iterator end = items.end(),
          inst = items.begin(); inst != end; ++inst)
    {
      std::string name = (*inst)->name();
      size_t group_separator_position = name.find("/");

      if (group_separator_position != std::string::npos)
      {
        std::string group_name = name.substr(0, group_separator_position);

        bool found = false;
        for (std::vector<std::string>::iterator end = groups.end(),
            index = groups.begin(); index != end && !found; index++)
        {
          found = (*index).compare(0, group_separator_position, group_name) == 0;
        }

        if (!found && group_name != current_group)
          groups.push_back(group_name);
      }
    }
  }

  template <class T>
  bool validate_group_for_movement(grt::ListRef<T> items, const grt::ValueRef &object, std::string group)
  {
    bool ret_val = false;

    // Validates the group is valid...
    size_t group_position = group.find("/");

    if( group.length() == 0 )
      Utilities::show_warning(_("Move To Group"), _("You must select the target group from the list or type a new group."),  _("Ok"));
    else if (group_position != std::string::npos)
      Utilities::show_warning(_("Move To Group"), _("The selected group is invalid, should not contain the \"/\" character."),  _("Ok"));
    else
    {
      // Gets the connection and connection list...
      grt::Ref<T> item = grt::Ref<T>::cast_from(object);

      // Creates the new connection name...
      std::string item_name = item->name();
      std::string new_item_name = "";

      group_position = item_name.find("/");

      if (group == "*Ungrouped*")
        new_item_name = item_name.substr(group_position + 1);
      else
      {
        if (group_position != std::string::npos)
          new_item_name = group + "/" + item_name.substr(group_position + 1);
        else
          new_item_name = group + "/" + item_name;
      }

      size_t item_index = bec::find_list_ref_item_position<T>(items, new_item_name, MatchAny, NULL, FindFull);

      if (item_index != grt::BaseListRef::npos)
        Utilities::show_warning(_("Move To Group"), _("Unable to perform the movement as there's an entry with the same name in the target group"),  _("Ok"));
      else
        ret_val = true;
    }

    return ret_val;
  }

  template <class T>
  void update_item_group(const grt::ValueRef &object, std::string group)
      // At this point the movement is considered valid so we just do it...
  {
    grt::Ref<T> item = grt::Ref<T>::cast_from(object);
    std::string item_name = item->name();
    size_t current_group_separator_position = item_name.find("/");

    std::string new_item_name = "";
    if (group == "*Ungrouped*")
      new_item_name = item_name.substr(current_group_separator_position + 1);
    else
    {
      if (current_group_separator_position != std::string::npos)
        new_item_name = group + "/" + item_name.substr(current_group_separator_position + 1);
      else
        new_item_name = group + "/" + item_name;
    }

    item->name(new_item_name);
  }

  template <class T>
  void move_item_to_group(std::string group, grt::ListRef<T> items, const grt::ValueRef &object)
  {
    size_t current_item_index, current_group_index, sibling_index, target_index;
    bool sibling_move = false;
    bool item_move = false;
    
    // To ensure the grouping order is maintained after a move to group operation it is needed to consider
    // - If the item being moved is in a group or not
    // - If the item is being moved to a group or not
    // - Removing the first item from a group may affect the group ordering, to prevent that, the next item in the group 
    //   is being moved to the position of the first item
    // - When moved to a group, the item will be placed after the last element in the group
    // - Moving to a new group or ungrouping the item doesn't affect it's position on the list

    //grt::Type object_type = object.type();
    
    grt::Ref<T> item = grt::Ref<T>::cast_from(object);
    std::string item_name = item->name();

    std::string item_group = "";
    size_t group_indicator_position = item_name.find("/");

    // Gets the position of the item being moved...
    current_item_index = find_list_ref_item_position<T>(items, item_name);
    if (group_indicator_position != std::string::npos)
    {
      item_group = item_name.substr(0, group_indicator_position + 1);

      // Gets the position of the first element on the group
      current_group_index = find_list_ref_item_position<T>(items, item_group);

      if (current_item_index == current_group_index)
      {
        sibling_index = find_list_ref_item_position<T>(items, item_group, MatchAfter, &item);

        if (sibling_index != grt::BaseListRef::npos)
          sibling_move = true;
      }
    }

    if (group != "*Ungrouped*")
    {
      // Gets the position of the last element on the target group if exists ( happily the function does that when passing MatchBefore without a reference )
      std::string tmp_group = group + "/";
      target_index = find_list_ref_item_position<T>(items, tmp_group , MatchLast);

      if (target_index != grt::BaseListRef::npos)
      {
        item_move = true;

        if (target_index < current_item_index)
          target_index++;
      }
    }

    if (sibling_move)
    {
      items.reorder(sibling_index, current_item_index);

      if (sibling_index > current_item_index)
        current_item_index++;
    }

    if (item_move)
      items.reorder(current_item_index, target_index);

    update_item_group<T>(object, group);
  }  

/**
 * Creates the main home screen (Workbench Central, Workspace) if not yet done and docks it to 
 * the main application window.
 */
void WBContextUI::show_home_screen()
{
  if (_home_screen == NULL)
  {    
    _home_screen= mforms::manage(new HomeScreen(&_command_ui, _wb->get_grt()));
    _home_screen->set_on_close(boost::bind(&WBContextUI::home_screen_closing, this));
    _home_screen->set_callback((home_screen_action_callback)&WBContextUI::home_action_callback, this);
    _home_screen->handle_context_menu = boost::bind(&WBContextUI::handle_home_context_menu, this, _1, _2, _3);
    
    // setup context menus
    bec::MenuItemList items, pitems;
    bec::MenuItem item, sep;
    
    std::string value = _wb->read_state("WBCentralCollapsedInVersion", "home", std::string());
    if (value == strfmt("%i.%i.%i", APP_MAJOR_NUMBER, APP_MINOR_NUMBER, APP_RELEASE_NUMBER))
      _home_screen->set_wb_central_expanded(false);

    sep.type = bec::MenuSeparator;
    item.name= "open_connection";
    item.caption= "Query Database";
    items.push_back(item);
    {
      std::list<std::string> groups;
      bec::ArgumentPool argument_pool;
      _wb->update_plugin_arguments_pool(argument_pool);
      argument_pool.add_entries_for_object("selectedConnection", db_mgmt_ConnectionRef(_wb->get_grt()), "db.mgmt.Connection");
      groups.push_back("Menu/Home/Connections");
      pitems = _wb->get_grt_manager()->get_plugin_context_menu_items(groups, argument_pool);
      if (!pitems.empty())
      {
        items.push_back(sep);
        items.insert(items.end(), pitems.begin(), pitems.end());
      }
    }
    if (!items.empty())
      items.push_back(sep);

    item.name= "move_connection_up";
    item.caption= "Move Up";
    items.push_back(item);
    
    item.name= "move_connection_down";
    item.caption= "Move Down";
    items.push_back(item);
    
    item.name= "move_connection_to_group";
    item.caption= "Move to Group...";
    items.push_back(item);
    
    items.push_back(sep);

    item.name= "delete_connection";
    item.caption= "Delete Connection...";
    items.push_back(item);

    item.caption= "Delete All Connections...";
    item.name= "delete_connection_all";
    items.push_back(item);

    _home_screen->set_context_menu_items(HomeScreenSQLDevList, items);
    _home_screen->set_collapsed_group_data(HomeScreenSQLDevList, _wb->read_state("WBHomeGroupStateSQLDev", "home", std::string()));
    
    items.clear();

    item.name= "move_connection_up";
    item.caption= "Move Up";
    items.push_back(item);
    
    item.name= "move_connection_down";
    item.caption= "Move Down";
    items.push_back(item);
    
    items.push_back(sep);
    
    item.name= "delete_connection_group";
    item.caption= "Delete Group";
    items.push_back(item);

    _home_screen->set_context_menu_items(HomeScreenSQLDevList, items, true);
    
    //
    items.clear();
    item.name= "open_model";
    item.caption= "Open Model File";
    items.push_back(item);    
    {
      std::list<std::string> groups;
      bec::ArgumentPool argument_pool;
      _wb->update_plugin_arguments_pool(argument_pool);
      argument_pool.add_simple_value("selectedModelFile", grt::ValueRef());
      groups.push_back("Menu/Home/ModelFiles");
      pitems = _wb->get_grt_manager()->get_plugin_context_menu_items(groups, argument_pool);
      if (!pitems.empty())
      {
        items.push_back(sep);
        items.insert(items.end(), pitems.begin(), pitems.end());
      }      
    }
    if (!items.empty())
      items.push_back(sep);    
    item.name= "remove_model";
    item.caption= "Remove Model File from List";
    items.push_back(item);
    
    item.caption= "Clear List";
    item.name= "remove_model_all";
    items.push_back(item);    
    _home_screen->set_context_menu_items(HomeScreenModellingList, items);
    
    //
    items.clear();
    item.name= "open_administrator";
    item.caption= "Manage Instance";
    items.push_back(item);        
    {
      std::list<std::string> groups;
      bec::ArgumentPool argument_pool;
      _wb->update_plugin_arguments_pool(argument_pool);
      argument_pool.add_entries_for_object("selectedInstance", db_mgmt_ServerInstanceRef(_wb->get_grt()), "db.mgmt.ServerInstance");
      groups.push_back("Menu/Home/Instance");
      pitems = _wb->get_grt_manager()->get_plugin_context_menu_items(groups, argument_pool);
      if (!pitems.empty())
      {
        items.push_back(sep);
        items.insert(items.end(), pitems.begin(), pitems.end());
      }      
    }
    if (!items.empty())
      items.push_back(sep);

    item.name= "move_instance_up";
    item.caption= "Move Up";
    items.push_back(item);
	  
    item.name= "move_instance_down";
    item.caption= "Move Down";
    items.push_back(item);

    item.name= "move_instance_to_group";
    item.caption= "Move to Group...";
    items.push_back(item);

    items.push_back(sep);
    
    item.name= "delete_instance";
    item.caption= "Delete Server Instance...";
    items.push_back(item);

    item.caption= "Delete All Server Instances...";
    item.name= "delete_instance_all";
    items.push_back(item);
    
    _home_screen->set_context_menu_items(HomeScreenAdministrationList, items);
    _home_screen->set_collapsed_group_data(HomeScreenAdministrationList, _wb->read_state("WBHomeGroupStateAdministration", "home", std::string()));
    
    items.clear();
    
    item.name= "move_instance_up";
    item.caption= "Move Up";
    items.push_back(item);
    
    item.name= "move_instance_down";
    item.caption= "Move Down";
    items.push_back(item);
    
    items.push_back(sep);
    
    item.name= "delete_instance_group";
    item.caption= "Delete Group";
    items.push_back(item);
    
    _home_screen->set_context_menu_items(HomeScreenAdministrationList, items, true);
    

  }

  mforms::App::get()->dock_view(_home_screen, "maintab");
//  mforms::App::get()->set_view_title(_home_screen, "Home"); we use an icon for that page.
  
  refresh_home_models();
  refresh_home_instances(0);
  refresh_home_connections();
  refresh_home_starters();
}

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

/**
 * Called when the home screen is closed by the UI. We have to clear our reference then.
 */
bool WBContextUI::home_screen_closing()
{  
  if (_home_screen)
  {
    save_state(this, _home_screen);
    _home_screen->release();
  }
  _home_screen= NULL;
  return true;
}

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

void WBContextUI::home_action_callback(HomeScreenAction action, const grt::ValueRef &object, WBContextUI *self)
{
  try
  {
    self->handle_home_action(action, object);
  }
  catch (const std::exception &exc)
  {
    mforms::Utilities::show_error("Internal Error",
                                  base::strfmt("Exception caught while processing action from home screen: %s", exc.what()), "OK");
  }
}

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

void WBContextUI::starter_action_callback(StarterAction action, const app_StarterRef &object,
                                          void *self)
{
  try
  {
    ((WBContextUI*) self)->handle_starter_action(action, object);
  }
  catch (const std::exception &exc)
  {
    mforms::Utilities::show_error("Internal Error",
                                  base::strfmt("Exception caught while processing plugin action: %s", exc.what()), "OK");
  }
}

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

void WBContextUI::handle_home_context_menu(HomeScreenListType list, const grt::ValueRef &object, const std::string &action)
{
  if (action == "open_connection")
  {
    handle_home_action(ActionOpenConnectionFromList, object);
  }
  else if (action == "delete_connection")
  {
    db_mgmt_ConnectionRef connection(db_mgmt_ConnectionRef::cast_from(object));
    
    std::string name= connection->name();
    std::string text= strfmt(_("Do you want to delete connection %s?"), name.c_str());
    int answer= Utilities::show_warning(_("Delete Connection"), text,  _("Delete"), _("Cancel"));
    if (answer == mforms::ResultOk)
    {
      grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns());
      connections->remove(connection);
      refresh_home_connections();
    }
  }
  else if (action == "move_connection_up")
  {
    grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns());
    
    bec::move_list_ref_item<db_mgmt_Connection>(MoveUp, connections, object);
    
    refresh_home_connections();
    _home_screen->select_list_entry(HomeScreenSQLDevList, object);
  }
  else if (action == "move_connection_down")
  {
    grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns());
    
    bec::move_list_ref_item<db_mgmt_Connection>(MoveDown, connections, object);
    
    refresh_home_connections();
    _home_screen->select_list_entry(HomeScreenSQLDevList, object);
  }
  else if (action == "move_connection_to_group")
  {
    grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns());
    
    // Create the available groups for the movement...
    std::vector<std::string> groups;
    get_groups_for_movement<db_mgmt_Connection>(connections, object, groups);

    SelectOptionDialog dialog("Move To Group", "Pick a group to move the selected connection to\nor type a name to move it to a new one.", groups);
    dialog.set_validation_function(boost::bind(&validate_group_for_movement<db_mgmt_Connection>, connections, object, _1));
    std::string result = dialog.run();

    // At this point the movement is considered valid so we just do it...
    if( result != "")
    {
      move_item_to_group<db_mgmt_Connection>(result, connections, object);
      refresh_home_connections();
      _home_screen->select_list_entry(HomeScreenSQLDevList, object);
    }
  }
  else if (action == "delete_connection_group")
  {
    std::string group = object.repr();
    std::string text= strfmt(_("Do you really want to delete all the connections in group: %s?"), group.c_str());
    int answer= Utilities::show_warning(_("Delete Connection Group"), text,  _("Delete"), _("Cancel"));
    if (answer == mforms::ResultOk)
    {
      group +="/";
      int group_length = group.length();
      
      grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns());

      int index = connections.count() - 1;
      while (index >= 0)
      {
        std::string name = connections[index]->name();
        
        if (name.compare(0, group_length, group) == 0)
          connections->remove(index);

        index--;
      }

      refresh_home_connections();
    }
  }
  else if (action == "delete_connection_all")
  {
    std::string text= _("Do you really want to delete all defined connections?");
    int answer= Utilities::show_warning(_("Delete All Connections"), text,  _("Delete"), _("Cancel"));
    if (answer == mforms::ResultOk)
    {
      grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns());
      while (connections->count() > 0)
        connections->remove(0);
      refresh_home_connections();
    }
  }
  else if (action == "open_model")
  {
    handle_home_action(ActionOpenEERModelFromList, object);
  }
  else if (action == "remove_model")
  {
    _wb->get_root()->options()->recentFiles()->remove(object);
    std::string file = grt::StringRef::cast_from(object);
    if (file.size() > 5 && file.substr(file.size()-5) == ".mwbd")
    {
      try
      {
        base::remove_recursive(file);
      }
      catch (std::exception &exc)
      {
        log_error("Error removing model file %s: %s\n", file.c_str(), exc.what());
      }
    }
    refresh_home_models();
  }
  else if (action == "remove_model_all")
  {
    std::string text= _("Do you want to remove all entries from the model list?");
    int answer= Utilities::show_warning(_("Clear Model Entry List"), text,  _("Delete"), _("Cancel"));
    if (answer == mforms::ResultOk)
    {
      grt::StringListRef file_names(_wb->get_root()->options()->recentFiles());
      for (int index= file_names->count() - 1; index >= 0; index--)
      {
        if (g_str_has_suffix(file_names[index].c_str(), ".mwb"))
          file_names->remove(index);
      }
      refresh_home_models();
    }
  }
  else if (action == "open_administrator")
  {
    handle_home_action(ActionManageInstanceFromList, object);
  }
  else if (action == "delete_instance")
  {
    db_mgmt_ServerInstanceRef instance(db_mgmt_ServerInstanceRef::cast_from(object));
    
    std::string name= instance->name();
    std::string text= strfmt(_("Do you want to delete server instance %s?"), name.c_str());
    int answer= Utilities::show_warning(_("Delete Server Instance"), text,  _("Delete"), _("Cancel"));
    if (answer == mforms::ResultOk)
    {
      grt::ListRef<db_mgmt_ServerInstance> instances(_wb->get_root()->rdbmsMgmt()->storedInstances());
      instances->remove(instance);
      refresh_home_instances();
    }
  }
  else if (action == "move_instance_up")
  {
    grt::ListRef<db_mgmt_ServerInstance> instances(_wb->get_root()->rdbmsMgmt()->storedInstances());
    
    bec::move_list_ref_item<db_mgmt_ServerInstance>(MoveUp, instances, object);
    
    refresh_home_instances();
    _home_screen->select_list_entry(HomeScreenAdministrationList, object);
  }
  else if (action == "move_instance_down")
  {
    grt::ListRef<db_mgmt_ServerInstance> instances(_wb->get_root()->rdbmsMgmt()->storedInstances());
    
    bec::move_list_ref_item<db_mgmt_ServerInstance>(MoveDown, instances, object);
    
    refresh_home_instances();
    _home_screen->select_list_entry(HomeScreenAdministrationList, object);
  }
  else if (action == "move_instance_to_group")
  {
    grt::ListRef<db_mgmt_ServerInstance> instances(_wb->get_root()->rdbmsMgmt()->storedInstances());
    
    // Create the available groups for the movement...
    std::vector<std::string> groups;
    get_groups_for_movement<db_mgmt_ServerInstance>(instances, object, groups);

    SelectOptionDialog dialog("Move To Group", "Pick a group to move the selected server instance to\nor type a name to move it to a new one.", groups);
    dialog.set_validation_function(boost::bind(&validate_group_for_movement<db_mgmt_ServerInstance>, instances, object, _1));
    std::string result = dialog.run();

    if( result != "")
    {
      move_item_to_group<db_mgmt_ServerInstance>(result, instances, object);
      refresh_home_instances();
      _home_screen->select_list_entry(HomeScreenAdministrationList, object);
    }

  }  
  else if (action == "delete_instance_group")
  {
    std::string group = object.repr();
    std::string text= strfmt("Do you really want to delete all the server instances in group : %s?", group.c_str());
    int answer= Utilities::show_warning(_("Delete Server Instance Group"), text,  _("Delete"), _("Cancel"));
    if (answer == mforms::ResultOk)
    {
      group +="/";
      int group_length = group.length();
      
      grt::ListRef<db_mgmt_ServerInstance> instances(_wb->get_root()->rdbmsMgmt()->storedInstances());
      
      int index = instances.count() - 1;
      while (index >= 0)
      {
        std::string name = instances[index]->name();
        
        if (name.compare(0, group_length, group) == 0)
          instances->remove(index);
        
        index--;
      }
      
      refresh_home_instances();
    }
  }
  
  else if (action == "delete_instance_all")
  {
    std::string text= _("Do you really want to delete all defined server instances?");
    int answer= Utilities::show_warning(_("Delete All Server Instances"), text,  _("Delete"), _("Cancel"));
    if (answer == mforms::ResultOk)
    {
      grt::ListRef<db_mgmt_ServerInstance> instances(_wb->get_root()->rdbmsMgmt()->storedInstances());
      while (instances->count() > 0)
        instances->remove(0);
      refresh_home_instances();
    }
  }
  else 
  {
    bec::ArgumentPool argument_pool;
    _wb->update_plugin_arguments_pool(argument_pool);
    
    switch (list)
    {
      case HomeScreenSQLDevList:
        argument_pool.add_entries_for_object("selectedConnection", db_mgmt_ConnectionRef::cast_from(object));
        break;
      case HomeScreenModellingList:
        argument_pool.add_simple_value("selectedModelFile", grt::StringRef::cast_from(object));
        break;
      case HomeScreenAdministrationList:
        argument_pool.add_entries_for_object("selectedInstance", db_mgmt_ServerInstanceRef::cast_from(object));
        break;        
    }

    get_command_ui()->activate_command(action, argument_pool);
  }
}


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

void WBContextUI::start_plugin(const std::string& title, const std::string& command, bool force_external)
{
  try
  {
    std::string message_title= base::strfmt(_("Starting %s"), title.c_str());
    GUILock lock(_wb, message_title, _("Please stand by while the plugin is started ..."));
    if (base::starts_with(command, "plugin:"))
    {
      _wb->execute_plugin(command.substr(7, command.length()));
    }
    else
      if (base::starts_with(command, "browse:"))
        show_web_page(command.substr(7, command.length()), !force_external);
      else
        if (base::starts_with(command, "http://"))
          show_web_page(command, false);
  }
  catch (const std::exception &exc)
  {
    mforms::Utilities::show_error(base::strfmt("Could not open link or plugin"),
                                  exc.what(), "OK");
  }
}

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

void WBContextUI::handle_home_action(HomeScreenAction action, const grt::ValueRef &object)
{
  switch (action)
  {
    case ActionNone:
      break;
    case ActionWhatsNew:
    {
      GUILock lock(_wb, _("Opening 'What's New' web page"), _("The MySQL Workbench news web page is being opened "
                                                              "and should be available in a moment.\n\nPlease stand by..."));
      show_web_page("http://wb.mysql.com/?page_id=49", true);
    }
      break;
      
    case ActionStarter:
    {
      app_StarterRef starter= app_StarterRef::cast_from(object);
      start_plugin(starter->title(), starter->command());
    }
      break;
      
    case ActionStarterPopup:
    {
      StarterPopup popup(_wb);
      popup.set_callback(&WBContextUI::starter_action_callback, this);
      
      grt::DictRef values= grt::DictRef::cast_from(object);
      int x= values.get_int("left");
      int y= values.get_int("top");
      
      popup.show(x, y);
      break;
    }
      
    case ActionRemoveStarter:
    {
      int index= grt::IntegerRef::cast_from(object);
      _wb->get_root()->starters()->displayList()->remove(index);
      _wb->save_starters();
      refresh_home_starters();
    }
      break;
      
    case ActionOpenConnection:
    {
      _wb->show_status_text("Opening SQL Editor...");
      _wb->add_new_query_window(db_mgmt_ConnectionRef());
      break;
    }
      
    case ActionOpenConnectionFromList:
    {
      if (object.is_valid())
      {
        db_mgmt_ConnectionRef connection(db_mgmt_ConnectionRef::cast_from(object));
        //GUILock lock(_wb, _("Opening SQL Editor"), strfmt(_("An SQL editor instance for '%s' is opening and should be available in a "
        //                                                    "moment.\n\nPlease stand by..."), connection->name().c_str()));
        
        _wb->show_status_text("Opening SQL Editor...");          
        _wb->add_new_query_window(connection);
      }
      break;
    }
      
    case ActionNewConnection:
    {
      NewConnectionWizard wizard(_wb->get_root()->rdbmsMgmt());
      wizard.set_title("Setup New Connection");
      wizard.run();
      _wb->save_app_options();
      refresh_home_connections();
      break;
    }
      
    case ActionAddSchemaObject:
      break;
    case ActionEditTable:
    {
      EditTableDataWizard wizard(_wb);
      _wb->show_status_text("Edit Table Data Wizard Opened.");
      wizard.run_modal();
      _wb->show_status_text("");
      break;
    }
    case ActionEditSQLScript:
    {
      EditSqlScriptWizard wizard(_wb);
      _wb->show_status_text("Edit SQL Script Wizard Opened.");
      wizard.run_modal();
      _wb->show_status_text("");
      break;
    }
      
    case ActionManageConnections:
    {
      grtui::DbConnectionEditor editor(_wb->get_root()->rdbmsMgmt());
      _wb->show_status_text("Connection Manager Opened.");
      editor.run();
      _wb->show_status_text("");
      // save connection list now
      _wb->save_app_options();
      refresh_home_connections();        
      break;
    }
      
    case ActionOpenEERModel:
    {
      // Note: wb->open_document has an own GUILock, so we must not set another one here.
      std::string filename = _wb->show_file_dialog("open", _("Open Workbench Model"), "mwb");
      if (!filename.empty())
        _wb->open_document(filename);
      else
        _wb->show_status_text("Cancelled");
      break;
    }
      
    case ActionOpenEERModelFromList:
    {
      // Note: wb->open_document has an own GUILock, so we must not set another one here.
      if (object.is_valid())
      {
        std::string path = *grt::StringRef::cast_from(object);
        _wb->show_status_text(strfmt("Opening %s...", path.c_str()));
        _wb->open_document(path);
      }
      break;
    }
    case ActionNewEERModel:
    {
      _wb->new_document();
    }
      break;
      
    case ActionNewModelFromDB:
    {
      _wb->new_document();          
      if (_wb->get_document().is_valid())
      {
        ArgumentPool args;
        
        // delete the default schema
        if (_wb->get_document()->physicalModels()[0]->catalog()->schemata().count() > 0)
          _wb->get_document()->physicalModels()[0]->catalog()->schemata().remove(0);
        
        _wb->update_plugin_arguments_pool(args);
        args.add_entries_for_object("activeCatalog", _wb->get_document()->physicalModels()[0]->catalog(),
                                    "db.Catalog");
        
        _wb->execute_plugin("db.plugin.database.rev_eng", args);
      }
      else
        _wb->show_status_text("Error creating document");
      break;
    }
      
    case ActionNewModelFromScript:
    {
      _wb->new_document();
      if (_wb->get_document().is_valid())
      {
        ArgumentPool args;
        
        // delete the default schema
        if (_wb->get_document()->physicalModels()[0]->catalog()->schemata().count() > 0)
          _wb->get_document()->physicalModels()[0]->catalog()->schemata().remove(0);
        
        _wb->update_plugin_arguments_pool(args);
        args.add_entries_for_object("activeCatalog", _wb->get_document()->physicalModels()[0]->catalog(),
                                    "db.Catalog");
        _wb->execute_plugin("db.mysql.plugin.import.sql", args);
      }
      else
        _wb->show_status_text("Error creating document");
      break;
    }
      
    case ActionServerAdministration:
    {          
      app_PluginRef plugin(_wb->get_plugin_manager()->get_plugin("wb.admin.placeholder"));
      
      if (plugin.is_valid())
        try
      {
        grt::BaseListRef args(_wb->get_grt_manager()->get_grt(), grt::AnyType);
        _wb->get_plugin_manager()->open_plugin(plugin, args);
      }
      catch(grt::module_error& err)
      {
        std::string msg = strfmt("Error in wb.admin module: %s",err.what());
        _wb->show_status_text(msg);
      }
      break;
    }      
      
    case ActionManageInstanceFromList:
    {
      if (object.is_valid())
      {
        db_mgmt_ServerInstanceRef instance(db_mgmt_ServerInstanceRef::cast_from(object));
        if (!instance->connection().is_valid())
          mforms::Utilities::show_error("Cannot Open Administrator",
                                        "The selected server instance does not have a MySQL server connection assigned and cannot be opened.",
                                        "OK");
        else
        {
          GUILock lock(_wb, _("Opening MySQL Workbench Administrator"), strfmt(_("The MySQL Workbench Administrator for %s "
                                                                                 "is being opened and should be available in a moment.\n\nPlease stand by..."), instance->name().c_str()));
          _wb->add_new_admin_window(instance);
        }
      }
      break;
    }
      
    case ActionNewServerInstance:
    {
      NewServerInstanceWizard wizard(_wb);
      
      _wb->show_status_text(_("Creating new server instance"));
      if (wizard.run_modal())
      {
        _wb->save_app_options();
        refresh_home_connections();
        refresh_home_instances((int)_wb->get_root()->rdbmsMgmt()->storedInstances().count()-1);
        _wb->show_status_text(_("New server instance created"));
      }
      else
        _wb->show_status_text(_("New server instance creation cancelled"));

      break;
    }
      
    case ActionDumpRestore:
    {
      grt::BaseListRef args(_wb->get_grt_manager()->get_grt(), grt::AnyType);
      
      app_PluginRef plugin(_wb->get_plugin_manager()->get_plugin("wb.admin.dumpManager"));
      
      if (plugin.is_valid())
        try
      {
        _wb->get_plugin_manager()->open_plugin(plugin, args);
      }
      catch(grt::module_error& err)
      {
        std::string msg = strfmt("Error in dump module: %s",err.what());
        _wb->show_status_text(msg);
      }
      break;
    }
      
    case ActionManageSecurity:
    {
      grt::BaseListRef args(_wb->get_grt_manager()->get_grt(), grt::AnyType);
      
      app_PluginRef plugin(_wb->get_plugin_manager()->get_plugin("wb.admin.securityManager"));
      
      if (plugin.is_valid())
        try
      {
        _wb->get_plugin_manager()->open_plugin(plugin, args);
      }
      catch(grt::module_error& err)
      {
        std::string msg = strfmt("Error in securityManager module: %s",err.what());
        _wb->show_status_text(msg);
      }
      break;
    }
    case ActionManageServerInstances:
    {
      ServerInstanceEditor editor(_wb->get_grt_manager(), _wb->get_root()->rdbmsMgmt());
      _wb->show_status_text("Server Instance Manager Opened.");
      db_mgmt_ServerInstanceRef instance(editor.run());
      _wb->show_status_text("");
      // save instance list now
      _wb->save_app_options();
      refresh_home_connections();
      refresh_home_instances((int)_wb->get_root()->rdbmsMgmt()->storedInstances().get_index(instance));        
      break;
    }
  }
}

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

void WBContextUI::handle_starter_action(StarterAction action, const app_StarterRef &starter)
{
  int index= 0;
  switch (action)
  {
    case StarterPluginCentral:
    {
      GUILock lock(_wb, _("Opening Plugin Central"), _("The MySQL Workbench Plugin Central web page "
                                                       "is being opened and should be available in a moment.\n\nPlease stand by..."));
      show_web_page("http://wb.mysql.com/wb-plugins/", true);
    }
      break;
      
    case StarterStartPlugin:
    {
      std::string title= starter->title();
      std::string command= starter->command();
      start_plugin(title, command);
    }
      break;
      
    case StarterStartPluginExternal:
    {
      std::string title= starter->title();
      std::string command= starter->command();
      start_plugin(title, command, true);
    }
      break;
      
    /* Bug 12613922
    case StarterFindPlugin:
    {
      GUILock lock(_wb, _("Starting Plugin Search"), _("The MySQL Workbench Plugin Search web page "
                                                       "is being opened and should be available in a moment.\n\nPlease stand by..."));
      show_web_page("http://wb.mysql.com/wb-plugins/", true);
    }
      break;
    */
    case StarterAuthorHome:
      show_web_page(starter->authorHome(), true);
      break;
      
    case StarterSetAt6:
      index++; // fall through
    case StarterSetAt5:
      index++; // fall through
    case StarterSetAt4:
      index++; // fall through
    case StarterSetAt3:
      index++; // fall through
    case StarterSetAt2:
      index++; // fall through
    case StarterSetAt1:
      if ((int) _wb->get_root()->starters()->displayList().count() > index)
        _wb->get_root()->starters()->displayList().set(index, starter);
      else
        _wb->get_root()->starters()->displayList().insert(starter);
      _wb->save_starters();
      refresh_home_starters();
      break;
      
    case StarterInsertAt6:
      index++; // fall through
    case StarterInsertAt5:
      index++; // fall through
    case StarterInsertAt4:
      index++; // fall through
    case StarterInsertAt3:
      index++; // fall through
    case StarterInsertAt2:
      index++; // fall through
    case StarterInsertAt1:
      if ((int) _wb->get_root()->starters()->displayList().count() > index)
        _wb->get_root()->starters()->displayList().insert(starter, index);
      else
        _wb->get_root()->starters()->displayList().insert(starter);
      _wb->save_starters();
      refresh_home_starters();
      break;
      
  }
}

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

void WBContextUI::refresh_home_instances(int selected)
{
  if (!_home_screen)
    return;

  grt::ListRef<db_mgmt_ServerInstance> instances(_wb->get_root()->rdbmsMgmt()->storedInstances());
  
  _home_screen->clear_list(HomeScreenAdministrationList);

  // If there are no connections defined yet then create entries for all currently installed
  // local servers (only if this is the first run, after application start).
  if (_initializing && (instances->count() == 0))
  {
    grt::GRT* grt = _wb->get_grt();
    grt::Module* module = grt->get_module("Workbench");
    if (module == NULL)
      throw std::logic_error("Internal error: can't find Workbench module.");

    grt::StringListRef arguments(grt);
    module->call_function("createInstancesFromLocalServers", arguments);
  }

  for (grt::ListRef<db_mgmt_ServerInstance>::const_iterator end = instances.end(),
       inst = instances.begin(); inst != end; ++inst)
  {
    std::string text= get_server_info(*inst);
    
    _home_screen->add_list_entry(HomeScreenAdministrationList, (*inst)->name(), text, *inst);
  }
  
  grt::ValueRef selected_item = ((int) instances.count() > selected && selected >= 0) ? instances[selected] : db_mgmt_ServerInstanceRef();

  _home_screen->select_list_entry(HomeScreenAdministrationList, selected_item);

  _home_screen->refresh_list(HomeScreenAdministrationList);
}

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

void WBContextUI::refresh_home_connections(int selected)
{
  if (!_home_screen)
    return;

  grt::ListRef<db_mgmt_Connection> connections(_wb->get_root()->rdbmsMgmt()->storedConns());

  std::map<std::string, std::string> auto_save_files = WBContextSQLIDE::auto_save_sessions();
  
  _home_screen->clear_list(HomeScreenSQLDevList);

  // If there are no connections defined yet then create entries for all currently installed
  // local servers (only if this is the first run, after application start).
  if (_initializing && (connections->count() == 0))
  {
    grt::GRT* grt = _wb->get_grt();
    grt::Module* module = grt->get_module("Workbench");
    if (module == NULL)
      throw std::logic_error("Internal error: can't find Workbench module.");
    
    grt::StringListRef arguments(grt);
    module->call_function("createConnectionsFromLocalServers", arguments);
  }

  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;
    
    std::string host_entry= strfmt("%s:%i", dict.get_string("hostName").c_str(), (int) dict.get_int("port"));
    if ((*inst)->driver().is_valid() && (*inst)->driver()->name() == "MysqlNativeSSH")
      tunnel = "(ssh tunnel)";
    else
      if ((*inst)->driver().is_valid() && (*inst)->driver()->name() == "MysqlNativeSocket")
      {
        std::string socket= dict.get_string("socket"); // socket or pipe
        if (socket.length() == 0)
          socket= "MySQL"; // default pipe name
        // TODO: what about the default for sockets? Need to distinct the OS here for decision.
        host_entry= "Localhost via pipe " + socket;
      }
    
    std::string schema = dict.get_string("schema");
    if (!schema.empty())
      schema = "Schema: " + schema;
    
    std::string text = strfmt("User: %s  Host: %s %s  %s", dict.get_string("userName").c_str(), 
                              host_entry.c_str(), tunnel.c_str(), schema.c_str());
    
    if (auto_save_files.find((*inst)->id()) != auto_save_files.end())
      _home_screen->add_list_entry(HomeScreenSQLDevList, std::string((*inst)->name())+" (auto saved)", text, *inst,
                                   SQLEDITOR_AUTOSAVED_ICON_FILE);
    else
      _home_screen->add_list_entry(HomeScreenSQLDevList, (*inst)->name(), text, *inst);
  }
  
  grt::ValueRef selected_item = ((int) connections.count() > selected && selected >= 0) ? connections[selected] : db_mgmt_ConnectionRef();
  _home_screen->select_list_entry(HomeScreenSQLDevList, selected_item);

  _home_screen->refresh_list(HomeScreenSQLDevList);

  _home_screen->show_autosave_warning((int)WBContextModel::auto_save_files().size(), 
                                      (int)auto_save_files.size());
}

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

void WBContextUI::refresh_home_models(int selected)
{
  if (!_home_screen)
    return;
  _home_screen->clear_list(HomeScreenModellingList);
  
  std::map<std::string, std::string> auto_save_models(WBContextModel::auto_save_files());

  // find list of auto-saved models that are not in the list of recent files
  for (std::map<std::string, std::string>::const_iterator iter = auto_save_models.begin();
       iter != auto_save_models.end(); ++iter)
  {
    bool found = false;
    for (grt::StringListRef::const_iterator end = _wb->get_root()->options()->recentFiles().end(),
         f = _wb->get_root()->options()->recentFiles().begin(); f != end; ++f)
    {
      if (*f == iter->first || strcmp(g_basename((*f).c_str()), iter->first.c_str()) == 0)
      {
        found = true;
        break;
      }
    }
    if (!found)
      _home_screen->add_list_entry(HomeScreenModellingList, iter->first, "Unsaved Model", grt::StringRef(iter->second),
                                   MODEL_AUTOSAVED_ICON_FILE);
  }
  
  // Create list entries for each recently opened file.
  // First count how many there are so we can add examples if the list is empty.
  int model_count = 0;
  grt::StringListRef recentFiles(_wb->get_root()->options()->recentFiles());
  for (grt::StringListRef::const_iterator end = recentFiles.end(), f = recentFiles.begin();
    f != end; ++f)
  {
    if (g_str_has_suffix((*f).c_str(), ".mwb"))
      model_count++;
  }

  // If there is no entry in the MRU list then scan the examples folder for the Sakila model
  // file and add this as initial entry, so that a new user has an easy start.
  if (model_count == 0)
  {
    std::list<std::string> examples_paths;
#ifdef _WIN32
    examples_paths.push_back(mforms::Utilities::get_special_folder(mforms::WinProgramFilesX86) +
      "\\MySQL\\Samples and Examples 5.5");
    examples_paths.push_back(mforms::Utilities::get_special_folder(mforms::WinProgramFiles) +
      "\\MySQL\\Samples and Examples 5.5");
#endif
    for (std::list<std::string>::const_iterator path_iterator = examples_paths.begin();
      path_iterator != examples_paths.end(); path_iterator++)
    {
      if (g_file_test(path_iterator->c_str(), G_FILE_TEST_IS_DIR))
      {
        std::string pattern = make_path(*path_iterator, "*.mwb");
        std::list<std::string> sample_model_files = base::scan_for_files_matching(pattern, true);
        for (std::list<std::string>::const_iterator iterator = sample_model_files.begin();
          iterator != sample_model_files.end(); iterator++)
          recentFiles->insert_checked(grt::StringRef(*iterator));
      }
    }
  }

  for (grt::StringListRef::const_iterator end = recentFiles.end(), f = recentFiles.begin();
    f != end; ++f)
  {
    if (!g_str_has_suffix((*f).c_str(), ".mwb"))
      continue;
    
    bool has_auto_save;
    
    // check if there's an autosave of the model
    if (auto_save_models.find(*f) != auto_save_models.end()
        || auto_save_models.find(g_basename((*f).c_str())) != auto_save_models.end())
      has_auto_save = true;
    else
      has_auto_save = false;
    
#ifdef _WIN32
    struct _stat stbuf;
    
    if (base_stat((*f).c_str(), &stbuf) < 0)
      _home_screen->add_list_entry(HomeScreenModellingList, *f, "File not accessible", *f);
    else
    {
      struct tm t;
      localtime_s(&t, &stbuf.st_mtime);
      
      std::string text = strfmt("Last modified %s", asctime(&t));
      if (text[text.size()-1] == '\n')
        text = text.substr(0, text.size()-1);
      
      char *file = g_path_get_basename((*f).c_str());
      if (g_str_has_suffix(file, ".mwb"))
        file[strlen(file)-4]= 0;
      _home_screen->add_list_entry(HomeScreenModellingList, file, text, *f,
                                   has_auto_save ? MODEL_AUTOSAVED_ICON_FILE : "");
      g_free(file);
    }
#else
    struct stat stbuf;
    
    if (base_stat((*f).c_str(), &stbuf) < 0)
      _home_screen->add_list_entry(HomeScreenModellingList, *f, "File not accessible", *f);
    else
    {
      char *buf = asctime(localtime(&stbuf.st_mtime));
      std::string text = strfmt("Last modified %s", buf?buf:"???");
      
      char *file = g_path_get_basename((*f).c_str());
      if (g_str_has_suffix(file, ".mwb"))
        file[strlen(file)-4]= 0;
      _home_screen->add_list_entry(HomeScreenModellingList, file, text, *f,
                                   has_auto_save ? MODEL_AUTOSAVED_ICON_FILE : "");
      g_free(file);
    }
#endif
  }

  _home_screen->select_list_entry(HomeScreenModellingList, grt::IntegerRef(selected));

  _home_screen->refresh_list(HomeScreenModellingList);
    
  _home_screen->show_autosave_warning((int)WBContextModel::auto_save_files().size(), 
                                      (int)WBContextSQLIDE::auto_save_sessions().size());
}

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

void WBContextUI::refresh_home_starters()
{
  if (!_home_screen)
    return;
  _home_screen->clear_list(HomeScreenStarters);
  
  grt::ListRef<app_Starter> starters= _wb->get_root()->starters()->displayList();
  for (grt::ListRef<app_Starter>::const_iterator iterator= starters.begin(); iterator != starters.end(); 
       iterator++)
  {
    _home_screen->add_list_entry(HomeScreenStarters, "", "", *iterator, (*iterator)->smallIcon());
  }
  _home_screen->set_needs_repaint();
}

