#include "editor_base.h"
#include "gtk/lf_mforms.h"
#include "sqlide_form.h"
#include "linux_utilities/gtk_helpers.h"
#include "base/util_functions.h"
#include "base/wb_iterators.h"
#include "base/log.h"
#include "linux_utilities/toolbar_manager.h"
#include "mforms/../gtk/lf_view.h"
#include "mforms/../gtk/lf_menubar.h"
#include "mforms/../gtk/lf_toolbar.h"
#include "objimpl/ui/mforms_ObjectReference_impl.h"
#include "sqlide/query_side_palette.h"
#include <glib.h>
#include "grt/common.h"
#include "widget_saver.h"
#include "plugin_editor_base.h"

DEFAULT_LOG_DOMAIN("UI")
using base::strfmt;

static const std::vector<bec::NodeId> selected_nodeids(GridView& g)
{
  std::vector<int> rows = g.get_selected_rows();

  std::vector<bec::NodeId>  entries;
  entries.reserve(rows.size());

  for (base::const_range<std::vector<int> > it(rows); it; ++it)
    entries.push_back(*it);

  return entries;
}

//==============================================================================
//
//==============================================================================
struct SigcBlocker
{
  SigcBlocker(sigc::connection& c) : _c(c) {_c.block();}
  ~SigcBlocker() {_c.unblock();}

  sigc::connection& _c;
};

void drop_eol(const int column, Glib::ValueBase* vbase)
{
  if (column == 7)
  {
    GValue *vb = vbase->gobj();
    char* str = g_value_dup_string(vb);
    char* tstr = str;
    while (*tstr++)
    {
      if (*tstr == '\n')
        *tstr = ' ';
    }
    g_value_take_string(vb, str);
  }
}

//==============================================================================
//
//==============================================================================
QueryOutputView::QueryOutputView(const SqlEditorForm::Ref& be, DbSqlEditorView *db_sql_editor_view)
          : _be(be)
          , _action_output(be->log(), true)
          , _entries_grid(be->history()->entries_model(), true, false)
          , _details_grid(be->history()->details_model(), true, false)
          , _db_sql_editor_view(db_sql_editor_view)
{
  const char* const sections[] = {"Action Output", "Text Output", "History"};
  _action_output.show();
  _action_output.row_numbers_visible(false);
  _action_output.set_fixed_height_mode(true);
  _action_output.refresh(true);
  _action_output.view_model()->before_render = sigc::ptr_fun(drop_eol);
  _action_output.set_has_tooltip(true);
  _action_output.signal_query_tooltip().connect(sigc::mem_fun(this, &QueryOutputView::on_query_tooltip));

  _action_swnd.add(_action_output);
  _action_swnd.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  
  {
    Gtk::TreeViewColumn *col;
    col = _action_output.get_column(0); // icon
    if (col)
      col->set_resizable(false);
    col = _action_output.get_column(1); // index
    if (col)
      col->set_fixed_width(40);
    col = _action_output.get_column(2); // time
    if (col)
      col->set_fixed_width(80);
    col = _action_output.get_column(3); // action
    if (col)
    {
      col->set_fixed_width(400);
    }
    col = _action_output.get_column(4); // message
    if (col)
    {
      col->set_fixed_width(350);
    }
    col = _action_output.get_column(5); // duration
    if (col)
    {
      col->set_fixed_width(150);
      col->set_resizable(false);
    }
  }

  mforms::Menu* context_menu = be->log()->get_context_menu();
  _action_output.set_context_menu(context_menu);
  context_menu->signal_will_show()->connect(boost::bind(&QueryOutputView::output_menu_will_show, this));

  _entries_grid.set_context_menu_responder(sigc::mem_fun(this, &QueryOutputView::history_context_menu_responder));

  context_menu = be->history()->details_model()->get_context_menu();
  context_menu->set_handler(sigc::mem_fun(this, &QueryOutputView::handle_history_context_menu));
  _details_grid.set_context_menu(context_menu);

  _be->history()->entries_model()->refresh_ui_cb = sigc::mem_fun(this, &QueryOutputView::on_history_entries_refresh);
  _be->history()->details_model()->refresh_ui_cb = sigc::mem_fun(this, &QueryOutputView::on_history_details_refresh);

  _on_history_entries_selection_changed_conn = _entries_grid.get_selection()->signal_changed().connect(
                                                    sigc::mem_fun(this, &QueryOutputView::on_history_entries_selection_changed));

  _entries_grid.refresh(true);
  _details_grid.refresh(true);


  for (size_t i = 0; i < (sizeof(sections) / sizeof(const char* const)); ++i)
    _mode.append_text(sections[i]);

  _text_swnd.add(_text_output);
  _text_swnd.show_all();

  _entries_swnd.add(_entries_grid);
  _details_swnd.add(_details_grid);

  _entries_swnd.show_all();
  _details_swnd.show_all();

  _history_box.pack1(_entries_swnd, Gtk::FILL);
  _entries_swnd.set_size_request(100, -1);
  _history_box.pack2(_details_swnd, Gtk::EXPAND);
  _history_box.show_all();

  _note.append_page(_action_swnd,   sections[0]);
  _note.append_page(_text_swnd, sections[1]);
  _note.append_page(_history_box, sections[2]);
  _note.show_all();

  _note.set_show_tabs(false);

  Gtk::HBox  *mode_box = Gtk::manage(new Gtk::HBox());
  Gtk::Label *spacer   = Gtk::manage(new Gtk::Label());
  mode_box->pack_start(_mode, false, true);
  mode_box->pack_start(*spacer, true, true);
  _mode.property_has_frame() = false;

  _top_box.pack_start(*mode_box, false, true);
  _top_box.pack_start(_note, true, true);
  _top_box.show_all();

  _be->log()->refresh_ui_cb = sigc::bind<bool>(sigc::mem_fun(_action_output, &GridView::refresh), false);

  _mode.signal_changed().connect(sigc::mem_fun(this, &QueryOutputView::mode_change_requested));
  _mode.set_active(0);
}


