/*
 * Copyright (c) 2008, 2025, 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, version 2.0,
 * as published by the Free Software Foundation.
 *
 * This program is designed to work with certain software (including
 * but not limited to OpenSSL) that is licensed under separate terms, as
 * designated in a particular file or component or in included license
 * documentation.  The authors of MySQL hereby grant you an additional
 * permission to link the program and your derivative works with the
 * separately licensed software that they have either included with
 * the program or referenced in the documentation.
 * 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, version 2.0, 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 <string>
#if !defined(_MSC_VER)
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <glib.h>
#endif

#include "base/ui_form.h"
#include "base/string_utilities.h"
#include "base/file_utilities.h"
#include "base/log.h"
#include "base/notifications.h"

#include "grt.h"

#include "grts/structs.h"
#include "grts/structs.app.h"

#include "grt/editor_base.h"

#include "wb_context_ui.h"
#include "wb_context.h"
#include "model/wb_component.h"
#include "model/wb_component_physical.h"
#include "model/wb_context_model.h"
#include "model/wb_model_diagram_form.h"
#include "sqlide/wb_context_sqlide.h"

#include "wb_overview.h"
#include "model/wb_overview_physical.h"
#include "model/wb_diagram_options.h"

#include "grt/clipboard.h"
#include "grtui/gui_plugin_base.h"

#include "grt_shell_window.h"
#include "license_view.h"

#include "mforms/appview.h"

#include "mforms/home_screen.h"
#include "mforms/home_screen_connections.h"
#include "mforms/home_screen_documents.h"

#include "wb_command_ui.h"

#include "plugin_install_window.h"

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

DEFAULT_LOG_DOMAIN(DOMAIN_WB_CONTEXT_UI)

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

static void write_lock_file() {
  std::string userdir = mforms::App::get()->get_user_data_folder();
  std::string lockfile = userdir + "/ma_lock_fisrt.lck";
  struct stat buffer;
  if (stat(lockfile.c_str(), &buffer) != 0) { // file does not exist
    std::ofstream ofs(lockfile);
    ofs << "locked";
    ofs.close();
  }
}

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

std::shared_ptr<WBContextUI> WBContextUI::get() {
  static std::shared_ptr<WBContextUI> _singleton(new WBContextUI());
  return _singleton;
}

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

WBContextUI::WBContextUI() : _wb(new WBContext(false)), _command_ui(new CommandUI(_wb)) {
  _shell_window = 0;
  _active_form = 0;
  _active_main_form = 0;

  _addon_download_window = 0;
  _plugin_install_window = 0;

  _last_unsaved_changes_state = false;
  _quitting = false;
  _processing_action_open_connection = false;

  _home_screen = nullptr;

  // to notify that the save status of the doc has changed
  scoped_connect(grt::GRT::get()->get_undo_manager()->signal_changed(), std::bind(&WBContextUI::history_changed, this));

  // stuff to do when the active form is switched in the UI (through set_active_form)
  _form_change_signal.connect(std::bind(&WBContextUI::form_changed, this));
}

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

void WBContextUI::cleanUp() {
  if (_wb != nullptr) {
    _wb->do_close_document(true);
  }
  delete _addon_download_window;
  _addon_download_window = nullptr;

  delete _plugin_install_window;
  _plugin_install_window = nullptr;

  delete _shell_window;
  _shell_window = nullptr;

  if (_wb != nullptr && !_wb->cancel_idle_tasks()) {
    // Idle tasks are currently being executed. Wait a moment and then try again.
    g_usleep(static_cast<int>(100000));
    _wb->cancel_idle_tasks();
  }
  
  delete _wb;
  _wb = nullptr;

  delete _command_ui;
  _command_ui = nullptr;

  // home screen should be released by undock operation,
  // but it will be left in bad state, we have to clear this
  _home_screen = nullptr;

  // write lock indicator for new sticker (migration assistant)
  write_lock_file();
}

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

void WBContextUI::reinit() {
  if (_wb == nullptr) {
    _wb = new WBContext(false);
    _command_ui = new CommandUI(_wb);
  }

}
//--------------------------------------------------------------------------------------------------

WBContextUI::~WBContextUI() {
  cleanUp();
}

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

bool WBContextUI::init(WBFrontendCallbacks *callbacks, WBOptions *options) {
  // Log set folders.
  logInfo(
    "Initializing workbench context UI with these values:\n"
    "\tbase dir: %s\n\tplugin path: %s\n\tstruct path: %s\n\tmodule path: %s\n\t"
    "library path: %s\n\tuser data dir: %s\n\topen at start: %s\n\topen type: %s\n\trun at startup: %s\n\t"
    "run type: %s\n\tForce SW rendering: %s\n\tForce OpenGL: %s\n\tquit when done: %s\n",
    options->basedir.c_str(), options->plugin_search_path.c_str(), options->struct_search_path.c_str(),
    options->module_search_path.c_str(), options->library_search_path.c_str(), options->user_data_dir.c_str(),
    options->open_at_startup.c_str(), options->open_at_startup_type.c_str(), options->run_at_startup.c_str(),
    options->run_language.c_str(), options->force_sw_rendering ? "Yes" : "No",
    options->force_opengl_rendering ? "Yes" : "No", options->quit_when_done ? "Yes" : "No");

  bool flag = false;
  try {
    // this needs to be created after the search paths have been set
    // WBContext::init_ will call get_shell_window() at the right time, which
    // will trigger the instantiation
    //_shell_window = new GRTShellWindow(_wb->get_grt_manager());
    flag = _wb->init_(callbacks, options);

    if (!options->testing) {
      // has to be called after WBContext is initialized
      add_backend_builtin_commands();

      // look for auto-save files (must be done at startup to not confuse with autosaves created by ourself)
      WBContextModel::detect_auto_save_files(_wb->get_auto_save_dir());
      WBContextSQLIDE::detect_auto_save_files(_wb->get_auto_save_dir());
    }

  } catch (const std::exception &e) {
    logError("WBContextUI::init, exception '%s'\n", e.what()); // log_error logs to stderr too in debug mode.
  } catch (...) {
    logError("Some exception has happened. It was caught at WBContextUI::init.\n");
  }

  return flag;
}

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

GRTShellWindow *WBContextUI::get_shell_window() {
  if (!_shell_window)
    _shell_window = new GRTShellWindow(_wb);
  return _shell_window;
}

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

void WBContextUI::init_finish(WBOptions *options) {
  g_assert(_wb->get_root().is_valid());
  show_home_screen();
  _wb->init_finish_(options);

  NotificationCenter::get()->send("GNAppStarted", nullptr);
}

void WBContextUI::finalize() {
  _wb->finalize();
  _command_ui->clearBuildInCommands();
  if (_home_screen != nullptr)
    mforms::App::get()->undock_view(_home_screen);
}

bool WBContextUI::request_quit() {
  if (_quitting)
    return true;

  if (!bec::GRTManager::get()->in_main_thread())
    logWarning("request_quit() called in worker thread\n");

  {
    NotificationInfo info;
    info["cancel"] = "0";

    NotificationCenter::get()->send("GNAppShouldClose", nullptr, info);

    if (info["cancel"] != "0")
      return false;
  }

  if (!_wb->can_close_document())
    return false;

  if (_wb->get_sqlide_context() && !_wb->get_sqlide_context()->request_quit())
    return false;

  if (_shell_window != nullptr && !_shell_window->request_quit())
    return false;

  return true;
}

void WBContextUI::perform_quit() {
  _quitting = true;
  _wb->do_close_document(true);
  _wb->_frontendCallbacks->quit_application();
}

void WBContextUI::reset() {
  if (!dynamic_cast<OverviewBE *>(_active_form))
    _active_form = 0;
  if (!dynamic_cast<OverviewBE *>(_active_main_form))
    _active_main_form = 0;

  scoped_connect(get_physical_overview()->signal_selection_changed(),
                 std::bind(&WBContextUI::overview_selection_changed, this));

  get_physical_overview()->set_model(_wb->get_document()->physicalModels()[0]);

  _wb->request_refresh(RefreshSelection, "", 0);

  get_physical_overview()->send_refresh_children(bec::NodeId());

  _wb->get_model_context()->refill_catalog_tree();
}

void WBContextUI::history_changed() {
  if (!_wb->_file) // check if model is still opened, if not, leave
    return;

  if (_wb->has_unsaved_changes() != _last_unsaved_changes_state)
    _wb->request_refresh(RefreshDocument, "", (NativeHandle)0);

  bec::GRTManager::get()->run_once_when_idle(std::bind(&CommandUI::revalidate_edit_menu_items, get_command_ui()));

  _last_unsaved_changes_state = _wb->has_unsaved_changes();
}

void WBContextUI::update_current_diagram(bec::UIForm *form) {
  ModelDiagramForm *dform = dynamic_cast<ModelDiagramForm *>(form);
  if (dform) {
    model_DiagramRef diagram(dform->get_model_diagram());
    if (diagram.is_valid() && diagram->owner().is_valid())
      diagram->owner()->currentDiagram(diagram);
  }
}

void WBContextUI::overview_selection_changed() {
  if (get_active_main_form() == get_physical_overview()) {
    _wb->request_refresh(RefreshSelection, "", (NativeHandle)get_physical_overview()->get_frontend_data());
    get_command_ui()->revalidate_edit_menu_items();
  }
}

void WBContextUI::load_app_options(bool update) {
  if (!update)
    _command_ui->load_data();
}

static void add_script_file(WBContextUI *wbui) {
  std::string file = wbui->get_wb()->_frontendCallbacks->show_file_dialog("open", _("Add SQL Script File"), "sql");
  if (!file.empty()) {
    workbench_physical_ModelRef model;

    model = workbench_physical_ModelRef::cast_from(wbui->get_wb()->get_model_context()->get_active_model(false));
    if (model.is_valid())
      wbui->get_wb()->get_component<WBComponentPhysical>()->add_new_stored_script(model, file);
  }
}

static void add_note_file(WBContextUI *wbui) {
  std::string file =
    wbui->get_wb()->_frontendCallbacks->show_file_dialog("open", _("Add Note File"), "Text Files (*.txt)|*.txt");
  if (!file.empty()) {
    workbench_physical_ModelRef model;

    model = workbench_physical_ModelRef::cast_from(wbui->get_wb()->get_model_context()->get_active_model(false));
    if (model.is_valid())
      wbui->get_wb()->get_component<WBComponentPhysical>()->add_new_stored_note(model, file);
  }
}

/** builtin: commands for use in menus and toolbars that are handled by ourselves
  */
