/*
 * Copyright (c) 2013, 2015, 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 "wb_sql_editor_form.h"
#include "wb_sql_editor_panel.h"
#include "wb_sql_editor_result_panel.h"
#include "spatial_data_view.h"
#include "result_form_view.h"
#include "objimpl/db.query/db_query_Resultset.h"
#include "sqlide/recordset_cdbc_storage.h"
#include "grtdb/db_helpers.h"
#include "grtui/inserts_export_form.h"
#include "objimpl/wrapper/mforms_ObjectReference_impl.h"
#include "objimpl/db.query/db_query_Resultset.h"
#include "objimpl/db.query/db_query_EditableResultset.h"

#include "sqlide/column_width_cache.h"

#include "base/sqlstring.h"
#include "grt/parse_utils.h"
#include "grt/spatial_handler.h"
#include "base/log.h"
#include "base/string_utilities.h"
#include "base/boost_smart_ptr_helpers.h"

#include "mforms/utilities.h"
#include "mforms/treenodeview.h"
#include "mforms/label.h"
#include "mforms/box.h"
#include "mforms/table.h"
#include "mforms/tabview.h"
#include "mforms/tabswitcher.h"
#include "mforms/toolbar.h"
#include "mforms/scrollpanel.h"
#include "mforms/menubar.h"
#include "mforms/record_grid.h"
#include "mforms/imagebox.h"

#include "mforms/button.h"
#include "mforms/selector.h"
#include "mforms/textentry.h"

#include <algorithm>

using namespace base;

DEFAULT_LOG_DOMAIN("SqlResult")

class SqlEditorResult::DockingDelegate : public mforms::TabViewDockingPoint
{
  mforms::TabSwitcher *_switcher;

public:
  DockingDelegate(mforms::TabView *tabview, mforms::TabSwitcher *switcher, const std::string &name)
  : mforms::TabViewDockingPoint(tabview, name), _switcher(switcher)
  {
  }

  virtual void undock_view(AppView *view)
  {
    for (int i = 0; i < view_count(); i++)
      if (view_at_index(i) == view)
      {
        _switcher->remove_item(i);
        break;
      }

    mforms::TabViewDockingPoint::undock_view(view);
  }

  virtual void dock_view(AppView *view, const std::string &icon, int arg2)
  {
    mforms::TabViewDockingPoint::dock_view(view, icon, arg2);
    _switcher->add_item(view->get_title(), "", icon, "");
  }
};


SqlEditorResult::SqlEditorResult(SqlEditorPanel *owner)
: mforms::AppView(true, "QueryResult", false),
   _owner(owner),
  _tabview(mforms::TabViewTabless), _switcher(mforms::VerticalIconSwitcher),
  _tabdock_delegate(new DockingDelegate(&_tabview, &_switcher, std::string("SqlResultPanel"))), _tabdock(_tabdock_delegate, true),
  _pinned(false)

{
  _result_grid = NULL;
  _grid_header_menu = NULL;
  _column_info_menu = NULL;
  _column_info_created = false;
  _query_stats_created = false;
  _form_view_created = false;

  _spatial_view_initialized = false;
  _spatial_result_view = NULL;

  add(&_tabview, true, true);

  _switcher.attach_to_tabview(&_tabview);
  _switcher.set_collapsed(_owner->owner()->grt_manager()->get_app_option_int("Recordset:SwitcherCollapsed", 0) != 0);

  add(&_switcher, false, true);
  _switcher.signal_changed()->connect(boost::bind(&SqlEditorResult::switch_tab, this));
  _switcher.signal_collapse_changed()->connect(boost::bind(&SqlEditorResult::switcher_collapsed, this));
  
  _execution_plan_placeholder = NULL;

  // put a placeholder for the resultset, which will be replaced when a resultset is actually available
  _resultset_placeholder = mforms::manage(new mforms::AppView(false, "ResultGridPlaceholder", false));
  _resultset_placeholder->set_back_color("#ffffff");
  _resultset_placeholder->set_title("Result\nGrid");
  _resultset_placeholder->set_identifier("result_grid");
  _tabdock.dock_view(_resultset_placeholder, "output_type-resultset.png");

  {
    db_query_QueryEditorRef editor(owner->grtobj());
    _grtobj = db_query_ResultPanelRef(editor.get_grt());
    _grtobj->dockingPoint(mforms_to_grt(editor.get_grt(), &_tabdock));
  }

  set_on_close(boost::bind(&SqlEditorResult::can_close, this));
}


void SqlEditorResult::reset_sorting()
{
  Recordset::Ref rset(recordset());
  if (rset)
    rset->sort_by(0, 0, false);
  if (_result_grid)
  {
    for (int i = 0; i < _result_grid->get_column_count(); i++)
      _result_grid->set_column_header_indicator(i, mforms::NoIndicator);
  }
}

void SqlEditorResult::copy_column_name()
{
  int column = _result_grid->get_clicked_header_column();
  Recordset::Ref rset(recordset());
  if (rset)
    mforms::Utilities::set_clipboard_text(rset->get_column_caption(column));
}


void SqlEditorResult::copy_all_column_names()
{
  Recordset::Ref rset(recordset());
  if (rset)
  {
    std::string text;
    for (Recordset::Column_names::const_iterator col = rset->column_names()->begin(); col != rset->column_names()->end(); ++col)
      text.append(", ").append(*col);
    if (!text.empty())
      text = text.substr(2);
    mforms::Utilities::set_clipboard_text(text);
  }
}

void SqlEditorResult::open_field_editor(int row, int column)
{
  Recordset::Ref rset(recordset());
  if (rset)
  {
    Recordset_cdbc_storage::Ref storage(boost::dynamic_pointer_cast<Recordset_cdbc_storage>(rset->data_storage()));
    if (storage)
    {
      rset->open_field_data_editor(row, column, storage->field_info()[column].type);
    }
  }
}

void SqlEditorResult::update_selection_for_menu_extra(mforms::ContextMenu *menu, const std::vector<int> &rows, int column)
{
  mforms::MenuItem *item = menu->find_item("edit_cell");
  if (item)
  {
    if (item->signal_clicked()->empty() && !rows.empty())
      item->signal_clicked()->connect(boost::bind(&SqlEditorResult::open_field_editor, this, rows[0], column));
  }
}

void SqlEditorResult::set_recordset(Recordset::Ref rset)
{
  if (_resultset_placeholder)
  {
    _tabdock_delegate->undock_view(_resultset_placeholder);
    _resultset_placeholder = NULL;
  }

  _rset = rset;
  if (!rset->is_readonly())
    _grtobj->resultset(grtwrap_editablerecordset(grtobj(), rset));
  else
    _grtobj->resultset(grtwrap_recordset(grtobj(), rset));

  rset->update_selection_for_menu_extra = boost::bind(&SqlEditorResult::update_selection_for_menu_extra, this, _1, _2, _3);

  rset->get_toolbar()->find_item("record_export")->signal_activated()->connect(boost::bind(&SqlEditorResult::show_export_recordset, this));
  if (rset->get_toolbar()->find_item("record_import"))
    rset->get_toolbar()->find_item("record_import")->signal_activated()->connect(boost::bind(&SqlEditorResult::show_import_recordset, this));

  // reset the column header indicators
  rset->get_toolbar()->find_item("record_sort_reset")->signal_activated()->connect(boost::bind(&SqlEditorResult::reset_sorting, this));

  _grid_header_menu = new mforms::ContextMenu();
  _grid_header_menu->add_item_with_title("Copy Field Name", boost::bind(&SqlEditorResult::copy_column_name, this));
  _grid_header_menu->add_item_with_title("Copy All Field Names", boost::bind(&SqlEditorResult::copy_all_column_names, this));
  _grid_header_menu->add_separator();
  _grid_header_menu->add_item_with_title("Reset Sorting", boost::bind(&SqlEditorResult::reset_sorting, this));
  _grid_header_menu->add_item_with_title("Reset Column Widths", boost::bind(&SqlEditorResult::reset_column_widths, this));

  mforms::RecordGrid *grid = mforms::manage(mforms::RecordGrid::create(rset));
  {
    std::string font = _owner->owner()->grt_manager()->get_app_option_string("workbench.general.Resultset:Font");
    if (!font.empty())
      grid->set_font(font);

    grid->set_header_menu(_grid_header_menu);
  }
  dock_result_grid(grid);

  Recordset_cdbc_storage::Ref storage(boost::dynamic_pointer_cast<Recordset_cdbc_storage>(rset->data_storage()));
  rset->caption(strfmt("%s %i",
                       (storage->table_name().empty() ? _("Result") : storage->table_name().c_str()),
                       ++_owner->_rs_sequence));

  bec::UIForm::scoped_connect(rset->get_context_menu()->signal_will_show(),
                              boost::bind(&SqlEditorPanel::on_recordset_context_menu_show, _owner, Recordset::Ptr(rset)));

  restore_grid_column_widths();
  bec::UIForm::scoped_connect(_result_grid->signal_column_resized(),
                              boost::bind(&SqlEditorResult::on_recordset_column_resized, this, _1));

  bec::UIForm::scoped_connect(_result_grid->signal_columns_resized(),
                                boost::bind(&SqlEditorResult::onRecordsetColumnsResized, this, _1));

  rset->data_edited_signal.connect(boost::bind(&SqlEditorPanel::resultset_edited, _owner));
  rset->data_edited_signal.connect(boost::bind(&mforms::View::set_needs_repaint, grid));
}


SqlEditorResult::~SqlEditorResult()
{
  delete _column_info_menu;
  delete _grid_header_menu;
}


Recordset::Ref SqlEditorResult::recordset() const
{
  return _rset.lock();
}


bool SqlEditorResult::has_pending_changes()
{
  Recordset::Ref rset(recordset());
  if (rset)
    return rset->has_pending_changes();
  return false;
}


void SqlEditorResult::apply_changes()
{
  Recordset::Ref rset(recordset());
  if (rset)
    rset->apply_changes();
}


void SqlEditorResult::discard_changes()
{
  Recordset::Ref rset(recordset());
  if (rset)
    rset->rollback();
}


std::string SqlEditorResult::caption() const
{
  RETVAL_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, _rset, rs, "")
  {
    return rs->caption();
  }
}



std::vector<SpatialDataView::SpatialDataSource> SqlEditorResult::get_spatial_columns()
{
  std::vector<SpatialDataView::SpatialDataSource> spatial_columns;
  int i = 0;
  Recordset_cdbc_storage::Ref storage(boost::dynamic_pointer_cast<Recordset_cdbc_storage>(_rset.lock()->data_storage()));
  std::vector<Recordset_cdbc_storage::FieldInfo> &field_info(storage->field_info());
  for (std::vector<Recordset_cdbc_storage::FieldInfo>::const_iterator iter = field_info.begin();
       iter != field_info.end(); ++iter, ++i)
  {
    if (iter->type == "GEOMETRY")
    {
      SpatialDataView::SpatialDataSource field;
      field.source = get_title();
      field.resultset = _rset;
      field.column = iter->field;
      field.type = iter->type;
      field.column_index = i;
      spatial_columns.push_back(field);
    }
  }
  return spatial_columns;
}

void SqlEditorResult::set_title(const std::string &title)
{
  grtobj()->name(title);
  mforms::AppView::set_title(title);
}

bool SqlEditorResult::can_close()
{
  if (Recordset::Ref rs = recordset())
    if (!rs->can_close(true))
      return false;

  if (!_tabdock.close_all_views())
    return false;

  return true;
}


void SqlEditorResult::close()
{// called by DockingPoint::close_view()
  if (Recordset::Ref rs = recordset())
    rs->close();
  _tabdock.close_all_views();

  mforms::AppView::close();
}

void SqlEditorResult::switch_tab()
{
  mforms::AppView *tab = _tabdock.selected_view();

  if (tab)
  {
    if (tab->identifier() == "column_info" && !_column_info_created)
    {
      _column_info_created = true;
      create_column_info_panel();
    }
    else if (tab->identifier() == "query_stats" && !_query_stats_created)
    {
      _query_stats_created = true;
      create_query_stats_panel();
    }
    else if (tab->identifier() == "form_result")
    {
      if (!_form_view_created)
      {
        _form_view_created = true;
        _form_result_view->init_for_resultset(_rset, _owner->owner());
      }
      _form_result_view->display_record();
    }
    else if (tab->identifier() == "result_grid")
    {
      if (_resultset_placeholder)
      {
        _owner->owner()->exec_editor_sql(_owner, true, true, true, false, this);
        set_title(_rset.lock()->caption());
      }
    }
    else if (tab->identifier() == "execution_plan")
    {
      if (_execution_plan_placeholder)
      {
        _tabdock_delegate->undock_view(_execution_plan_placeholder);
        _execution_plan_placeholder = NULL;

        // if the explain tab is just a placeholder, execute visual explain, which will replace the tab when docking
        grt::BaseListRef args(_grtobj.get_grt());
        args.ginsert(_owner->grtobj());
        args.ginsert(_grtobj);
        try
        {
          // run the visual explain plugin, so it will fill the result panel
          _grtobj.get_grt()->call_module_function("SQLIDEQueryAnalysis", "visualExplain", args);
        }
        catch (std::exception &exc)
        {
          log_error("Error executing visual explain: %s\n", exc.what());
          mforms::Utilities::show_error("Execution Plan", "An internal error occurred while building the execution plan, please file a bug report.",
                                        "OK");
        }
      }
    }
    else if (tab->identifier() == "spatial_result_view")
    {
      if (!_spatial_view_initialized)
      {
        _spatial_view_initialized = true;
        _spatial_result_view->refresh_layers();
      }
      _spatial_result_view->activate();
    }
  }
}


void SqlEditorResult::add_switch_toggle_toolbar_item(mforms::ToolBar *tbar)
{
  _collapse_toggled_sig.disconnect();
  mforms::App *app = mforms::App::get();
  tbar->add_item(mforms::manage(new mforms::ToolBarItem(mforms::ExpanderItem)));
  mforms::ToolBarItem *item = mforms::manage(new mforms::ToolBarItem(mforms::ToggleItem));
  item->set_name("sidetoggle");
  item->set_icon(app->get_resource_path("output_type-toggle-on.png"));
  item->set_alt_icon(app->get_resource_path("output_type-toggle-off.png"));
  item->signal_activated()->connect(boost::bind(&SqlEditorResult::toggle_switcher_collapsed, this));
  item->set_checked(!_switcher.get_collapsed());
  tbar->add_item(item);
  _collapse_toggled_sig = _collapse_toggled.connect(boost::bind(&mforms::ToolBarItem::set_checked, item, _1));
}


void SqlEditorResult::switcher_collapsed()
{
  bool state = _switcher.get_collapsed();
  for (std::list<mforms::ToolBar*>::const_iterator it = _toolbars.begin(); it != _toolbars.end(); ++it)
  {
    (*it)->find_item("sidetoggle")->set_checked(state);
  }
  relayout();
  
  bec::GRTManager *grtm = _owner->owner()->grt_manager();
  grtm->set_app_option("Recordset:SwitcherCollapsed", grt::IntegerRef(state?1:0));
}


void SqlEditorResult::show_export_recordset()
{
  bec::GRTManager *grtm = _owner->owner()->grt_manager();
  try
  {
    RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR (Recordset, _rset, rs)
    {
      grt::ValueRef option(grtm->get_app_option("Recordset:LastExportPath"));
      std::string path = option.is_valid() ? grt::StringRef::cast_from(option) : "";
      option = grtm->get_app_option("Recordset:LastExportExtension");
      std::string extension = option.is_valid() ? grt::StringRef::cast_from(option) : "";
      InsertsExportForm exporter(0/*mforms::Form::main_form()*/, rs_ref, extension);
      exporter.set_title(_("Export Resultset"));
      if (!path.empty())
        exporter.set_path(path);
      path = exporter.run();
      if (path.empty())
        grtm->replace_status_text(_("Export resultset canceled"));
      else
      {
        grtm->replace_status_text(strfmt(_("Exported resultset to %s"), path.c_str()));
        grtm->set_app_option("Recordset:LastExportPath", grt::StringRef(path));
        extension = base::extension(path);
        if (!extension.empty() && extension[0] == '.')
          extension = extension.substr(1);
        if (!extension.empty())
          grtm->set_app_option("Recordset:LastExportExtension", grt::StringRef(extension));
      }
    }
  }
  catch (const std::exception &exc)
  {
    mforms::Utilities::show_error("Error exporting recordset", exc.what(), "OK");
  }
}