//------------------------------------------------------------------------------
bool QueryOutputView::on_query_tooltip(int x, int y, bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip>& tooltip)
{
  Gtk::TreePath path;
  if (_action_output.get_path_at_pos(x, y, path))
  {
    std::string action, response, duration;
    _be->log()->get_field(path[0], 3, action);
    _be->log()->get_field(path[0], 4, response);
    _be->log()->get_field(path[0], 5, duration);
    if (duration.empty())
      tooltip->set_markup(base::strfmt("<b>Action:</b> %s\n<b>Response:</b> %s",
              action.c_str(), response.c_str()));
    else
      tooltip->set_markup(base::strfmt("<b>Action:</b> %s\n<b>Response:</b> %s\n<b>Duration:</b> %s", 
              action.c_str(), response.c_str(), duration.c_str()));
    return true;
  }
  return false;
}

//------------------------------------------------------------------------------
void QueryOutputView::mode_change_requested()
{
  const int mode = _mode.get_active_row_number();
  if (mode >= 0)
    _note.set_current_page(mode);
}

//------------------------------------------------------------------------------
void QueryOutputView::refresh()
{
  const int mode = _mode.get_active_row_number();
  switch (mode)
  {
    case 0: // Action Output
    {
      _action_output.refresh(false);

      const int log_row_count = _action_output.row_count();
      if (log_row_count > 0)
      {
        Gtk::TreePath path;
        path.push_back(log_row_count-1);
        _action_output.scroll_to_row(path);
        _action_output.set_cursor(path);
      }
      break;
    }
    case 2: // History output
    {
      const Glib::RefPtr<Gtk::TreeModel> entry_model = _entries_grid.get_model();
      const Gtk::TreeModel::Children children = entry_model->children();
      const int size = children.size();
      if (size > 0)
      {
        const Gtk::TreeIter iter = (--children.end());
        const Gtk::TreePath path = entry_model->get_path(iter);
        _entries_grid.set_cursor(path);
      }

      _details_grid.scroll_to(1);
      break;
    }
  }
}

//------------------------------------------------------------------------------
int QueryOutputView::on_history_entries_refresh()
{
  SigcBlocker signal_block(_on_history_entries_selection_changed_conn);

  _entries_grid.refresh(false);
  _entries_grid.scroll_to(1);

  return 0;
}

//------------------------------------------------------------------------------
int QueryOutputView::on_history_details_refresh()
{
  SigcBlocker signal_block(_on_history_entries_selection_changed_conn);

  _details_grid.refresh(false);
   _details_grid.scroll_to(1);

  return 0;
}

//------------------------------------------------------------------------------
void QueryOutputView::on_history_entries_selection_changed()
{
  const int row = _entries_grid.current_row();
  if (-1 < row)
  {
    _be->history()->current_entry(row);
    _details_grid.refresh(false);
  }
}

//------------------------------------------------------------------------------
void QueryOutputView::output_menu_will_show()
{
  std::vector<int> sel_indices = _action_output.get_selected_rows();
  _be->log()->set_selection(sel_indices);
}

//------------------------------------------------------------------------------
void QueryOutputView::handle_history_context_menu(const std::string& action)
{
  DbSqlEditorHistory::EntriesModel::Ref entries_model = _be->history()->entries_model();

  const std::vector<bec::NodeId> entries = selected_nodeids(_entries_grid);

  if (action == "delete_selection")
  {
    entries_model->activate_popup_item_for_nodes(action, entries);
    _entries_grid.refresh(false);
  }
  else if (action == "delete_all")
  {
    entries_model->activate_popup_item_for_nodes(action, entries);
    _entries_grid.refresh(false);
  }
  else
  {
    const int selected_entry = (entries.size() > 0) ? (*entries.begin())[0] : -1;

    if (selected_entry >= 0)
    {
      if (action == "clear")
      {
        {
          std::vector<int> e(1, selected_entry);
          entries_model->delete_entries(e);
          _entries_grid.refresh(false);
        }
      }
      else
      {
        std::vector<int> rows = _details_grid.get_selected_rows();
        std::list<int>  details;

        for (base::const_range<std::vector<int> > it(rows); it; ++it)
          details.push_back(*it);

        const std::string sql = _db_sql_editor_view->be()->restore_sql_from_history(selected_entry, details);

        if (action == "append_selected_items" || action == "replace_sql_script")
        {
          QueryView* qv = _db_sql_editor_view->active_view();
          if (qv)
            qv->be()->sql(((action == "replace_sql_script") ? sql : qv->be()->sql() + sql).c_str());
        }
        else if (action == "copy_row")
        {
          Glib::RefPtr<Gtk::Clipboard> clip = Gtk::Clipboard::get();
          if (clip)
            clip->set_text(sql);
        }
      }
    }
  }
}

//------------------------------------------------------------------------------
void QueryOutputView::history_context_menu_responder()
{
  const std::vector<bec::NodeId> entries = selected_nodeids(_entries_grid);
  const bec::MenuItemList menuitems = _be->history()->entries_model()->get_popup_items_for_nodes(entries);

  run_popup_menu(menuitems, gtk_get_current_event_time(), sigc::mem_fun(this, &QueryOutputView::handle_history_context_menu), &_context_menu);
}

//------------------------------------------------------------------------------
void QueryOutputView::output_text(const std::string& text, const bool bring_to_front)
{
  Glib::RefPtr<Gtk::TextBuffer> buf = _text_output.get_buffer();
  buf->insert(buf->end(), text);

  if (bring_to_front)
    _mode.set_active(1); // 1 - Text output tab
}

