#include "gtk/gtk.h"
#include "mysql_table_editor_fe.h"
#include "linux_utilities/MGGladeXML.h"
#include "grtdb/db_object_helpers.h"
#include "treemodel_wrapper.h"
#include "gtk_helpers.h"
#include "auto_completable.h"

#include "mysql_table_editor_column_page.h"

#include "menu_manager.h"

#include <gtkmm/comboboxtext.h>
#include <gtkmm/textview.h>
#include <gtkmm/scrolledwindow.h>
#include <set>
#include <memory>

AutoCompletable DbMySQLTableEditorColumnPage::_types_completion;
AutoCompletable DbMySQLTableEditorColumnPage::_names_completion;

//------------------------------------------------------------------------------
DbMySQLTableEditorColumnPage::DbMySQLTableEditorColumnPage(DbMySQLTableEditor *owner
                                                          ,MySQLTableEditorBE *be
                                                          ,MGGladeXML         *xml)
                             : _owner(owner)
                             , _be(be)
                             , _xml(xml)
                             , _tv(0)
                             , _tv_holder(0)
                             , _edit_conn(0)
                             , _ce(0)
                             , _editing(false)
{
  _xml->get("table_columns_holder", &_tv_holder);

  refill_columns_tv();
  refill_completions();

  Gtk::TextView *column_comment;
  _xml->get("column_comment", &column_comment);
  _owner->add_text_change_timer(column_comment, sigc::mem_fun(this, &DbMySQLTableEditorColumnPage::set_comment));

  _xml->get("column_collation_combo", &_collation_combo);
  _collation_combo->clear_items();
  _collation_combo->set_size_request(80, -1);
  fill_combo_from_string_list(_collation_combo, _be->get_charset_collation_list());
  _collation_combo->signal_changed().connect(sigc::mem_fun(this, &DbMySQLTableEditorColumnPage::set_collation));
}

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

//------------------------------------------------------------------------------
void DbMySQLTableEditorColumnPage::switch_be(MySQLTableEditorBE* be)
{
  _be = be;

  refill_columns_tv();
  refill_completions();

  // refresh is done from TableEditor  
}

//--------------------------------------------------------------------------------
void DbMySQLTableEditorColumnPage::refill_completions()
{
  _types_completion.clear();
  std::vector<std::string> types(_be->get_columns()->get_datatype_names());
  
  for (std::vector<std::string>::const_iterator iter= types.begin(); iter != types.end(); ++iter)
  {
    if (*iter != "-")
      _types_completion.add_completion_text(*iter);
  }

  _names_completion.clear();
  
  // Add to completion list all column names in a schema
  bec::ColumnNamesSet column_names = _be->get_columns()->get_column_names_completion_list();
  
  bec::ColumnNamesSet::const_iterator it   = column_names.begin();
  bec::ColumnNamesSet::const_iterator last = column_names.end();
  for (; last != it; ++it)
    _names_completion.add_completion_text(*it);
}