void SqlEditorResult::show_import_recordset()
{
  bec::GRTManager *grtm = _owner->owner()->grt_manager();
  try
  {
    RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, _rset, rs)
    {
      grt::BaseListRef args(grtm->get_grt());

      if (result_grtobj().is_valid())
      {
        args.ginsert(result_grtobj());
        grt::Module *module = grtm->get_grt()->get_module("SQLIDEUtils");
        if (module)
          module->call_function("importRecordsetDataFromFile", args);
      }
      else
        log_fatal("resultset GRT obj is NULL\n");
    }
  }
  catch (const std::exception &exc)
  {
    mforms::Utilities::show_error("Error importing recordset", exc.what(), "OK");
  }
}


void SqlEditorResult::toggle_switcher_collapsed()
{
  bool flag = !_switcher.get_collapsed();
  _switcher.set_collapsed(flag);
  _collapse_toggled(flag);
}


void SqlEditorResult::on_recordset_column_resized(int column)
{
  if (column >= 0)
  {
    std::string column_id = _column_width_storage_ids[column];
    int width = _result_grid->get_column_width(column);
   _owner->owner()->column_width_cache()->save_column_width(column_id, width);
  }
}

grt::ValueRef run_and_return(const boost::function<void()>& f)
{
  f();
  return grt::ValueRef();
}