//==============================================================================
//
//==============================================================================
QueryView::QueryView(const int current_index, DbSqlEditorView* owner)
          : _owner(owner)
          , _editor_box(false)
          , _label(0)
          , _query_toolbar(owner->be()->sql_editor_toolbar(current_index))
          , _apply_btn(_("Apply"))
          , _cancel_btn(_("Revert"))
          , _dock_delegate(&_rs_tabs, RESULT_DOCKING_POINT)
          , _dpoint(&_dock_delegate, false)
          , _query_collapsed(false)
          , _updating_results(false)
{
  SqlEditorForm::Ref             be = owner->be();
  Sql_editor::Ref            editor = be->sql_editor(current_index);
  Gtk::Widget&        editor_widget = *mforms::gtk::ViewImpl::get_widget_for_view(editor->get_editor_control());

  _editor = editor;

  {
    db_query_QueryEditorRef qeditor(db_query_QueryEditorRef::cast_from(_editor->grtobj()));
    qeditor->resultDockingPoint(mforms_to_grt(qeditor->get_grt(), &_dpoint, "DockingPoint"));
  }

  _dock_delegate.notebook_changed_signal.connect(sigc::mem_fun(this, &QueryView::update_lower_notebook_visibility));

  editor_widget.set_size_request(-1, 1);

  editor->get_editor_control()->set_show_find_panel_callback(boost::bind(&QueryView::show_find_panel, this, _1, _2));

  _rs_tabs.set_tab_pos(Gtk::POS_BOTTOM);

  boost::shared_ptr<mforms::ToolBar> toolbar(be->sql_editor_toolbar(be->sql_editor_index(editor)));
  Gtk::Widget* w = mforms::gtk::widget_for_toolbar(toolbar.get());
  if (w)
  {
    w->show();
    _editor_box.pack_start(*w, false, true);
    //_rs_box.pack_start(*w, false, true);
  }
  else
    g_warning("No toolbar for editor");

  _rs_box.pack_start(_rs_tabs, true, true);
  _rs_box.show_all();

  _editor_box.pack_end(editor_widget, true, true);
  _top_pane.pack1(_editor_box, true, true);
  _top_pane.pack2(_rs_box, false, true);

  _top_pane.set_name("sqled.query_view.top_pane");
  _top_pane.show_all();
  _top_pane.property_position().signal_changed().connect(sigc::mem_fun(this, &QueryView::top_pane_changed));

  _rs_tabs.signal_switch_page().connect(sigc::mem_fun(this, &QueryView::rs_page_switched));
  _rs_tabs.signal_page_reordered().connect(sigc::mem_fun(this, &QueryView::tab_reordered));

  // map is not reliable, it can be mapped but still not visible on screen. so monitor idle 
 // _polish_conn = _top_pane.signal_map().connect(sigc::mem_fun(this, &QueryView::polish));

  _btn_box.pack_start(_apply_btn, true, true);
  _btn_box.pack_start(_cancel_btn, true, true);
  _btn_box.pack_start(_editability_label, true, true);
  _btn_box.pack_start(_editability_icon, false, true);

  _editability_icon.set(Gdk::Pixbuf::create_from_file(_owner->grt_manager()->get_data_file_path("images/mini_notice.png")));

  _rs_tabs.set_action_widget(&_btn_box, Gtk::PACK_END);
  _btn_box.show_all();
  _editability_label.hide();
  _editability_icon.hide();
  _apply_btn.signal_clicked().connect(sigc::mem_fun(this, &QueryView::apply_recordset_changes));
  _cancel_btn.signal_clicked().connect(sigc::mem_fun(this, &QueryView::discard_recordset_changes));
//  utils::gtk::load_settings(_owner->grt_manager(), &_top_pane);
 _polish_conn = Glib::signal_idle().connect(sigc::bind_return(sigc::mem_fun(this, &QueryView::polish), false));
}

//------------------------------------------------------------------------------
QueryView::~QueryView()
{
}


//------------------------------------------------------------------------------
void QueryView::show_find_panel(mforms::CodeEditor* editor, bool show)
{
  Gtk::Widget *panel = mforms::gtk::ViewImpl::get_widget_for_view(editor->get_find_panel());
  if (show)
  {
    if (!panel->get_parent())
      _editor_box.pack_start(*panel, false, true);
    panel->show();
  }
  else
  {
    panel->hide();
  }
}


//------------------------------------------------------------------------------
void QueryView::top_pane_changed()
{
  if (!_polish_conn.connected() && _rs_tabs.get_n_pages() > 0)
  {
    // user has dragged the splitter (or maybe it was changed by us, but shouldn't be the case)
    _query_collapsed = false;
    utils::gtk::save_settings(_owner->grt_manager(), &_top_pane, false);
  }
}

//------------------------------------------------------------------------------
void QueryView::polish()
{
  if (_owner->be()->sql_editor_start_collapsed(_owner->be()->sql_editor_index(_editor)))
  {
    _query_collapsed = true;
    _top_pane.set_position(0);
  }
  else
    gtk_paned_set_pos_ratio(&_top_pane, 1);
  _polish_conn.disconnect();
}

//------------------------------------------------------------------------------
void QueryView::update_exec_sql_progress(float progress, const std::string &message)
{}

//------------------------------------------------------------------------------
void QueryView::update_label(const std::string& label)
{
  if (_label)
    _label->set_text(label);
}


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

void QueryView::update_recordset_caption()
{
  RecordsetView* rs = active_recordset();
  if (rs)
  {
    ActiveLabel *label = dynamic_cast<ActiveLabel*>(_rs_tabs.get_tab_label(*rs));
    if (label)
      label->set_text(rs->model()->caption());

    const bool enable_buttons = rs->model()->has_pending_changes();
    _btn_box.set_sensitive(enable_buttons);
    if (_owner->be()->get_menubar())
      _owner->be()->get_menubar()->validate();
  }
}

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

static void hide_rs_pane(Gtk::Paned *pane)
{
  void *p = pane->get_data("allow_save");
  pane->set_data("allow_save", 0);
  gtk_paned_set_pos_ratio(pane, 1);
  pane->set_data("allow_save", p);
}