void WBContextUI::add_backend_builtin_commands() {
  _command_ui->add_builtin_command("show_about", std::bind(&WBContextUI::show_about, this));
  _command_ui->add_builtin_command("overview.home", std::bind(&WBContextUI::show_home_screen, this));

  _command_ui->add_builtin_command("add_script_file", std::bind(add_script_file, this));
  _command_ui->add_builtin_command("add_note_file", std::bind(add_note_file, this));
  _command_ui->add_builtin_command("web_mysql_home",
                                   std::bind(&WBContextUI::show_web_page, this, "http://mysql.com/", true));
  _command_ui->add_builtin_command(
      "web_mysql_docs", std::bind(&WBContextUI::show_web_page, this, "https://dev.mysql.com/doc/workbench/en/", true));

  _command_ui->add_builtin_command("web_mysql_blog",
                                   std::bind(&WBContextUI::show_web_page, this, "https://mysqlworkbench.org/", true));

  _command_ui->add_builtin_command(
      "web_mysql_forum", std::bind(&WBContextUI::show_web_page, this, "https://forums.mysql.com/list.php?152", true));
 
  _command_ui->add_builtin_command(
    "web_migration_asistant_learn_more", std::bind(&WBContextUI::show_web_page, this, "https://dev.mysql.com/doc/workbench/en/heatwave-migration-assistant.html", true));

  if (_wb->is_commercial()) {
    _command_ui->add_builtin_command("web_support",
                                     std::bind(&WBContextUI::show_web_page, this, "http://support.oracle.com", true));
  }

  _command_ui->add_builtin_command("help_index", std::bind(&WBContextUI::show_help_index, this));
  _command_ui->add_builtin_command("show-license", std::bind(&WBContextUI::showLicense, this));
  _command_ui->add_builtin_command("locate_log_file", std::bind(&WBContextUI::locate_log_file, this));
  _command_ui->add_builtin_command("show_log_file", std::bind(&WBContextUI::show_log_file, this));
}