void SqlEditorResult::onRecordsetColumnsResized(const std::vector<int> cols)
{
  std::vector<int>::const_iterator it;
  std::map<std::string, int> widths;
  for(it = cols.begin(); it != cols.end(); ++it)
  {
    if (*it >= 0)
    {
      std::string column_id = _column_width_storage_ids[*it];
      int width = _result_grid->get_column_width(*it);
      widths.insert(std::make_pair(column_id, width));
    }
  }
  if (!widths.empty())
  {
    boost::function<void()> f = boost::bind(&ColumnWidthCache::save_columns_width, _owner->owner()->column_width_cache(), widths);
    _owner->owner()->grt_manager()->get_dispatcher()->execute_async_function("store column widths", boost::bind(&run_and_return, f));
  }
}

void SqlEditorResult::reset_column_widths()
{
  ColumnWidthCache *cache = _owner->owner()->column_width_cache();

  RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, _rset, rs)
  {
    Recordset_cdbc_storage::Ref storage(boost::dynamic_pointer_cast<Recordset_cdbc_storage>(rs->data_storage()));
    std::vector<Recordset_cdbc_storage::FieldInfo> &field_info(storage->field_info());

    for (int c = (int)field_info.size(), i = 0; i < c; i++)
    {
      std::string column_storage_id;

      column_storage_id = field_info[i].field + "::" + field_info[i].schema + "::" + field_info[i].table;

      cache->delete_column_width(column_storage_id);
    }
  }

  restore_grid_column_widths();
}