void QueryView::update_resultsets()
{
  const int editor_index = index();

  SqlEditorForm::Ref             be                  = _owner->be();
  const int                      n_recordsets_in_be  = be->recordset_count(editor_index);
  std::vector<Recordset::Ref>    recordsets_from_be;
  std::map<Recordset::Ref, int>  recordset_indexes;

  _updating_results = true;

  recordsets_from_be.reserve(n_recordsets_in_be);

  // prepare list of recordsets in BE, which later be matched against exisiting ones in UI(tabs)
  if (n_recordsets_in_be > 0)
  {
    for (int i = n_recordsets_in_be - 1; i >= 0; --i)
    {
      recordsets_from_be.push_back(be->recordset(editor_index, i));
      recordset_indexes[recordsets_from_be.back()] = i;
    }
  }

  Recordset::Ref              rs;
  const int                   ntabs = _rs_tabs.get_n_pages();
  std::vector<Gtk::Widget*>   orphan_views; // list of views which Recordset::ref is not in BE

  // Walk notebook pages removing existing Recordset objects from the @recordsets_from_be list
  // Also add view to @orphan_views if Recordset is not in BE, for later remove_page
  for (int i = 0; i < ntabs; ++i)
  {
    RecordsetView* view = dynamic_cast<RecordsetView*>(_rs_tabs.get_nth_page(i));
    if (view)
    {
      rs = view->model();
      const size_t before_remove = recordsets_from_be.size();
      base::vector_remove(recordsets_from_be, rs);

      if (recordsets_from_be.size() == before_remove)
        orphan_views.push_back(view);
    }
  }

  // Remove orphan pages
  for (size_t i = 0; i < orphan_views.size(); ++i)
    _rs_tabs.remove_page(*orphan_views[i]);

  // Create new pages
  const int size = recordsets_from_be.size();
  for (int i = 0; i < size; ++i)
  {
    const Recordset::Ref&        rs = recordsets_from_be[i];

    std::auto_ptr<ActiveLabel>   lbl(new ActiveLabel(rs->caption(), sigc::bind(sigc::mem_fun(this, &QueryView::close_recordset), rs->key(), true)));
    std::auto_ptr<RecordsetView> view(RecordsetView::create(rs, 0));


    if (lbl.get() && view.get())
    {
      lbl->show();
      view->show();

      RecordsetView* rs_view   = view.get();

      mforms::Menu* menu = lbl.get()->get_menu();
      init_tab_menu(menu);
      menu->set_handler(sigc::bind(sigc::mem_fun(this, &QueryView::tab_menu_handler), lbl.get(), rs_view));
      rs_view->set_data("active_label", lbl.get());

      const int pos_added = _rs_tabs.append_page(*Gtk::manage(view.release()), *(Gtk::manage(lbl.release())));
      _rs_tabs.set_tab_reorderable(*rs_view, true);

      rs_view->refresh();
      rs_view->show();
      rs->task->msg_cb(sigc::bind(sigc::mem_fun(this, &QueryView::process_task_msg), rs_view));

      _rs_tabs.set_current_page(pos_added);
    }
  }

  if (ntabs == 0 && _rs_tabs.get_n_pages() > 0)
    update_lower_notebook_visibility(true);
  else if (ntabs > 0 && _rs_tabs.get_n_pages() == 0)
    update_lower_notebook_visibility(false);
  _updating_results = false;

  rs_page_switched(0, _rs_tabs.get_current_page());

  reenable_items_in_tab_menus();
  if (_label)
    _label->stop_busy();
}

void QueryView::update_lower_notebook_visibility(bool added)
{
  if (!_query_collapsed)
  {
    const int max_position = _top_pane.get_height() - 200;
    _polish_conn.disconnect();

    if (added && _rs_tabs.get_n_pages() == 1) // first tab was added
      utils::gtk::load_settings(_owner->grt_manager(), &_top_pane, sigc::bind(sigc::ptr_fun(gtk_paned_set_pos_ratio), &_top_pane, 0.4), false, -max_position);
    else if (_rs_tabs.get_n_pages() == 0) // tabs are gone
      Glib::signal_idle().connect_once(sigc::bind(sigc::ptr_fun(hide_rs_pane), &_top_pane));
    else if (_rs_tabs.get_n_pages() > 0) // more tabs was added or replaced
    { // if the current size is too small, make it a bit bigger
      if (_top_pane.get_position() > max_position)
        Glib::signal_idle().connect_once(sigc::bind(sigc::mem_fun(&_top_pane, &Gtk::Paned::set_position), max_position));
    }
  }
}

//------------------------------------------------------------------------------
void QueryView::recalc_rstab_indices()
{
  const int size = _rs_tabs.get_n_pages();
  for (int i = 0; i < size; ++i)
  {
    RecordsetView* view = dynamic_cast<RecordsetView*>(_rs_tabs.get_nth_page(i));
    if (view)
      _owner->be()->recordset_reorder(index(), view->model(), i);
  }
}

//------------------------------------------------------------------------------
void QueryView::close()
{
  _owner->close_editor_tab(this);
}

//------------------------------------------------------------------------------
RecordsetView* QueryView::active_recordset()
{
  RecordsetView* rs(0);
  const int page = _rs_tabs.get_current_page();
  if (page >= 0)
    rs = dynamic_cast<RecordsetView*>(_rs_tabs.get_nth_page(page));

  return rs;
}

//------------------------------------------------------------------------------
void QueryView::close_recordset(long long key, bool confirm)
{
  const int ntabs = _rs_tabs.get_n_pages();

  for (int i = 0; i < ntabs; ++i)
  {
    RecordsetView* view  = dynamic_cast<RecordsetView*>(_rs_tabs.get_nth_page(i));
    if (view)
    {
      const Recordset::Ref model = view->model();
      if (model && model->key() == key)
      {
        close_recordset_by_ptr(view, confirm);
        break;
      }
    }
  }
}

//------------------------------------------------------------------------------
void QueryView::close_recordset_by_ptr(RecordsetView* view, const bool confirm)
{
  if (view)
  {
    if (confirm && view->model()->can_close(true))
    {
      view->model()->close();
     // _rs_tabs.remove_page(*view);
    }
  }

  if (_rs_tabs.get_n_pages() == 0) // tabs are gone
    gtk_paned_set_pos_ratio(&_top_pane, 1);

  reenable_items_in_tab_menus();
}

//------------------------------------------------------------------------------
void QueryView::rs_page_switched(GtkNotebookPage *page, guint page_index)
{
  bool show_action_area = false;
  bool action_area_enabled = false;

  if (_updating_results)
    return;

  RecordsetView* rs = active_recordset();
  if (rs)
  {
    Recordset::Ref rsm = rs->model();
    _owner->be()->active_recordset(index(), rsm);
    show_action_area    = !rsm->is_readonly();
    action_area_enabled = rsm->has_pending_changes();
    _editability_label.set_tooltip_text(rsm->readonly_reason());
    _editability_icon.set_tooltip_text(rsm->readonly_reason());
  }
  else
  {
    _owner->be()->active_recordset(index(), Recordset::Ref());
  }

  if (show_action_area)
  {
    _apply_btn.show();
    _cancel_btn.show();
    _editability_label.hide();
    _editability_icon.hide();
    _btn_box.set_sensitive(action_area_enabled);
  }
  else
  {
    _apply_btn.hide();
    _cancel_btn.hide();
    _editability_label.show();
    _editability_label.set_markup("<small>ReadOnly</small>");
    _editability_icon.show();
  }
}