//--------------------------------------------------------------------------------
void DbMySQLTableEditorColumnPage::refill_columns_tv()
{
  std::auto_ptr<Gtk::TreeView> new_tv(new Gtk::TreeView());

  // Replace old treeview with  newly created treeview
  _tv_holder->remove();
  
  if (_tv)
    _tv->remove_all_columns();
  delete _tv;
  
  _tv = new_tv.get();
  _tv->set_enable_tree_lines(true);
  _tv->get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);

  Glib::RefPtr<ListModelWrapper> model = ListModelWrapper::create(_be->get_columns(), _tv, "DbMySQLTableEditorColumnPage");

  model->model().append_string_column(MySQLTableColumnsListBE::Name, "Column Name", EDITABLE, WITH_ICON);
  model->model().append_combo_column(MySQLTableColumnsListBE::Type, "Datatype", model->model().create_model(get_types_for_table(_be->table())), EDITABLE);
  model->model().append_check_column(MySQLTableColumnsListBE::IsPK, "PK", EDITABLE);
  model->model().append_check_column(MySQLTableColumnsListBE::IsNotNull, "NN", EDITABLE);
  model->model().append_check_column(MySQLTableColumnsListBE::IsBinary, "BIN", EDITABLE);
  model->model().append_check_column(MySQLTableColumnsListBE::IsUnsigned, "UN", EDITABLE);
  model->model().append_check_column(MySQLTableColumnsListBE::IsZerofill, "ZF", EDITABLE);
  model->model().append_check_column(MySQLTableColumnsListBE::IsAutoIncrement, "AI", EDITABLE);
  model->model().append_string_column(MySQLTableColumnsListBE::Default, "Default", EDITABLE);

  _model = model;
  new_tv.release();

  _tv_holder->add(*Gtk::manage(_tv));
  _tv->show();

  _tv->set_model(_model);

  std::vector<Gtk::TreeViewColumn*> cols = _tv->get_columns();
  for (int j = cols.size() - 1; j >= 0; --j)
  {
    std::vector<Gtk::CellRenderer*> rends= cols[j]->get_cell_renderers();
    for (int i = rends.size() - 1; i >= 0; --i)
    {
      GtkCellRenderer* rend = rends[i]->gobj();
      rends[i]->set_data("idx", (gpointer)j);
      g_signal_connect(rend, "editing-started", GCallback(&DbMySQLTableEditorColumnPage::type_cell_editing_started), this);
    }
  }

  _tv->signal_event().connect(sigc::mem_fun(*this, &DbMySQLTableEditorColumnPage::process_event));
  _tv->signal_cursor_changed().connect(sigc::mem_fun(*this, &DbMySQLTableEditorColumnPage::cursor_changed));
  _tv->signal_size_allocate().connect(sigc::mem_fun(this, &DbMySQLTableEditorColumnPage::check_resize));
  _tv->signal_visibility_notify_event().connect(sigc::mem_fun(this, &DbMySQLTableEditorColumnPage::do_on_visible));

  _tv->set_reorderable(true);
}

//--------------------------------------------------------------------------------
bec::NodeId DbMySQLTableEditorColumnPage::get_selected()
{
  Gtk::TreeModel::Path   path;
  Gtk::TreeView::Column *column(0);
  _tv->get_cursor(path, column);
  
  return _model->get_node_for_path(path);
}

//--------------------------------------------------------------------------------
void DbMySQLTableEditorColumnPage::refresh()
{ 
  if (!_editing)
  {
    // get currently selected item
    Gtk::TreeModel::Path   path;
    Gtk::TreeView::Column *column(0);
    _tv->get_cursor(path, column);


    bec::ListModel* m = _model->get_be_model();
    _model->set_be_model(0);
    _tv->unset_model();

    _tv->set_model(_model);

    _model->set_be_model(m);
    _tv->unset_model();
    _model->refresh();
    _tv->set_model(_model);
  }
}

//--------------------------------------------------------------------------------
void DbMySQLTableEditorColumnPage::partial_refresh(const int what)
{
  switch (what)
  {
    case ::bec::TableEditorBE::RefreshColumnCollation:
    {
      update_collation();
      break;
    }
    default:;
  }
}