std::vector<float> SqlEditorResult::get_autofit_column_widths(Recordset *rs)
{
  std::vector<float> widths(rs->get_column_count());
  std::string font = _owner->owner()->grt_manager()->get_app_option_string("workbench.general.Resultset:Font");

  for (size_t c = rs->get_column_count(), j = 0; j < c; j++)
  {
    widths[j] = (float)mforms::Utilities::get_text_width(rs->get_column_caption(j), font);
  }

  // look in 1st 10 rows for the max width of the columns
  for (size_t i = 0; i < 10; i++)
  {
    for (size_t c = rs->get_column_count(), j = 0; j < c; j++)
    {
      std::string value;
      rs->get_field(i, j, value);
      widths[j] = std::max(widths[j], (float)mforms::Utilities::get_text_width(value, font));
    }
  }
  return widths;
}


void SqlEditorResult::restore_grid_column_widths()
{
  ColumnWidthCache *cache = _owner->owner()->column_width_cache();

  RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, _rset, rs)
  {
    Recordset_cdbc_storage::Ref storage(boost::dynamic_pointer_cast<Recordset_cdbc_storage>(rs->data_storage()));
    std::vector<Recordset_cdbc_storage::FieldInfo> &field_info(storage->field_info());
    std::vector<float> autofit_widths;

    for (int c = (int)field_info.size(), i = 0; i < c; i++)
    {
      std::string column_storage_id;

      column_storage_id = field_info[i].field + "::" + field_info[i].schema + "::" + field_info[i].table;
      _column_width_storage_ids.push_back(column_storage_id);

      // check if we have a remembered column width
      int width = cache->get_column_width(column_storage_id);
      if (width > 0)
      {
        _result_grid->set_column_width(i, width);
      }
      else
      {
        // if not, we set a default width based on the width of the 1st 50 rows
        if (autofit_widths.empty())
          autofit_widths = get_autofit_column_widths(rs);

        int width = int(autofit_widths[i] + 10);
        if (width < 40)
          width = 40;
        else if (width > 250)
          width = 250;
        _result_grid->set_column_width(i, width);
      }
    }
  }
}