#ifndef ___specialforms

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

PhysicalOverviewBE *WBContextUI::get_physical_overview() {
  return get_wb()->get_model_context() ? get_wb()->get_model_context()->get_overview() : 0;
}

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

/*
 * Opens the given web page in the system's default browser.
 */
void WBContextUI::show_web_page(const std::string &url, bool internal_browser) {
  mforms::Utilities::open_url(url);
}

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

void WBContextUI::showLicense() {
  LicenseView *view = mforms::manage(new LicenseView(this));
  mforms::App::get()->dock_view(view, "maintab");
  view->set_title(_("License Info"));
}

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

void WBContextUI::show_help_index() {
  GUILock lock(_wb, _("Starting Doc Lib"), _("The MySQL Doc Library is opening currently, "
                                             "which should be finished in a moment .\n\nPlease stand by..."));

  mforms::Utilities::open_url("http://dev.mysql.com/doc/refman/8.0/en");
}

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

void WBContextUI::locate_log_file() {
  if (!base::Logger::log_dir().empty())
    mforms::Utilities::open_url(base::Logger::log_dir());
}

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

void WBContextUI::show_log_file() {
  if (!base::Logger::log_filename().empty())
    mforms::Utilities::open_url(base::Logger::log_filename());
}

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

void WBContextUI::activate_figure(const grt::ValueRef &value) {
  ModelDiagramForm *form = 0;
  if (model_FigureRef::can_wrap(value)) {
    model_FigureRef figure(model_FigureRef::cast_from(value));
    form = get_wb()->get_model_context()->get_diagram_form_for_diagram_id(figure->owner().id());
    if (form)
      form->focus_and_make_visible(figure, true);
  } else if (model_ConnectionRef::can_wrap(value)) {
    model_ConnectionRef conn(model_ConnectionRef::cast_from(value));
    ModelDiagramForm *form = get_wb()->get_model_context()->get_diagram_form_for_diagram_id(conn->owner().id());
    if (form)
      form->focus_and_make_visible(conn, true);
  } else if (model_LayerRef::can_wrap(value)) {
    model_LayerRef layer(model_LayerRef::cast_from(value));
    ModelDiagramForm *form = get_wb()->get_model_context()->get_diagram_form_for_diagram_id(layer->owner().id());
    if (form)
      form->focus_and_make_visible(layer, true);
  }
}

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