// Label associated with the view in gtk::notebook
//------------------------------------------------------------------------------
void QueryView::set_linked_label(ActiveLabel* lbl)
{
  _label = lbl;
}

//------------------------------------------------------------------------------
int QueryView::index()
{
  return (_owner && _owner->be()) ? _owner->be()->sql_editor_index(_editor) : -1;
}

//------------------------------------------------------------------------------
void QueryView::save()
{
  const int view_index = index();

  if (view_index >= 0)
    _owner->be()->save_sql_script_file(_owner->be()->sql_editor_path(view_index), view_index);
}

//------------------------------------------------------------------------------
std::string QueryView::editor_path()
{
  std::string  path;
  const int    view_index = index();

  if (view_index >= 0)
    path = _owner->be()->sql_editor_path(view_index);

  return path;
}

//------------------------------------------------------------------------------
void QueryView::apply_recordset_changes()
{
  RecordsetView* rs = active_recordset();
  if (rs)
  {
    Recordset::Ref rsm = rs->model();
    if (rsm->has_pending_changes())
      rsm->apply_changes();
  }
}

//------------------------------------------------------------------------------
void QueryView::discard_recordset_changes()
{
  RecordsetView* rs = active_recordset();
  if (rs)
  {
    Recordset::Ref rsm = rs->model();
    if (rsm->has_pending_changes())
      rsm->rollback();
  }
}

//------------------------------------------------------------------------------
int QueryView::process_task_msg(int msgType, const std::string &message, const std::string &detail, RecordsetView *rsv)
{
  _owner->output_text(message, true);
  return 0;
}

//------------------------------------------------------------------------------
void QueryView::init_tab_menu(mforms::Menu* menu)
{
  menu->add_item("Close Tab", "close tab");
  menu->add_item("Close Other Tabs", "close other tabs");
}

//------------------------------------------------------------------------------
void QueryView::tab_menu_handler(const std::string& action, ActiveLabel* sender, RecordsetView* rsview)
{
  if (action == "close tab")
  {
    Glib::signal_idle().connect(sigc::bind_return(sigc::bind(sigc::mem_fun(this, &QueryView::close_recordset_by_ptr), rsview, true), false));
  }
  else if (action == "close other tabs")
  {
    int size = _rs_tabs.get_n_pages();
    std::vector<RecordsetView*> to_delete;
    to_delete.reserve(size - 1);

    for (int i = 0; i < size; ++i)
    {
      RecordsetView* view  = dynamic_cast<RecordsetView*>(_rs_tabs.get_nth_page(i));
      if (view && rsview != view)
        to_delete.push_back(view);
    }

    size = to_delete.size();
    for (int i = 0; i < size; ++i)
      close_recordset_by_ptr(to_delete[i], true);
  }
}

//------------------------------------------------------------------------------
void QueryView::reenable_items_in_tab_menus()
{
  const int size = _rs_tabs.get_n_pages();

  for (int i = 0; i < size; ++i)
  {
    ActiveLabel* const al = (ActiveLabel*)_rs_tabs.get_nth_page(i)->get_data("active_label");
    if (al)
    {
      mforms::Menu* const menu = al->get_menu();
      const int index = menu->get_item_index("close other tabs");
      if (index >= 0)
        menu->set_item_enabled(index, size > 1);
    }
  }
}

//------------------------------------------------------------------------------
void QueryView::start_busy()
{
  if (_label)
    _label->start_busy();
}

//------------------------------------------------------------------------------
void QueryView::stop_busy()
{
  if (_label)
    _label->stop_busy();
}