//------------------------------------------------------------------------------
void DbMySQLTableEditorColumnPage::type_cell_editing_started(GtkCellRenderer* cr, GtkCellEditable* ce, gchar* path, gpointer udata)
{   
  //Connect edit_done, so we can trigger refresh
  DbMySQLTableEditorColumnPage* columns_page = reinterpret_cast<DbMySQLTableEditorColumnPage*>(udata);
  columns_page->_editing = true;

  const int idx = (int)((long long)gtk_object_get_data(GTK_OBJECT(cr), "idx"));
  
  bec::NodeId node(path);

  if ( GTK_IS_COMBO_BOX_ENTRY(ce) && idx == 1) // Attach types auto completion to the cell
  {
    GtkBin     *combo = GTK_BIN(ce);
    Gtk::Entry *entry = Glib::wrap((GtkEntry*)gtk_bin_get_child(combo));
    
    if ( entry )
      _types_completion.add_to_entry(entry);
  }
  else if ( GTK_IS_ENTRY(ce) && idx == 0) // Fill in name of the column
  {
    Gtk::Entry* entry = Glib::wrap(GTK_ENTRY(ce));

    std::string name;
    if (node.back() == columns_page->_be->get_columns()->count()-1)
      columns_page->_be->get_columns()->set_field(node, MySQLTableColumnsListBE::Name, 1);

    columns_page->_be->get_columns()->get_field(node, MySQLTableColumnsListBE::Name, name);
    entry->set_text(name);

    _names_completion.add_to_entry(entry);
  }


  // clean up edit_done signal/slot
  if ( columns_page->_ce && columns_page->_edit_conn )
  {
    g_signal_handler_disconnect (columns_page->_ce, columns_page->_edit_conn);
    columns_page->_ce = 0;
    columns_page->_edit_conn = 0;
  }

  if (GTK_IS_CELL_EDITABLE(ce))
  {
    columns_page->_ce = ce;
    columns_page->_edit_conn = g_signal_connect(ce, "editing-done", GCallback(&DbMySQLTableEditorColumnPage::cell_editing_done), udata);
  }
}

//--------------------------------------------------------------------------------
void DbMySQLTableEditorColumnPage::cell_editing_done(GtkCellEditable* ce, gpointer udata)
{
  DbMySQLTableEditorColumnPage* columns_page = reinterpret_cast<DbMySQLTableEditorColumnPage*>(udata);
  columns_page->_editing = false;

  if ( columns_page->_ce && columns_page->_edit_conn )
  {
    g_signal_handler_disconnect(columns_page->_ce, columns_page->_edit_conn);
    columns_page->_ce = 0;
    columns_page->_edit_conn = 0;
  }

  Gtk::TreeModel::Path   path;
  Gtk::TreeView::Column *column(0);
  columns_page->_tv->get_cursor(path, column);

  columns_page->refresh();

  columns_page->_tv->set_cursor(path);
}

//------------------------------------------------------------------------------
grt::StringListRef DbMySQLTableEditorColumnPage::get_types_for_table(const db_TableRef table)
{
  grt::StringListRef list(table->get_grt());
  std::vector<std::string> types(_be->get_columns()->get_datatype_names());
  
  for (std::vector<std::string>::const_iterator iter= types.begin(); iter != types.end(); ++iter)
  {
    if (*iter == "-")
      list.insert("----------");
    else
      list.insert(*iter);
  }

  return list;
}

//------------------------------------------------------------------------------
void DbMySQLTableEditorColumnPage::check_resize(Gtk::Allocation& r)
{
  //   0       1        2  3    4   5   6   7       8
  // name    type      PK  NN  BIN  UN  ZF  AI      Default
  const int step = r.get_width() / 10;
  _tv->get_column(0)->set_min_width(4 * step);
  _tv->get_column(1)->set_min_width(2 * step);
  _tv->get_column(8)->set_min_width(2 * step);
}

//------------------------------------------------------------------------------
bool DbMySQLTableEditorColumnPage::process_event(GdkEvent* event)
{
  if ( event->type == GDK_2BUTTON_PRESS && event->button.button == 1 )
  {
    Gtk::TreeModel::Path   path;
    Gtk::TreeView::Column *column(0);
    int                    cell_x(-1);
    int                    cell_y(-1);
    
    if ( _tv->get_path_at_pos((int)event->button.x, (int)event->button.y, path, column, cell_x, cell_y) )
    {
      bec::NodeId node = _model->get_node_for_path(path);
      if ( node.is_valid() )
      {
        bool primary_key = _be->get_columns()->get_column_flag(node, "PRIMARY KEY");
        _be->get_columns()->set_column_flag(node, "PRIMARY KEY", !primary_key);
      }
    }
  }
  else if ( event->type == GDK_KEY_RELEASE )
  {
    type_column_event(event);
  }
  return false;
}