void WBContextUI::form_changed() {
  _wb->request_refresh(RefreshZoom, "", (NativeHandle)0);

  bec::UIForm *form = get_active_main_form();
  if (form && form->get_menubar())
    get_command_ui()->revalidate_menu_bar(form->get_menubar());
}

bec::ValueInspectorBE *WBContextUI::create_inspector_for_selection(bec::UIForm *form, std::vector<std::string> &items) {
  grt::ListRef<model_Object> selection;

  // grt::ListRef<model_Object> selection(form->get_selection());

  if (dynamic_cast<ModelDiagramForm *>(form))
    selection = dynamic_cast<ModelDiagramForm *>(form)->get_selection();
  else
    return 0;

  if (selection.is_valid() && selection.count() > 0) {
    if (selection.count() == 1) {
      items.push_back(
        strfmt("%s: %s", selection[0]->name().c_str(), selection[0].get_metaclass()->get_attribute("caption").c_str()));

      return ValueInspectorBE::create(selection[0], false, true);
    } else {
      std::vector<grt::ObjectRef> list;

      items.push_back(_("Multiple Items"));
      for (size_t c = selection.count(), i = 0; i < c; i++) {
        items.push_back(strfmt("%s: %s", selection[i]->name().c_str(),
                               selection[i].get_metaclass()->get_attribute("caption").c_str()));
        list.push_back(selection.get(i));
      }

      return ValueInspectorBE::create(list);
    }
  }

  return 0;
}