void SqlEditorResult::dock_result_grid(mforms::RecordGrid *view)
{
  _result_grid = view;
  view->set_name("result-grid-wrapper");

  mforms::AppView *grid_view;
  {
    mforms::AppView *box = grid_view = mforms::manage(new mforms::AppView(false, "ResultGridView", false));
    box->set_name("resultset-host");
    mforms::ToolBar *tbar = _rset.lock()->get_toolbar();
    tbar->set_name("resultset-toolbar");
    _toolbars.push_back(tbar);

    add_switch_toggle_toolbar_item(tbar);
    
    box->add(tbar, false, true);
    box->add(view, true, true);

    box->set_title("Result\nGrid");
    box->set_identifier("result_grid");
    _tabdock.dock_view(box, "output_type-resultset.png");
  }
  {
    bool editable = false;
    if (Recordset::Ref rset = _rset.lock())
      editable = !rset->is_readonly();
    _form_result_view = mforms::manage(new ResultFormView(editable));
    add_switch_toggle_toolbar_item(_form_result_view->get_toolbar());
    _form_result_view->set_title("Form\nEditor");
    _form_result_view->set_identifier("form_result");
    _tabdock.dock_view(_form_result_view, "output_type-formeditor.png");
  }
  {
    _column_info_box = mforms::manage(new mforms::AppView(false, "ResultFieldTypes", false));
    _column_info_box->set_back_color("#ffffff");
    _column_info_box->set_title("Field\nTypes");
    _column_info_box->set_identifier("column_info");
    _tabdock.dock_view(_column_info_box, "output_type-fieldtypes.png");
  }
  {
    _query_stats_box = mforms::manage(new mforms::AppView(false, "ResultQueryStats", false));
    _query_stats_box->set_back_color("#ffffff");
    _query_stats_box->set_title("Query\nStats");
    _query_stats_box->set_identifier("query_stats");
    _tabdock.dock_view(_query_stats_box, "output_type-querystats.png");
  }

  create_spatial_view_panel_if_needed();



  // reorder the explain tab to last
  bool has_explain_tab = false;
  for (int i = 0; i < _tabdock_delegate->view_count(); i++)
  {
    mforms::AppView *view = _tabdock_delegate->view_at_index(i);
    if (!view)
      continue;

    if (view->identifier() == "execution_plan")
    {
      has_explain_tab = true;
      view->retain();
      _tabdock_delegate->undock_view(view);
      _tabdock.dock_view(view, "output_type-executionplan.png");
      view->release();
      break;
    }
  }
  if (!has_explain_tab)
  {
    _execution_plan_placeholder = mforms::manage(new mforms::AppView(false, "ExecutionPlan", false));
    _execution_plan_placeholder->set_back_color("#ffffff");
    _execution_plan_placeholder->set_title("Execution\nPlan");
    _execution_plan_placeholder->set_identifier("execution_plan");
    _tabdock.dock_view(_execution_plan_placeholder, "output_type-executionplan.png");
  }
  _switcher.set_selected(0);

}

void SqlEditorResult::create_spatial_view_panel_if_needed()
{
  if (Recordset::Ref rset = _rset.lock())
  {
    Recordset_cdbc_storage::Ref storage(boost::dynamic_pointer_cast<Recordset_cdbc_storage>(rset->data_storage()));
    bool has_geometry = false;
    std::vector<Recordset_cdbc_storage::FieldInfo> &field_info(storage->field_info());
    for (std::vector<Recordset_cdbc_storage::FieldInfo>::const_iterator iter = field_info.begin();
         iter != field_info.end(); ++iter)
    {
      if (iter->type == "GEOMETRY")
      {
        has_geometry = true;
        break;
      }
    }
    if (has_geometry)
    {
      if (!spatial::Projection::get_instance().check_libproj_availability())
      {
        mforms::Utilities::show_message_and_remember("Unable to initialize Spatial Viewer",
                                                     "Spatial support requires the PROJ.4 library (libproj). If you already have it installed, please set the PROJSO environment variable to its location before starting Worbench.",
                                                     "Ok", "", "",
                                                     "SqlEditorResult.libprojcheck", "");
        return;
      }
      _spatial_result_view = mforms::manage(new SpatialDataView(this));
      add_switch_toggle_toolbar_item(_spatial_result_view->get_toolbar());
      mforms::AppView *box = mforms::manage(new mforms::AppView(false, "SpatialView", false));
      box->set_title("Spatial\nView");
      box->set_identifier("spatial_result_view");
      box->set_name("spatial-host");
      box->add(_spatial_result_view, true, true);
      _tabdock.dock_view(box, "output_type-spacialview.png");
    }
  }
}


static std::string format_ps_time(boost::int64_t t)
{
  int hours, mins;
  double secs;

  secs = t / 1000000000000.0;
  mins = int(secs / 60) % 60;
  hours = mins / 3600;

  return base::strfmt("%i:%02i:%02.8f", hours, mins, secs);
}

static mforms::Label *bold_label(const std::string text)
{
  mforms::Label *l = mforms::manage(new mforms::Label(text));
  l->set_style(mforms::BoldStyle);
  return l;
}


void SqlEditorResult::copy_column_info(mforms::TreeNodeView *tree)
{
  std::list<mforms::TreeNodeRef> nodes(tree->get_selection());
  std::string text;

  for (std::list<mforms::TreeNodeRef>::const_iterator node = nodes.begin(); node != nodes.end(); ++node)
  {
    text.append(base::strfmt("%i", (*node)->get_int(0)));
    for (int i= 1; i < tree->get_column_count(); i++)
    {
      if (i >= 1 && i <= 5)
        text.append(",").append((*node)->get_string(i));
      else
        text.append(",").append(base::strfmt("%i", (*node)->get_int(i)));
    }
    text.append("\n");
  }
  mforms::Utilities::set_clipboard_text(text);
}


void SqlEditorResult::copy_column_info_name(mforms::TreeNodeView *tree)
{
  std::list<mforms::TreeNodeRef> nodes(tree->get_selection());
  std::string text;

  for (std::list<mforms::TreeNodeRef>::const_iterator node = nodes.begin(); node != nodes.end(); ++node)
  {
    text.append((*node)->get_string(1)).append("\n");
  }
  mforms::Utilities::set_clipboard_text(text);
}


