/*
 * Copyright (c) 2007, 2013, 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 "sqlide/recordset_view.h"
#include "sqlide/utils_fe.h"
#include "linux_utilities/gtk_helpers.h"
#include "base/string_utilities.h"
#include "linux_utilities/toolbar_manager.h"
#include "mforms/utilities.h"
#include "mforms/menubar.h"
#include "base/log.h"

DEFAULT_LOG_DOMAIN("RecordsetView");

using base::strfmt;


RecordsetView * RecordsetView::create(Recordset::Ref model, Gtk::Container *parent)
{
  RecordsetView *view= Gtk::manage(new RecordsetView(model));
  view->init();
  if (parent)
    parent->add(*view);
  return view;
}

RecordsetView::RecordsetView(Recordset::Ref model)
:
_grid(NULL), _single_row_height(-1)
{
  _filter_entry= 0;
  this->model(model);
}

RecordsetView::~RecordsetView()
{
}

void RecordsetView::init()
{
  bec::GRTManager *grtm= _model->grtm();
  Glib::RefPtr<Gtk::Builder> xml(Gtk::Builder::create_from_file(grtm->get_data_file_path("recordset_view.glade")));

  Gtk::Container *recordset_view = 0;
  xml->get_widget("recordset_view", recordset_view);
  Gtk::ScrolledWindow *recordset_placeholder = 0;
  xml->get_widget("recordset_placeholder", recordset_placeholder);

  _grid= GridView::create(_model, false); // XXX put back to true when column-autosizing is fixed
  _grid->get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);

  _grid->_copy_func_ptr = sigc::mem_fun(this,&RecordsetView::copy);

  recordset_placeholder->add(*_grid);

  recordset_view->reparent(*this);
  set_shadow_type(Gtk::SHADOW_NONE);

  _toolbar_box = 0;
  xml->get_widget("recordset_toolbar", _toolbar_box);
  
  ActionList &action_list= _model->action_list();
  action_list.register_action("record_first", sigc::mem_fun(this, &RecordsetView::on_goto_first_row_btn_clicked));
  action_list.register_action("record_back", sigc::mem_fun(this, &RecordsetView::on_record_prev));
  action_list.register_action("record_next", sigc::mem_fun(this, &RecordsetView::on_record_next));
  action_list.register_action("record_last", sigc::mem_fun(this, &RecordsetView::on_goto_last_row_btn_clicked));
  //action_list.register_action("record_fetch_all", sigc::mem_fun(this, &RecordsetView::));
  action_list.register_action("record_wrap_vertical", sigc::mem_fun(this, &RecordsetView::on_toggle_vertical_sizing));
  action_list.register_action("record_sort_asc", sigc::mem_fun(this, &RecordsetView::on_record_sort_asc));
  action_list.register_action("record_sort_desc", sigc::mem_fun(this, &RecordsetView::on_record_sort_desc));
  action_list.register_action("record_edit", sigc::mem_fun(this, &RecordsetView::on_record_edit));
  action_list.register_action("record_add", sigc::mem_fun(this, &RecordsetView::on_record_add));
  action_list.register_action("record_del", sigc::mem_fun(this, &RecordsetView::on_record_del));
  action_list.register_action("record_save", sigc::mem_fun(this, &RecordsetView::on_commit_btn_clicked));
  action_list.register_action("record_discard", sigc::mem_fun(this, &RecordsetView::on_rollback_btn_clicked));
  //action_list.register_action("record_refresh", sigc::mem_fun(this, &RecordsetView::));

  _grid->signal_event().connect(sigc::mem_fun(this, &RecordsetView::on_event));

  update_toolbar();
  
  show();

}

void RecordsetView::model(Recordset::Ref value)
{
  _model= value;
  _model->refresh_ui_cb= sigc::mem_fun(this, &RecordsetView::refresh);
  _model->refresh_ui_status_bar_signal.connect(sigc::mem_fun(this, &RecordsetView::update_toolbar));
  //_model->task->msg_cb(sigc::mem_fun(this, &RecordsetView::process_task_msg));

  if (_grid)
    _grid->model(_model);
}

Gtk::Widget *RecordsetView::create_toolbar_item(const bec::ToolbarItem &item)
{
  if (item.command == "filter")
  {
    Gtk::Entry *entry= _filter_entry = Gtk::manage(new Gtk::Entry());
    entry->set_size_request(100, -1);
    entry->signal_key_release_event().connect(sigc::bind(sigc::mem_fun(this, &RecordsetView::on_data_search_entry_key_pressed), entry));
    return entry;
  }
  
  return 0;
}


void RecordsetView::update_toolbar()
{
  Glib::ustring text;

  // force a repaint of the records in case the contents have changed too
  _grid->queue_draw();

  if (_filter_entry)
    text = _filter_entry->get_text();
  
  ToolbarManager::rebuild_toolbar(_toolbar_box,
                                  _model->get_toolbar_items(),
                                  sigc::mem_fun(this, &RecordsetView::create_toolbar_item),
                                  sigc::mem_fun(this, &RecordsetView::activate_toolbar_item)
                                 );
  
  if (_filter_entry)
    _filter_entry->set_text(text);
}

void RecordsetView::copy(const std::vector<int> &rows)
{
  if (_model)
    _model->copy_rows_to_clipboard(rows, ", ");
}


bool RecordsetView::activate_toolbar_item(const std::string &action)
{
  try
  {
    bool r = _model->action_list().trigger_action(action);
    if (r)
      update_toolbar();
    return r;
  }
  catch (const std::exception &exc)
  {
    log_error("Unhandled exception in activate_toolbar_item(%s): %s", action.c_str(), exc.what());
    mforms::Utilities::show_error(_("Unhandled Error"), exc.what(), "OK", "", "");
  }
  return false;
}


void RecordsetView::reset()
{
  _model->reset();
}

int RecordsetView::refresh()
{
  _grid->refresh(true);
  update_toolbar();

  // calculate the height of a row with single line of text
  if (_grid->view_model()->row_numbers_visible())
  {
    Gtk::TreeViewColumn *col = _grid->get_column(0);
    Gtk::CellRenderer *rend = col ? col->get_first_cell_renderer() : 0;
    if (rend)
    {
      int x, y, w, h;
      rend->get_size(*_grid, x, y, w, h);
      _single_row_height = h;
    }
  }
 
  if (_grid->get_fixed_height_mode())
    set_fixed_row_height(_single_row_height);
  else
    set_fixed_row_height(-1);
  return 0;
}


bool RecordsetView::on_event(GdkEvent *event)
{
  bool processed= false;

  if (GDK_BUTTON_PRESS == event->type && 3 == event->button.button)
  {
    std::vector<int> rows = _grid->get_selected_rows();
    Gtk::TreePath path;
    Gtk::TreeViewColumn *column;

    _grid->grab_focus();
    int cell_x, cell_y;
    if (_grid->get_path_at_pos(event->button.x, event->button.y, path, column, cell_x, cell_y))
    {
      // if we clicked on an unselected item, then select it otherwise keep original selection state
      if (std::find(rows.begin(), rows.end(), path[0]) == rows.end())
      {
        if (_grid->allow_cell_selection() && column != _grid->get_column(0))
        {
          _grid->select_cell(path[0], *column);
          _grid->get_selection()->unselect_all();
          rows.clear();
          rows.push_back(path[0]);
        }
        else
          _grid->select_cell(path[0], -1);
      }
      else
      {
        // the right-clicked row is selected, so keep the selection as is
      }
    }

    int row, col;
    _grid->current_cell(row, col);

    _model->update_selection_for_menu(rows, col);
    _model->get_context_menu()->popup_at(event->button.x, event->button.y);

    processed= true;
  }

  if (!processed)
    processed= Gtk::Frame::on_event(event);

  return processed;
}



bool RecordsetView::on_data_search_entry_key_pressed(GdkEventKey *event, Gtk::Entry *search_entry)
{
  switch (event->keyval)
  {
  case GDK_Return:
  case GDK_KP_Enter:
  case GDK_ISO_Enter:
  case GDK_3270_Enter:
  std::string value= search_entry->get_text();
  if (value.empty())
    _model->reset_data_search_string();
  else
    _model->set_data_search_string(value);
    return true;
  }
  return false;
}

void RecordsetView::on_commit_btn_clicked()
{
  save_changes();
}

void RecordsetView::on_rollback_btn_clicked()
{
  revert_changes();
}

bool RecordsetView::has_changes()
{
  return _model->has_pending_changes();
}

void RecordsetView::save_changes()
{
  _model->apply_changes();
}

void RecordsetView::revert_changes()
{
  _model->rollback();
}

void RecordsetView::on_goto_first_row_btn_clicked()
{
  if (_model->row_count() == 0)
    return;
    
  Gtk::TreePath tree_path(1);
  tree_path[0]= 0;
  _grid->set_cursor(tree_path);
}

void RecordsetView::on_goto_last_row_btn_clicked()
{
  Gtk::TreePath tree_path(1);
  size_t row_count= _model->row_count();
  if (row_count == 0)
    return;
  tree_path[0] = row_count - 1;
  _grid->set_cursor(tree_path);
}

void RecordsetView::on_record_prev()
{
  Gtk::TreeModel::Path path;
  Gtk::TreeViewColumn *column= NULL;
  _grid->get_cursor(path, column);
  if (!column)
    return;
  path.prev();
  _grid->set_cursor(path, *column);
}

void RecordsetView::on_record_next()
{
  Gtk::TreeModel::Path path;
  Gtk::TreeViewColumn *column= NULL;
  _grid->get_cursor(path, column);
  if (!column)
    return;
  path.next();
  _grid->set_cursor(path, *column);
}

void RecordsetView::on_record_edit()
{
  if (_model->is_readonly())
    return;
  Gtk::TreeModel::Path path;
  Gtk::TreeViewColumn *column= NULL;
  _grid->get_cursor(path, column);
  if (!column)
    return;
  _grid->set_cursor(path, *column, true);
}

void RecordsetView::on_record_add()
{
  if (_model->is_readonly())
    return;
  Gtk::TreePath tree_path(1);
  size_t row_count= _model->row_count();
  if (row_count == 0)
    return;
  tree_path[0] = row_count;
  _grid->set_cursor(tree_path);
  on_record_edit();
}

void RecordsetView::on_record_del()
{
  if (_model->is_readonly())
    return;
  std::vector<int> rows = _grid->get_selected_rows();
  std::vector<bec::NodeId> nodes;
  for (size_t i = 0; i < rows.size(); i++)
    nodes.push_back(rows[i]);

//  _grid->delete_selected_rows();
  _model->delete_nodes(nodes);
}

void RecordsetView::on_record_sort_asc()
{
  int row, col;
  _grid->current_cell(row, col);
  if (col < 0)
    return;
  _grid->sort_by_column(col, -1, true);
}

void RecordsetView::on_record_sort_desc()
{
  int row, col;
  _grid->current_cell(row, col);
  if (col < 0)
    return;
  _grid->sort_by_column(col, 1, true);
}

void RecordsetView::on_toggle_vertical_sizing()
{
  if (!_grid->get_fixed_height_mode())
  {
    std::vector<Gtk::TreeViewColumn*> columns = _grid->get_columns();
    for (std::vector<Gtk::TreeViewColumn*>::iterator iter = columns.begin(); iter != columns.end(); ++iter)
      (*iter)->set_sizing(Gtk::TREE_VIEW_COLUMN_FIXED);
  }
  _grid->set_fixed_height_mode(!_grid->get_fixed_height_mode());
  refresh();
}

void RecordsetView::set_fixed_row_height(int height)
{
  if (_grid && _grid->view_model())
  {
    std::vector<Gtk::TreeViewColumn*> columns = _grid->get_columns();
    if (_grid->view_model()->row_numbers_visible())
      columns.erase(columns.begin());

    for (std::vector<Gtk::TreeViewColumn*>::iterator iter = columns.begin(); iter != columns.end(); ++iter)
    {
      std::vector<Gtk::CellRenderer*> cells((*iter)->get_cell_renderers());
      for (std::vector<Gtk::CellRenderer*>::iterator cell = cells.begin(); cell != cells.end(); ++cell)
        (*cell)->set_fixed_size(-1, height);
    }
  }
}