bec::ValueInspectorBE *WBContextUI::create_inspector_for_selection(std::vector<std::string> &items) {
  std::string res;

  grt::ListRef<GrtObject> selection(get_physical_overview()->get_selection());
  std::string name_mem_name("name");

  if (selection.is_valid() && selection.count() > 0) {
    if (selection.count() == 1) {
      GrtObjectRef obj = selection[0];
      if (obj.is_valid() && obj->has_member(name_mem_name)) {
        items.push_back(strfmt("%s: %s", obj.get_string_member(name_mem_name).c_str(),
                               obj.get_metaclass()->get_attribute("caption").c_str()));

        return ValueInspectorBE::create(selection[0], false, true);
      }
    } else {
      std::vector<grt::ObjectRef> list;

      items.push_back(_("Multiple Items"));
      for (size_t c = selection.count(), i = 0; i < c; i++) {
        if (!selection[i].is_valid()) // skip "Add item" entry in objects list
          continue;
        items.push_back(strfmt("%s: %s", selection[i].get_string_member(name_mem_name).c_str(),
                               selection[i].get_metaclass()->get_attribute("caption").c_str()));
        list.push_back(selection.get(i));
      }

      return ValueInspectorBE::create(list);
    }
  }

  return 0;
}

std::string WBContextUI::get_description_for_selection(grt::ListRef<GrtObject> &activeObjList,
                                                       std::vector<std::string> &items) {
  std::string res;

  if (get_physical_overview() != nullptr) {
    grt::ListRef<GrtObject> selection(get_physical_overview()->get_selection());
    activeObjList = selection;

    std::string comment_mem_name("comment");
    std::string name_mem_name("name");

    if (selection.is_valid() && selection.count() > 0) {
      if (selection.count() == 1) {
        GrtObjectRef obj(selection[0]);
        if (obj.is_valid() && obj.has_member(comment_mem_name) && obj.has_member(name_mem_name)) {
          items.push_back(strfmt("%s: %s", obj->name().c_str(), obj.get_metaclass()->get_attribute("caption").c_str()));
          res = obj.get_string_member(comment_mem_name);
        }
      } else {
        items.push_back(_("Multiple Items"));
        for (size_t c = selection.count(), i = 0; i < c; i++) {
          GrtObjectRef obj(selection[i]);
          if (obj.is_valid() && obj.has_member(comment_mem_name) && obj.has_member(name_mem_name)) {
            items.push_back(
              strfmt("%s: %s", obj->name().c_str(), obj.get_metaclass()->get_attribute("caption").c_str()));
            std::string comment = obj.get_string_member(comment_mem_name);
            if (0 == i)
              res = comment;
            else if (0 != res.compare(comment))
              res =
                "<Multiple Items>\nThat means not all selected items have same comment.\nBeware applying changes will "
                "override comments for all selected objects.";
          }
        }
      }
    }
  }
  return res;
}

std::string WBContextUI::get_description_for_selection(bec::UIForm *form, grt::ListRef<GrtObject> &activeObjList,
                                                       std::vector<std::string> &items) {
  grt::ListRef<model_Object> selection;

  if (dynamic_cast<ModelDiagramForm *>(form))
    selection = dynamic_cast<ModelDiagramForm *>(form)->get_selection();
  else
    return get_description_for_selection(activeObjList, items);

  std::string res;

  activeObjList = grt::ListRef<model_Object>(true);

  std::string comment_mem_name("comment");
  std::string descr_mem_name("description");

  if (selection.is_valid() && selection.count() > 0) {
    bool first = true;
    for (size_t c = selection.count(), i = 0; i < c; i++) {
      model_ObjectRef figure(selection[i]);
      WBComponent *comp = _wb->get_component_handling(figure);
      GrtObjectRef dbobject;
      if (comp)
        dbobject = comp->get_object_for_figure(figure);

      if (dbobject.is_valid() && dbobject.has_member(comment_mem_name)) {
        activeObjList.insert(dbobject);

        items.push_back(
          strfmt("%s: %s", figure->name().c_str(), figure->get_metaclass()->get_attribute("caption").c_str()));
        std::string comment = dbobject.get_string_member(comment_mem_name);
        if (first)
          res = comment;
        else if (0 != res.compare(comment))
          res =
            _("<Multiple Items>\nThat means not all selected items have same comment.\nBeware applying changes will "
              "override comments for all selected objects.");

        first = false;
      } else if (!dbobject.is_valid() && figure.is_valid() && figure.has_member(descr_mem_name)) {
        activeObjList.insert(figure);

        items.push_back(
          strfmt("%s: %s", figure->name().c_str(), figure->get_metaclass()->get_attribute("caption").c_str()));
        std::string comment = figure.get_string_member(descr_mem_name);
        if (first)
          res = comment;
        else if (0 != res.compare(comment))
          res =
            _("<Multiple Items>\nThat means not all selected items have same comment.\nBeware applying changes will "
              "override comments for all selected objects.");

        first = false;
      }
    }
    if (items.size() > 1)
      items.insert(items.begin(), _("Multiple Items"));
  }

  return res;
}