void SqlEditorResult::create_column_info_panel()
{
  RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, _rset, rs)
  {
    Recordset_cdbc_storage::Ref storage(boost::dynamic_pointer_cast<Recordset_cdbc_storage>(rs->data_storage()));

    mforms::Box *box = _column_info_box;
    mforms::ToolBar *tbar = mforms::manage(new mforms::ToolBar(mforms::SecondaryToolBar));
    _toolbars.push_back(tbar);
    mforms::ToolBarItem *item;
    item = mforms::manage(new mforms::ToolBarItem(mforms::TitleItem));
    item->set_text("Field Types");
    tbar->add_item(item);

    add_switch_toggle_toolbar_item(tbar);

    box->add(tbar, false, true);

    if (_owner->owner()->collect_field_info())
    {
      mforms::TreeNodeView *tree = mforms::manage(new mforms::TreeNodeView(mforms::TreeFlatList|mforms::TreeAltRowColors|mforms::TreeShowRowLines|mforms::TreeShowColumnLines|mforms::TreeNoBorder));
      tree->add_column(mforms::IntegerColumnType, "#", 50);
      tree->add_column(mforms::StringColumnType, "Field", 130);
      tree->add_column(mforms::StringColumnType, "Schema", 130);
      tree->add_column(mforms::StringColumnType, "Table", 130);
      tree->add_column(mforms::StringColumnType, "Type", 150);
      tree->add_column(mforms::StringColumnType, "Character Set", 100);
      tree->add_column(mforms::IntegerColumnType, "Display Size", 80);
      tree->add_column(mforms::IntegerColumnType, "Precision", 80);
      tree->add_column(mforms::IntegerColumnType, "Scale", 80);
      tree->end_columns();

      tree->set_selection_mode(mforms::TreeSelectMultiple);

      _column_info_menu = new mforms::ContextMenu();
      _column_info_menu->add_item_with_title("Copy", boost::bind(&SqlEditorResult::copy_column_info, this, tree));
      _column_info_menu->add_item_with_title("Copy Name", boost::bind(&SqlEditorResult::copy_column_info_name, this, tree));
      tree->set_context_menu(_column_info_menu);

      int i = 0;
      std::vector<Recordset_cdbc_storage::FieldInfo> &field_info(storage->field_info());
      for (std::vector<Recordset_cdbc_storage::FieldInfo>::const_iterator iter = field_info.begin();
           iter != field_info.end(); ++iter)
      {
        mforms::TreeNodeRef node = tree->add_node();
        node->set_int(0, ++i);
        node->set_string(1, iter->field);
        node->set_string(2, iter->schema);
        node->set_string(3, iter->table);
        node->set_string(4, iter->type);
        node->set_string(5, iter->charset);
        node->set_int(6, iter->display_size);
        node->set_int(7, iter->precision);
        node->set_int(8, iter->scale);
      }
      box->add(tree, true, true);
    }
  }
}


static std::string render_stages(std::vector<SqlEditorForm::PSStage> &stages)
{
  std::string path = mforms::Utilities::get_special_folder(mforms::ApplicationData) + "/stages.png";
  static struct Color {
    double r, g, b;
  } colors[] = {
    {0.17, 0.34, 0.89},
    {0.89, 0.34, 0.17},
    {0.34, 0.89, 0.17},

    {1.00, 0.37, 0.37},

    {0.17, 0.89, 0.89},
    {0.89, 0.89, 0.17},
    {0.89, 0.17, 0.89},

    {0.37, 0.64, 0.64},
    {0.64, 0.37, 0.64},
    {0.64, 0.64, 0.37},
  };
  int ncolors = sizeof(colors)/sizeof(Color);
  double total = 0;

  for (size_t i = 0; i < stages.size(); i++)
  {
    total += stages[i].wait_time;
  }

  int rows_of_text = (int)stages.size() / 3 + 1;
  cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 800, 30 + 20 + rows_of_text * 25);
  cairo_t *cr = cairo_create(surf);

  cairo_set_font_size(cr, 12);

  cairo_set_line_width(cr, 1);
  cairo_set_source_rgb(cr, 1, 1, 1);
  cairo_paint(cr);

  double x = 0.0;
  for (size_t i = 0; i < stages.size(); i++)
  {
    cairo_set_source_rgb(cr, colors[i%ncolors].r, colors[i%ncolors].g, colors[i%ncolors].b);
    cairo_rectangle(cr, x, 0, x + stages[i].wait_time * 800 / total, 30);
    cairo_fill(cr);

    {
      double capx = (i % 3) * 800.0 / 3 + 1;
      double capy = 50 + (i / 3) * 25.0;

      cairo_text_extents_t ext;
      cairo_text_extents(cr, stages[i].name.c_str(), &ext);

      cairo_save(cr);
      cairo_set_source_rgb(cr, 0, 0, 0);
      cairo_move_to(cr, floor(capx) + 30, capy + 25 + ext.y_bearing);
      if (base::starts_with(stages[i].name, "stage/sql/"))
        cairo_show_text(cr, base::strfmt("%s - %.4fms", stages[i].name.c_str() + sizeof("stage/sql/")-1, stages[i].wait_time).c_str());
      else
        cairo_show_text(cr, base::strfmt("%s - %.4fms", stages[i].name.c_str(), stages[i].wait_time).c_str());
      cairo_rectangle(cr, floor(capx), capy, 20, 20);
      cairo_stroke_preserve(cr);
      cairo_restore(cr);
      cairo_fill(cr);
    }
    x += stages[i].wait_time * 800 / total;
  }

  cairo_rectangle(cr, 0, 0, 800, 30);
  cairo_set_source_rgb(cr, 0, 0, 0);
  cairo_stroke(cr);

  cairo_set_line_width(cr, 3);
  cairo_set_source_rgba(cr, 0, 0, 0, 0.3);
  cairo_move_to(cr, 2, 28);
  cairo_line_to(cr, 2, 2);
  cairo_line_to(cr, 798, 2);
  cairo_stroke(cr);

  cairo_surface_write_to_png(surf, path.c_str());
  cairo_destroy(cr);
  cairo_surface_destroy(surf);

  return path;
}