//------------------------------------------------------------------------------
void QueryView::focus()
{
  _editor->get_editor_control()->focus();
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
DbSqlEditorView *DbSqlEditorView::create(SqlEditorForm::Ref editor_be)
{
  return Gtk::manage(new DbSqlEditorView(editor_be));
}

//------------------------------------------------------------------------------
DbSqlEditorView::DbSqlEditorView(SqlEditorForm::Ref editor_be)
                : _be(editor_be)
                , _output(_be, this)
                , _side_palette(mforms::gtk::ViewImpl::get_widget_for_view(_be->get_side_palette()))
                , _grtm(editor_be->grt_manager())
                , _right_aligned(editor_be->wbsql()->get_wbui()->get_wb()->get_wb_options().get_int("Sidebar:RightAligned", 0))
{
  _top_pane.set_name("sqled.top_pane" + std::string(_right_aligned ? ".right_aligned" : ".left_aligned"));
  _main_pane.set_name("sqled.main_pane" + std::string(_right_aligned ? ".right_aligned" : ".left_aligned"));
  _top_right_pane.set_name("sqled.top_right_pane" + std::string(_right_aligned ? ".right_aligned" : ".left_aligned"));

  Gtk::Widget *v = this;
  _be = editor_be;
  _be->set_frontend_data(dynamic_cast<FormViewBase*>(this));

  mforms::View *sbview  = _be->get_sidebar();
  Gtk::Widget  *sidebar = mforms::gtk::ViewImpl::get_widget_for_view(sbview);

  _editor_note = Gtk::manage(new Gtk::Notebook()); // init FormViewBase::_editor_note
  _editor_note->show();
  _editor_note->set_scrollable(true);
  _editor_note->set_show_border(false);

  _main_pane.pack1(_top_right_pane, true, false);
  _main_pane.pack2(_output.get_outer(), false, true);

  sidebar->set_no_show_all(true);
  if (_right_aligned)
  {
    _top_pane.add1(_main_pane);
    _top_pane.add2(*sidebar);

    _top_right_pane.pack2(*_editor_note, true, false);
    if (_side_palette)
      _top_right_pane.pack1(*_side_palette, false, true);
  }
  else
  {
    _top_pane.add1(*sidebar);
    _top_pane.add2(_main_pane);

    _top_right_pane.pack1(*_editor_note, true, false);
    if (_side_palette)
      _top_right_pane.pack2(*_side_palette, false, true);
  }

  _top_pane.show_all();
  _top_right_pane.show_all();
  _main_pane.show_all();

  // Add main menu
  Gtk::Widget *w = mforms::gtk::widget_for_menubar(_be->get_menubar());
  if (w)
    pack_start(*w, false, true);

  // Add main toolbar
  w = mforms::gtk::widget_for_toolbar(_be->get_toolbar());
  if (w)
  {
    w->show();
    pack_start(*w, false, true);
  }

  pack_start(_top_pane, true, true);
  show_all();

  // Connect signals
  _be->sql_editor_new_ui.connect(sigc::mem_fun(this, &DbSqlEditorView::add_editor_tab));
  _be->set_partial_refresh_ui_slot(sigc::mem_fun(this, &DbSqlEditorView::partial_refresh_ui));

  _be->exec_sql_task->progress_cb(sigc::mem_fun(this, &DbSqlEditorView::on_exec_sql_progress));
  _be->exec_sql_task->finish_cb(sigc::mem_fun(this, &DbSqlEditorView::on_exec_sql_done));
  _be->recordset_list_changed.connect(sigc::mem_fun(this, &DbSqlEditorView::recordset_list_changed));
  _be->output_text_slot= sigc::mem_fun(this, &DbSqlEditorView::output_text);

  _update_resultset_slots_lock = g_mutex_new();

  _dispatch_rset_update_conn = _dispatch_rset_update.connect(sigc::mem_fun(this, &DbSqlEditorView::update_resultsets_from_main));
  _editor_note->signal_switch_page().connect(sigc::mem_fun(this, &DbSqlEditorView::editor_page_switched));
  _editor_note->signal_page_reordered().connect(sigc::mem_fun(this, &DbSqlEditorView::editor_page_reordered));

  _polish_conn = signal_map().connect(sigc::mem_fun(this, &DbSqlEditorView::polish));

  // restore state of toolbar
  {
    mforms::ToolBar *toolbar = _be->get_toolbar();
    bool flag;

    toolbar->set_item_checked("wb.toggleSchemataBar", flag = !_grtm->get_app_option_int("DbSqlEditor:SchemaSidebarHidden", 0));
    if (flag) _top_pane.get_child1()->show(); else _top_pane.get_child1()->hide();

    toolbar->set_item_checked("wb.toggleOutputArea", flag = !_grtm->get_app_option_int("DbSqlEditor:OutputAreaHidden", 0));
    if (flag) _main_pane.get_child2()->show(); else _main_pane.get_child2()->hide();

    toolbar->set_item_checked("wb.toggleSideBar", flag = !_grtm->get_app_option_int("DbSqlEditor:SidebarHidden", 0));
    if (flag) _top_right_pane.get_child2()->show(); else _top_right_pane.get_child2()->hide();
  }

//  utils::gtk::load_toolbar_state(_grtm, _be->get_toolbar());

  utils::gtk::load_settings(_grtm, &_top_pane, sigc::bind(sigc::ptr_fun(gtk_paned_set_pos_ratio), &_top_pane, _right_aligned ? 0.8 : 0.2), false);
  utils::gtk::load_settings(_grtm, &_main_pane, sigc::bind(sigc::ptr_fun(gtk_paned_set_pos_ratio), &_main_pane, _right_aligned ? 0.2 : 0.8), false);
  utils::gtk::load_settings(_grtm, &_top_right_pane, sigc::bind(sigc::ptr_fun(gtk_paned_set_pos_ratio), &_top_right_pane, _right_aligned ? 0.1 : 0.9), true);

  _top_pane.property_position().signal_changed().connect(sigc::bind(sigc::ptr_fun(utils::gtk::save_settings), _grtm, &_top_pane, false));
  _main_pane.property_position().signal_changed().connect(sigc::bind(sigc::ptr_fun(utils::gtk::save_settings), _grtm, &_main_pane, false));
  _top_right_pane.property_position().signal_changed().connect(sigc::bind(sigc::ptr_fun(utils::gtk::save_settings), _grtm, &_top_right_pane, true));
}

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

bool DbSqlEditorView::perform_command(const std::string &cmd)
{
  if (cmd == "wb.toggleSchemataBar")
  {
    Gtk::Widget* w = _top_pane.get_child1();
    bool hidden = !be()->get_toolbar()->get_item_checked("wb.toggleSchemataBar");
    if (!hidden)
      w->show();
    else
      w->hide();
    _grtm->set_app_option("DbSqlEditor:SchemaSidebarHidden", grt::IntegerRef(hidden));
  }
  else if (cmd == "wb.toggleOutputArea")
  {
    Gtk::Widget* w = _main_pane.get_child2();
    bool hidden = !be()->get_toolbar()->get_item_checked("wb.toggleOutputArea");
    if (!hidden)
      w->show();
    else
      w->hide();
    _grtm->set_app_option("DbSqlEditor:OutputAreaHidden", grt::IntegerRef(hidden));
  } 
  else if (cmd == "wb.toggleSideBar")
  {
    Gtk::Widget* w = _top_right_pane.get_child2();
    bool hidden = !be()->get_toolbar()->get_item_checked("wb.toggleSideBar");
    if (!hidden)
      w->show();
    else
      w->hide();
    _grtm->set_app_option("DbSqlEditor:SidebarHidden", grt::IntegerRef(hidden));
  }
  else 
    return false;
  return true;
}

//------------------------------------------------------------------------------
void DbSqlEditorView::polish()
{
  gtk_paned_set_pos_ratio(&_main_pane, 0.7);
  _polish_conn.disconnect();
}

//------------------------------------------------------------------------------
DbSqlEditorView::~DbSqlEditorView()
{
// state is saved when it changes. saving again on quit, will give unexpected results
//  utils::gtk::save_settings(_grtm, &_top_pane, false);
//  utils::gtk::save_settings(_grtm, &_main_pane, false);
//  utils::gtk::save_settings(_grtm, &_top_right_pane, true);

  const std::vector<QueryView*> views = DbSqlEditorView::query_views();
  for (size_t i = 0; i < views.size(); ++i)
  {
    _editor_note->remove_page(*views[i]->get_outer());
    delete views[i];
  }

  _dispatch_rset_update_conn.disconnect();

  if (_side_palette)
    _side_palette->hide();

  dispose();

  while (!g_mutex_trylock(_update_resultset_slots_lock)); // maybe add sleep here

  g_mutex_unlock(_update_resultset_slots_lock);
  g_mutex_free(_update_resultset_slots_lock);
  _update_resultset_slots_lock = 0;
}

//------------------------------------------------------------------------------
void DbSqlEditorView::dispose()
{
  if (_be)
  {
  //  utils::gtk::save_toolbar_state(_grtm, _be->get_toolbar());
    _be->close();
    _be.reset();
  }
}

//------------------------------------------------------------------------------
std::vector<QueryView*> DbSqlEditorView::query_views()
{
  std::vector<QueryView*> list;

  const int size = _editor_note->get_n_pages();
  list.reserve(size);

  for (int i = 0; i < size; ++i)
  {
    Gtk::Widget* const child = _editor_note->get_nth_page(i);
    if (child)
    {
      QueryView* qv = reinterpret_cast<QueryView*>(child->get_data("query_view"));
      if (qv)
        list.push_back(qv);
    }
  }

  return list;
}

//------------------------------------------------------------------------------
void DbSqlEditorView::init()
{
}

//------------------------------------------------------------------------------
bool DbSqlEditorView::validate_explain_sql()
{
  return true;
}

//------------------------------------------------------------------------------
void DbSqlEditorView::explain_sql()
{
  if (_be)
    _be->explain_sql();
}

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

QueryView* DbSqlEditorView::active_view()
{
  QueryView* ret = 0;

  const int     cur_page_id = _editor_note->get_current_page();
  Gtk::Widget*  content     = _editor_note->get_nth_page(cur_page_id);
  if (content)
    ret = (QueryView*)content->get_data("query_view");

  return ret;
}

//------------------------------------------------------------------------------
QueryView* DbSqlEditorView::find_view_by_index(const int index)
{
  QueryView* view = 0;

  const int size = _editor_note->get_n_pages();
  for (int i = 0; i < size; ++i)
  {
    Gtk::Widget* const child = _editor_note->get_nth_page(i);
    if (child)
    {
      QueryView* qv = reinterpret_cast<QueryView*>(child->get_data("query_view"));
      if (qv && qv->index() == index)
      {
        view = qv;
        break;
      }
    }
  }

  return view;
}


//------------------------------------------------------------------------------
void DbSqlEditorView::recalc_tab_indices()
{
  if (_be)
  {
    int query_views_cnt = 0;

    const int size = _editor_note->get_n_pages();
    for (int i = 0; i < size; ++i)
    {
      Gtk::Widget* const child = _editor_note->get_nth_page(i);
      if (child)
      {
        QueryView* qv = reinterpret_cast<QueryView*>(child->get_data("query_view"));
        if (qv) // skip non query view tabs, query view tabs have "query_view" set
        {
          if (qv->be())
            _be->sql_editor_reorder(qv->be(), query_views_cnt++);
          else
            log_error("QueryView has no backend\n");
        }
      }
    }
  }
}


//------------------------------------------------------------------------------
void DbSqlEditorView::init_tab_menu(mforms::Menu* menu)
{
  menu->add_item("New Tab", "new tab");
  menu->add_item("Save Tab", "save tab");
  menu->add_separator();
  menu->add_item("Close Tab", "close tab");
  menu->add_item("Close Other Tabs", "close other tabs");
  menu->add_separator();
  menu->add_item("Copy Path to Clipboard", "copy path");
  menu->set_item_enabled(4, false);
}

//------------------------------------------------------------------------------
void DbSqlEditorView::tab_menu_handler(const std::string& action, ActiveLabel* sender, QueryView* qv)
{
  if (qv && _be)
  {
    if (action == "new tab")
      _be->new_sql_script_file();
    else if (action == "save tab")
      qv->save();
    else if (action == "copy path")
    {
      const std::string path = qv->editor_path();
      Glib::RefPtr<Gtk::Clipboard> clip = Gtk::Clipboard::get();
      if (clip && !path.empty())
        clip->set_text(path);
    }
    else if (action == "close tab")
    {
      Glib::signal_idle().connect(sigc::bind_return(sigc::bind(sigc::mem_fun(this, &DbSqlEditorView::close_editor_tab), qv), false));
    }
    else if (action == "close other tabs")
    {
      int size = _editor_note->get_n_pages();
      std::vector<QueryView*> to_close_list;
      to_close_list.reserve(size);

      for (int i = 0; i < size; ++i)
      {
        QueryView *cqv = find_view_by_index(i);
        if (cqv != qv)
          to_close_list.push_back(cqv);
      }

      size = to_close_list.size();
      for (int i = 0; i < size; ++i)
        close_editor_tab(to_close_list[i]);
    }
  }
}

//------------------------------------------------------------------------------
void DbSqlEditorView::reenable_items_in_tab_menus()
{
  const Gtk::Notebook::PageList pages = _editor_note->pages();
  const int size = pages.size();

  for (int i = 0; i < size; ++i)
  {
    Gtk::Notebook_Helpers::Page page = pages[i];
    ActiveLabel* const al = dynamic_cast<ActiveLabel*>(page.get_tab_label());
    if (al)
    {
      mforms::Menu* const menu = al->get_menu();
      const int index = menu->get_item_index("close other tabs");
      if (index >= 0)
        menu->set_item_enabled(index, size > 1);
    }

    QueryView* qv = reinterpret_cast<QueryView*>(page.get_child()->get_data("query_view"));
    if (qv)
      qv->reenable_items_in_tab_menus();
  }

}

//------------------------------------------------------------------------------
void DbSqlEditorView::partial_refresh_ui(const int what)
{
  if(!_be)
    return;

  switch (what)
  {
    case SqlEditorForm::RefreshEditorTitle:
    {
      QueryView* qv = active_view();
      if (qv)
        qv->update_label(_be->sql_editor_caption());
      break;
    }
    case SqlEditorForm::RefreshRecordsetTitle:
    {
      QueryView* qv = active_view();
      if (qv)
        qv->update_recordset_caption();
      break;
    }
    case SqlEditorForm::QueryExecutionStarted:
    {
      QueryView* qv = active_view();
      if (qv)
        qv->start_busy();
      break;
    }
  }
}

//------------------------------------------------------------------------------
void DbSqlEditorView::plugin_tab_added(PluginEditorBase *plugin)
{
  const int page_num = _editor_note->page_num(*(static_cast<Gtk::Widget*>(plugin)));
  if (page_num >= 0)
    _editor_note->set_current_page(page_num);
}

//------------------------------------------------------------------------------
int DbSqlEditorView::add_editor_tab(int active_sql_editor_index)
{
  QueryView    *qv    = new QueryView(active_sql_editor_index, this);
  ActiveLabel  *label = Gtk::manage(new ActiveLabel(_be->sql_editor_caption(active_sql_editor_index)
                                       ,sigc::mem_fun(qv, &QueryView::close)
                                       ));

  qv->set_linked_label(label);
  mforms::Menu* menu = label->get_menu();
  init_tab_menu(menu);
  menu->set_handler(sigc::bind(sigc::mem_fun(this, &DbSqlEditorView::tab_menu_handler), label, qv));

  qv->get_outer()->set_data("query_view", (void*)qv); // Adding backlink

  const int page_index =_editor_note->append_page(*qv->get_outer(), *label);
  _editor_note->set_tab_reorderable(*qv->get_outer(), true);
  qv->get_outer()->show();
  label->show();

  _editor_note->set_current_page(page_index);
  reenable_items_in_tab_menus();

  qv->focus();
//  utils::gtk::load_toolbar_state(_grtm, _be->get_toolbar());

  return 0;
}

//------------------------------------------------------------------------------
void DbSqlEditorView::close_editor_tab(QueryView* qv)
{
  const int idx = qv->index();

  if (!_be || !_be->sql_editor_will_close(idx))
    return;

  _be->remove_sql_editor(idx);
  _editor_note->remove_page(*qv->get_outer());

  delete qv;

  recalc_tab_indices();

  int size = 0;
  for (int i = _editor_note->get_n_pages() - 1; i >= 0; --i)
  {
    Gtk::Widget* const child = _editor_note->get_nth_page(i);
    if (child && child->get_data("query_view"))
        ++size;
  }

  if (size == 0)
  {
    _be->new_sql_script_file();
    //const int new_editor_index = _be->add_sql_editor();
    //add_editor_tab(new_editor_index);
  }

  reenable_items_in_tab_menus();
}

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

bool DbSqlEditorView::close_focused_tab()
{
  QueryView *qv = active_view();
  if (qv)
  {
    close_editor_tab(qv);
    return true;
  }
  else
  {
    Gtk::Widget* content = _editor_note->get_nth_page(_editor_note->get_current_page());
    Gtk::Widget* label   = _editor_note->get_tab_label(*content);

    ActiveLabel* const al = dynamic_cast<ActiveLabel*>(label);
    if (al)
    {
      al->call_close();
      _editor_note->remove_page(*content);

      recalc_tab_indices();
      reenable_items_in_tab_menus();
    }
  }

  return false;
}

//------------------------------------------------------------------------------
void DbSqlEditorView::editor_page_switched(GtkNotebookPage *page, guint index)
{
  Gtk::Widget*  content = _editor_note->get_nth_page(index);
  if (_be && content)
  {
    QueryView* qv = (QueryView*)content->get_data("query_view");
    if (qv)
      _be->active_sql_editor_index(qv->index());
    else
      _be->active_sql_editor_index(-1);
  }
}

//------------------------------------------------------------------------------
bool DbSqlEditorView::on_close()
{
  for (int i= _editor_note->get_n_pages()-1; i >= 0; i--)
  {
    PluginEditorBase *editor = dynamic_cast<PluginEditorBase*>(_editor_note->get_nth_page(i));
    if (editor && !dynamic_cast<QueryView*>(editor) && !close_plugin_tab(editor)) 
      return false;
  }

  if (_be && _be->can_close())
  {
    //_closing = true;
    return true;
  }
  return false;
}

//------------------------------------------------------------------------------
int DbSqlEditorView::on_exec_sql_progress(float progress, const std::string &message)
{
  _output.refresh();

  QueryView* qv = active_view();
  if (qv)
    qv->update_exec_sql_progress(progress, message);
  return 0;
}

//------------------------------------------------------------------------------
int DbSqlEditorView::on_exec_sql_done()
{
  QueryView* qv = active_view();
  if (qv)
  {
    qv->stop_busy();

    qv->update_resultsets();
  }
  return 0;
}

//------------------------------------------------------------------------------
void DbSqlEditorView::update_resultsets_from_main()
{
  std::deque<sigc::slot<void> >  slots;

  {
    base::GMutexLock lock(_update_resultset_slots_lock);
    slots = _update_resultset_slots;
    _update_resultset_slots.clear();
  }

  const size_t size = slots.size();
  for (size_t i = 0; i < size; ++i)
    slots[i]();
}

//------------------------------------------------------------------------------
void DbSqlEditorView::update_resultsets(int editor_index, Recordset::Ref rset, bool added)
{
  QueryView* qv = find_view_by_index(editor_index);
  if (qv)
    qv->update_resultsets();
  else
    log_error("editor index %i is unknown\n", editor_index);
}

//------------------------------------------------------------------------------
void DbSqlEditorView::recordset_list_changed(int editor_index, Recordset::Ref rset, bool added)
{
  log_debug2("recordset_list_changed: editor_index = %i, added = %i\n", editor_index, added);
  sigc::slot<void> update_resultset_slot = sigc::bind(sigc::mem_fun(this, &DbSqlEditorView::update_resultsets), editor_index, rset, added);

  {
    base::GMutexLock lock(_update_resultset_slots_lock);
    _update_resultset_slots.push_back(update_resultset_slot);
  }

  _dispatch_rset_update.emit();
}

//------------------------------------------------------------------------------
void DbSqlEditorView::output_text(const std::string &text, bool bring_to_front)
{
  _output.output_text(text, bring_to_front);
}