void WBContextUI::set_description_for_selection(const grt::ListRef<GrtObject> &objList, const std::string &val) {
  if (objList.is_valid() && objList.count() > 0) {
    std::string comment_mem_name("comment");
    std::string descr_mem_name("description");

    grt::AutoUndo undo;

    for (size_t c = objList.count(), i = 0; i < c; i++) {
      GrtObjectRef obj(objList[i]);
      if (obj.is_valid()) {
        if (obj.has_member(comment_mem_name)) {
          obj.set_member(comment_mem_name, grt::StringRef(val));
          get_physical_overview()->send_refresh_for_schema_object(obj, true);
        } else if (obj.has_member(descr_mem_name)) {
          obj.set_member(descr_mem_name, grt::StringRef(val));
          get_physical_overview()->send_refresh_for_schema_object(obj, true);
        }
      }
    }

    undo.end(_("Set Object Description"));
  }
}

DiagramOptionsBE *WBContextUI::create_diagram_options_be(mdc::CanvasView *view) {
  model_DiagramRef model_diagram(get_wb()->get_model_context()->get_active_model_diagram(true));

  if (model_diagram.is_valid())
    return new DiagramOptionsBE(view, model_diagram, _wb);
  else
    return 0;
}

std::string WBContextUI::get_active_diagram_info() {
  wb::ModelDiagramForm *form = dynamic_cast<wb::ModelDiagramForm *>(get_active_main_form());

  if (form)
    return form->get_diagram_info_text();

  return "";
}

#endif // ___specialforms

#ifndef ___preferences
//-----------------------------------------------------------------------------------
// utility functions for user preferences

void WBContextUI::get_doc_properties(std::string &caption, std::string &version, std::string &author,
                                     std::string &project, std::string &date_created, std::string &date_changed,
                                     std::string &description) {
  app_DocumentInfoRef info = _wb->get_document()->info();

  caption = info->caption();
  version = info->version();
  author = info->author();
  project = info->project();
  date_created = info->dateCreated();
  date_changed = info->dateChanged();
  description = info->description();
}

void WBContextUI::set_doc_properties(const std::string &caption, const std::string &version, const std::string &author,
                                     const std::string &project, const std::string &date_created,
                                     const std::string &date_changed, const std::string &description) {
  app_DocumentInfoRef info = _wb->get_document()->info();

  grt::AutoUndo undo;
  info->caption(caption);
  info->version(version);
  info->author(author);
  info->project(project);
  info->dateCreated(date_created);
  info->dateChanged(date_changed);
  info->description(description);
  undo.end("Change document properties");
}

std::list<WBPaperSize> WBContextUI::get_paper_sizes(bool descr_in_inches) {
  std::list<WBPaperSize> sizes;

  grt::ListRef<app_PaperType> types(_wb->get_root()->options()->paperTypes());

  for (size_t c = types.count(), i = 0; i < c; i++) {
    WBPaperSize size;
    size.name = types[i]->name();
    size.caption = types[i]->caption();
    size.width = types[i]->width();
    size.height = types[i]->height();
    size.margins_set = types[i]->marginsSet() != 0;
    size.margin_top = types[i]->marginTop();
    size.margin_bottom = types[i]->marginBottom();
    size.margin_left = types[i]->marginLeft();
    size.margin_right = types[i]->marginRight();

    if (descr_in_inches)
      size.description = strfmt("%.2f in x %.2f in", size.width * 0.03937, size.height * 0.03937);
    else
      size.description = strfmt("%.2f cm x %.2f cm", size.width / 10, size.height / 10);

    sizes.push_back(size);
  }

  return sizes;
}