static std::string render_waits(std::vector<SqlEditorForm::PSWait> &waits)
{
  std::string path = mforms::Utilities::get_special_folder(mforms::ApplicationData) + "/waits.png";
  static struct Color {
    double r, g, b;
  } colors[] = {
    {0.17, 0.34, 0.89},
    {0.89, 0.34, 0.17},
    {0.34, 0.89, 0.17},

    {1.00, 0.37, 0.37},

    {0.17, 0.89, 0.89},
    {0.89, 0.89, 0.17},
    {0.89, 0.17, 0.89},

    {0.37, 0.64, 0.64},
    {0.64, 0.37, 0.64},
    {0.64, 0.64, 0.37},
  };
  int ncolors = sizeof(colors)/sizeof(Color);
  double total = 0;

  for (size_t i = 0; i < waits.size(); i++)
  {
    total += waits[i].wait_time;
  }

  int rows_of_text = (int)waits.size() / 2 + 1;
  cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 800, 30 + 20 + rows_of_text * 25);
  cairo_t *cr = cairo_create(surf);

  cairo_set_font_size(cr, 12);

  cairo_set_line_width(cr, 1);
  cairo_set_source_rgb(cr, 1, 1, 1);
  cairo_paint(cr);

  double x = 0.0;
  for (size_t i = 0; i < waits.size(); i++)
  {
    cairo_set_source_rgb(cr, colors[i%ncolors].r, colors[i%ncolors].g, colors[i%ncolors].b);
    cairo_rectangle(cr, x, 0, x + waits[i].wait_time * 800 / total, 30);
    cairo_fill(cr);

    {
      double capx = (i % 2) * 800.0 / 2 + 1;
      double capy = 50 + (i / 2) * 25.0;

      cairo_text_extents_t ext;
      cairo_text_extents(cr, waits[i].name.c_str(), &ext);

      cairo_save(cr);
      cairo_set_source_rgb(cr, 0, 0, 0);
      cairo_move_to(cr, floor(capx) + 30, capy + 25 + ext.y_bearing);
      cairo_show_text(cr, base::strfmt("%s - %.4fms", waits[i].name.c_str(), waits[i].wait_time).c_str());

      cairo_rectangle(cr, floor(capx), capy, 20, 20);
      cairo_stroke_preserve(cr);
      cairo_restore(cr);
      cairo_fill(cr);
    }
    x += waits[i].wait_time * 800 / total;
  }

  cairo_rectangle(cr, 0, 0, 800, 30);
  cairo_set_source_rgb(cr, 0, 0, 0);
  cairo_stroke(cr);

  cairo_set_line_width(cr, 3);
  cairo_set_source_rgba(cr, 0, 0, 0, 0.3);
  cairo_move_to(cr, 2, 28);
  cairo_line_to(cr, 2, 2);
  cairo_line_to(cr, 798, 2);
  cairo_stroke(cr);

  cairo_surface_write_to_png(surf, path.c_str());
  cairo_destroy(cr);
  cairo_surface_destroy(surf);
  
  return path;
}