//------------------------------------------------------------------------------
void DbMySQLTableEditorColumnPage::type_column_event(GdkEvent* event)
{
  if ( event->type == GDK_KEY_RELEASE )
  {
    const int key = event->key.keyval;
    if ( key == GDK_Tab/* || key == GDK_Return */)
    {
      // Advance to the next column
      Gtk::TreeModel::Path    path;
      Gtk::TreeView::Column  *column(0);
      _tv->get_cursor(path, column);
      
      if ( column )
      {
        //   0       1        2  3         4
        // name    type      NN AI      Default
        Glib::ListHandle<Gtk::TreeView::Column*> columns = _tv->get_columns();
  
        Glib::ListHandle<Gtk::TreeView::Column*>::const_iterator it   = columns.begin();
        Glib::ListHandle<Gtk::TreeView::Column*>::const_iterator last = columns.end();
        
        int i = 0;
        for ( ; last != it; ++it )
        {
          if ( (*it)->get_title() == column->get_title() )
            break;
          ++i;
        }

        refresh();
        
        ++it; ++i;
        if ( it != last && i <= 1 )
          _tv->set_cursor(path, **it, true);
        else // next row
        {
          path.next();
          _tv->set_cursor(path, **columns.begin(), true);
        }
      }
    }
  }
}

//------------------------------------------------------------------------------
void DbMySQLTableEditorColumnPage::cursor_changed()
{   
  if (!_editing)
  {
    Gtk::TreeModel::Path   path;
    Gtk::TreeView::Column *column(0);
    _tv->get_cursor(path, column);
  
    bec::NodeId node = _model->get_node_for_path(path);
  
    if ( node.is_valid() )
      update_column_details(node);
  }
}

//------------------------------------------------------------------------------
void DbMySQLTableEditorColumnPage::update_column_details(const ::bec::NodeId& node)
{
  Gtk::TextView *column_comment;
  _xml->get("column_comment", &column_comment);
  
  std::string comment;
  _be->get_columns()->get_field(node, MySQLTableColumnsListBE::Comment, comment);
  
  column_comment->get_buffer()->set_text(comment);
  
  update_collation();
}

//------------------------------------------------------------------------------
void DbMySQLTableEditorColumnPage::set_comment(const std::string& comment)
{
  const bec::NodeId node = get_selected();
  if (node.is_valid())
  {
    ::bec::TableColumnsListBE* cols = _be->get_columns();
    cols->set_field(node, (int)MySQLTableColumnsListBE::Comment, comment);
  }
}

//------------------------------------------------------------------------------
void DbMySQLTableEditorColumnPage::set_collation()
{
  const bec::NodeId node = get_selected();
  if (node.is_valid())
  {
    bec::TableColumnsListBE *columns = _be->get_columns();
    columns->set_field(node, MySQLTableColumnsListBE::CharsetCollation, _collation_combo->get_active_text());
  }
}

//------------------------------------------------------------------------------
void DbMySQLTableEditorColumnPage::update_collation()
{
  const bec::NodeId node = get_selected();
  if (node.is_valid())
  {
    std::string has_charset;
    MySQLTableColumnsListBE* columns = _be->get_columns();
  
    Gtk::ComboBoxText *collation_combo;
    _xml->get("column_collation_combo", &collation_combo);
    
    columns->get_field(node, MySQLTableColumnsListBE::HasCharset, has_charset);
    if ( "1" == has_charset )
    {
      std::string column_cscoll;
      columns->get_field(node, MySQLTableColumnsListBE::CharsetCollation, column_cscoll);
      
      collation_combo->set_sensitive(true);
      collation_combo->set_active_text(column_cscoll);
    }
    else
    {
      collation_combo->set_sensitive(false);
    }
  }
}

//--------------------------------------------------------------------------------
bool DbMySQLTableEditorColumnPage::do_on_visible(GdkEventVisibility*)
{
  MySQLTableColumnsListBE *columns = _be->get_columns();
  if ( columns->count() == 1 )
  {
    ::bec::NodeId node(columns->get_node(0));
    _tv->set_cursor(node2path(node), *(_tv->get_column(0)), true);
  }
  return false;
}