bool WBContextUI::add_paper_size(const std::string &name, double width, double height, bool margins, double margin_top,
                                 double margin_bottom, double margin_left, double margin_right) {
  if (grt::find_named_object_in_list(_wb->get_root()->options()->paperTypes(), name).is_valid())
    return false;

  app_PaperTypeRef type(grt::Initialized);
  type->owner(_wb->get_root()->options());
  type->name(name);
  type->width(width);
  type->height(height);
  type->marginsSet(margins ? 1 : 0);
  type->marginTop(margin_top);
  type->marginBottom(margin_bottom);
  type->marginLeft(margin_left);
  type->marginRight(margin_right);
  _wb->get_root()->options()->paperTypes().insert(type);

  return true;
}

app_PageSettingsRef WBContextUI::get_page_settings() {
  if (_wb->get_document().is_valid())
    return _wb->get_document()->pageSettings();
  else {
    // XXX add proper initialization for non-trivial types in structs.app.h too.
    app_PageSettingsRef settings = app_PageSettingsRef(grt::Initialized);
    settings->scale(1);
    settings->paperType(app_PaperTypeRef());

    return settings;
  }
}

grt::DictRef WBContextUI::get_model_options(const std::string &model_id) {
  grt::ListRef<workbench_physical_Model> pmodels(_wb->get_document()->physicalModels());

  for (size_t c = pmodels.count(), i = 0; i < c; i++) {
    if (pmodels.get(i).id() == model_id) {
      return pmodels.get(i)->options();
    }
  }
  return grt::DictRef();
}

std::vector<std::string> WBContextUI::get_wb_options_keys(const std::string &model) {
  std::vector<std::string> keylist;
  grt::DictRef options = _wb->get_wb_options();

  for (grt::DictRef::const_iterator iter = options.begin(); iter != options.end(); ++iter) {
    keylist.push_back(iter->first);
  }

  return keylist;
}

bool WBContextUI::get_wb_options_value(const std::string &model, const std::string &key, std::string &value) {
  grt::DictRef options = _wb->get_wb_options();
  grt::ValueRef val;

  // If a model is given check if it is set to use global values or its own.
  if (!model.empty()) {
    grt::DictRef model_options(get_model_options(model));
    bool use_global = model_options.get_int("useglobal", 1) != 0;

    if (key == "useglobal") {
      if (use_global)
        value = "1";
      else
        value = "0";
      return true;
    }

    if (!use_global && model_options.has_key(key))
      val = model_options.get(key);
  }

  if (!val.is_valid() && options.has_key(key))
    val = options.get(key);

  switch (val.type()) {
    case grt::StringType:
    case grt::DoubleType:
    case grt::IntegerType:
      value = val.toString();
      return true;
    default:
      return false;
  }
}

void WBContextUI::set_wb_options_value(const std::string &model, const std::string &key, const std::string &value,
                                       const grt::Type default_type) {
  grt::DictRef options;
  grt::Type type;
  if (_wb->get_wb_options().has_key(key))
    type = _wb->get_wb_options().get(key).type();
  else
    type = default_type;

  if (!model.empty()) {
    options = get_model_options(model);

    options.gset("useglobal", 0);

    if (options.has_key(key))
      type = options.get(key).type();
  }

  if (!options.is_valid())
    options = _wb->get_wb_options();

  switch (type) {
    case grt::DoubleType: {
      grt::DoubleRef v(base::atof<double>(value, 0.0));
      if (!options.has_key(key) || options.get_double(key) != *v)
        options.set(key, v);
      break;
    }
    case grt::IntegerType: {
      grt::IntegerRef v(base::atoi<int>(value, 0));
      if (!options.has_key(key) || options.get_int(key) != *v)
        options.set(key, v);
      break;
    }
    case grt::StringType: {
      grt::StringRef v(value);
      if (!options.has_key(key) || options.get_string(key) != *v)
        options.set(key, v);
      break;
    }
    default:
      throw std::runtime_error("No valid grt type specified when setting options value.");
  }
}