void SqlEditorResult::create_query_stats_panel()
{
  RETURN_IF_FAIL_TO_RETAIN_WEAK_PTR(Recordset, _rset, rs)
  {
    SqlEditorForm::RecordsetData* rsdata = dynamic_cast<SqlEditorForm::RecordsetData*>(rs->client_data());
    std::string info;
    
    mforms::ScrollPanel *spanel = mforms::manage(new mforms::ScrollPanel());
    mforms::Table *table = mforms::manage(new mforms::Table());
    table->set_padding(20);
    table->set_row_count(6);
    table->set_column_count(2);
    table->set_row_spacing(4);
    spanel->set_back_color("#ffffff");

    mforms::ToolBar *tbar = mforms::manage(new mforms::ToolBar(mforms::SecondaryToolBar));
    _toolbars.push_back(tbar);
    mforms::ToolBarItem *item;
    item = mforms::manage(new mforms::ToolBarItem(mforms::TitleItem));
    item->set_text("Query Statistics");
    tbar->add_item(item);

    add_switch_toggle_toolbar_item(tbar);

    _query_stats_box->add(tbar, false, true);
    
    mforms::Box *box = mforms::manage(new mforms::Box(false));
    box->set_padding(8);
    // show basic stats
    box->add(bold_label("Timing (as measured at client side):"), false, true);
    info.clear();
    info = strfmt("Execution time: %s\n", format_ps_time(boost::int64_t(rsdata->duration * 1000000000000.0)).c_str());
    box->add(mforms::manage(new mforms::Label(info)), false, true);
    
    // if we're in a server with PS, show some extra PS goodies
    std::map<std::string, boost::int64_t> &ps_stats(rsdata->ps_stat_info);

    if (ps_stats.empty())
    {
      if (!rsdata->ps_stat_error.empty())
        box->add(bold_label(rsdata->ps_stat_error), false, true);
      else
        box->add(bold_label("For more details, enable \"statement instrumentation\" for the Performance Schema and \"Query -> Collect Performance Schema Stats\"."), false, true);
        
      table->add(box, 0, 1, 1, 2, mforms::HFillFlag|mforms::HExpandFlag);
    }
    else
    {
      box->add(bold_label("Timing (as measured by the server):"), false, true);
      info.clear();
      info.append(strfmt("Execution time: %s\n", format_ps_time(ps_stats["TIMER_WAIT"]).c_str()));
      info.append(strfmt("Table lock wait time: %s\n", format_ps_time(ps_stats["LOCK_TIME"]).c_str()));
      box->add(mforms::manage(new mforms::Label(info)), false, true);

      box->add(bold_label("Errors:"), false, true);
      info.clear();
      info.append(strfmt("Had Errors: %s\n", ps_stats["ERRORS"] ? "YES" : "NO"));
      info.append(strfmt("Warnings: %" PRId64 "\n", ps_stats["WARNINGS"]));
      box->add(mforms::manage(new mforms::Label(info)), false, true);

      box->add(bold_label("Rows Processed:"), false, true);
      info.clear();
      info.append(strfmt("Rows affected: %" PRId64 "\n", ps_stats["ROWS_AFFECTED"])),
      info.append(strfmt("Rows sent to client: %" PRId64 "\n", ps_stats["ROWS_SENT"]));
      info.append(strfmt("Rows examined: %" PRId64 "\n", ps_stats["ROWS_EXAMINED"]));
      box->add(mforms::manage(new mforms::Label(info)), false, true);

      box->add(bold_label("Temporary Tables:"), false, true);
      info.clear();
      info.append(strfmt("Temporary disk tables created: %" PRId64 "\n", ps_stats["CREATED_TMP_DISK_TABLES"]));
      info.append(strfmt("Temporary tables created: %" PRId64 "\n", ps_stats["CREATED_TMP_TABLES"]));
      box->add(mforms::manage(new mforms::Label(info)), false, true);
      
      table->add(box, 0, 1, 1, 2, mforms::HFillFlag|mforms::HExpandFlag);

      box = mforms::manage(new mforms::Box(false));
      box->set_padding(8);

      box->add(bold_label("Joins per Type:"), false, true);
      info.clear();
      info.append(strfmt("Full table scans (Select_scan): %" PRId64 "\n", ps_stats["SELECT_SCAN"]));
      info.append(strfmt("Joins using table scans (Select_full_join): %" PRId64 "\n", ps_stats["SELECT_FULL_JOIN"]));
      info.append(strfmt("Joins using range search (Select_full_range_join): %" PRId64 "\n", ps_stats["SELECT_FULL_RANGE_JOIN"]));
      info.append(strfmt("Joins with range checks (Select_range_check): %" PRId64 "\n", ps_stats["SELECT_RANGE_CHECK"]));
      info.append(strfmt("Joins using range (Select_range): %" PRId64 "\n", ps_stats["SELECT_RANGE"]));
      box->add(mforms::manage(new mforms::Label(info)), false, true);

      box->add(bold_label("Sorting:"), false, true);
      info.clear();
      info.append(strfmt("Sorted rows (Sort_rows): %" PRId64 "\n", ps_stats["SORT_ROWS"]));
      info.append(strfmt("Sort merge passes (Sort_merge_passes): %" PRId64 "\n", ps_stats["SORT_MERGE_PASSES"]));
      info.append(strfmt("Sorts with ranges (Sort_range): %" PRId64 "\n", ps_stats["SORT_RANGE"]));
      info.append(strfmt("Sorts with table scans (Sort_scan): %" PRId64 "\n", ps_stats["SORT_SCAN"]));
      box->add(mforms::manage(new mforms::Label(info)), false, true);

      box->add(bold_label("Index Usage:"), false, true);
      info.clear();
      if (ps_stats["NO_INDEX_USED"])
        info.append("No Index used");
      else
        info.append("At least one Index was used");
      if (ps_stats["NO_GOOD_INDEX_USED"])
        info.append("No good index used");
      info.append("\n");
      box->add(mforms::manage(new mforms::Label(info)), false, true);

      box->add(bold_label("Other Info:"), false, true);
      info.clear();
      info.append(strfmt("Event Id: %" PRId64 "\n", ps_stats["EVENT_ID"]));
      info.append(strfmt("Thread Id: %" PRId64 "\n", ps_stats["THREAD_ID"]));
      box->add(mforms::manage(new mforms::Label(info)), false, true);

      table->add(box, 1, 2, 1, 2, mforms::HFillFlag|mforms::HExpandFlag);
    }

    std::vector<SqlEditorForm::PSStage> stages(rsdata->ps_stage_info);
    if (!stages.empty())
    {
      mforms::Label *l = mforms::manage(new mforms::Label("Time Spent per Execution Stage (aggregated)"));
      l->set_text_align(mforms::MiddleCenter);
      l->set_style(mforms::BoldStyle);
      table->add(l, 0, 2, 2, 3, mforms::HFillFlag);

      std::string file = render_stages(stages);
      mforms::ImageBox *image = mforms::manage(new mforms::ImageBox());
      image->set_image(file);
      table->add(image, 0, 2, 3, 4, mforms::HFillFlag);
    }

    std::vector<SqlEditorForm::PSWait> waits(rsdata->ps_wait_info);
    if (!waits.empty())
    {
      mforms::Label *l = mforms::manage(new mforms::Label("Time Spent Waiting (aggregated)"));
      l->set_text_align(mforms::MiddleCenter);
      l->set_style(mforms::BoldStyle);
      table->add(l, 0, 2, 4, 5, mforms::HFillFlag);

      std::string file = render_waits(waits);
      mforms::ImageBox *image = mforms::manage(new mforms::ImageBox());
      image->set_image(file);
      table->add(image, 0, 2, 5, 6, mforms::HFillFlag);
    }

    spanel->add(table);

    _query_stats_box->add(spanel, true, true);
  }
}


void SqlEditorResult::view_record_in_form(int row_id)
{
  if (_form_result_view)
  {
    _tabview.set_active_tab(1);
    switch_tab();
    _form_result_view->display_record(row_id);
  }
}