void WBContextUI::discard_wb_model_options(const std::string &model) {
  grt::DictRef opts = get_model_options(model);
  if (opts.is_valid()) {
    for (grt::DictRef::const_iterator item = opts.begin(); item != opts.end(); ++item) {
      opts.set(item->first, grt::ValueRef());
    }
    opts.gset("useglobal", 1);
  }
}

#endif // ___preferences

#ifndef ___forms

//--------------------------------------------------------------------------------
// Form Management

void *WBContextUI::form_destroyed(void *data) {
  UIForm *form = reinterpret_cast<UIForm *>(data);
  WBContextUI *wb = reinterpret_cast<WBContextUI *>(form->get_owner_data());

  if (wb->_active_form == form)
    wb->set_active_form(0);
  if (wb->_active_main_form == form)
    wb->_active_main_form = 0;

  return 0;
}

std::string WBContextUI::get_active_context(bool main_context) {
  bec::UIForm *form = main_context ? get_active_main_form() : get_active_form();

  if (form)
    return form->get_form_context_name();

  return "";
}

void WBContextUI::set_active_form(bec::UIForm *form) {
  if (_active_form == form)
    return;
  // register callbacks to form if needed

  if (_active_form && form)
    _active_form->remove_destroy_notify_callback(_active_form);
  _active_form = form;
  if (form) {
    form->add_destroy_notify_callback(reinterpret_cast<void *>(form), &WBContextUI::form_destroyed);
    form->set_owner_data(reinterpret_cast<void *>(this));
  }

  if (form && form->is_main_form()) {
    if (_active_main_form != form) {
      _active_main_form = form;

      base::NotificationInfo info;
      info["form"] = form ? form->form_id() : "";
      info["context"] = get_active_context(true);
      NotificationCenter::get()->send("GNMainFormChanged", 0, info);
    }
  }

  _form_change_signal(form);
}

bec::UIForm *WBContextUI::get_active_main_form() {
  return _active_main_form;
}

bec::UIForm *WBContextUI::get_active_form() {
  return _active_form;
}

#endif // ___forms

#ifndef ___others

//-----------------------------------------------------------------------------------
// other functionality for UI

std::string WBContextUI::get_document_name() {
  if (_wb->get_filename().empty())
    return "Untitled";
  else
    return base::basename(_wb->get_filename());
}

std::string WBContextUI::get_title() {
  if (_wb->get_model_context()) {
#ifndef __APPLE__
    if (_wb->has_unsaved_changes())
      return get_document_name() + "* - MySQL Workbench";
    else
#endif
      return get_document_name() + " - MySQL Workbench";
  } else
    return "MySQL Workbench";
}

#endif // ___others

void WBContextUI::start_plugin_net_install(const std::string &url) {
  if (!_addon_download_window)
    _addon_download_window = new AddOnDownloadWindow(this);
  _addon_download_window->install_addon_from_url(url);
}

bool WBContextUI::start_plugin_install(const std::string &path) {
  if (!_plugin_install_window)
    _plugin_install_window = new PluginInstallWindow(this);
  return _plugin_install_window->install_plugin(path);
}

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

static struct RegisterNotifDocs_wb_context_ui {
  RegisterNotifDocs_wb_context_ui() {
    base::NotificationCenter::get()->register_notification(
      "GNAppStarted", "application", "Sent when Workbench starts up and finishes with various initialization routines.",
      "", "");

    base::NotificationCenter::get()->register_notification("GNAppShouldClose", "application",
                                                           "Sent when the user requests Workbench to close. Close can "
                                                           "be cancelled by setting the 'cancel' field in the info "
                                                           "dict to 1.",
                                                           "", "cancel - set to 1 if exit should be cancelled");

    base::NotificationCenter::get()->register_notification(
      "GNMainFormChanged", "application", "Sent when the main tab from the application is switched.", "",
      "form - the ID of the newly active form\n"
      "context - the context name of the newly active form");

    base::NotificationCenter::get()->register_notification("GNApplicationActivated", "application",
                                                           "Sent when the application was activated.", "", "");
    base::NotificationCenter::get()->register_notification("GNApplicationDeactivated", "application",
                                                           "Sent when the application lost the active status.", "", "");
  }
} initdocs_wb_context_ui;
