/* 
 * Copyright (c) 2008, 2012, 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 "../lf_mforms.h"
#include "../lf_treeview.h"

#include <glib.h>
#include "base/log.h"
#include "base/string_utilities.h"

#define NO_INT64_COLUMNS

DEFAULT_LOG_DOMAIN("mforms.linux");

namespace mforms {
namespace gtk {


TreeViewImpl::ColumnRecord::~ColumnRecord()
{
  for (std::vector<Gtk::TreeModelColumnBase*>::iterator iter= columns.begin();
       iter != columns.end(); ++iter)
    delete *iter;
}

void TreeViewImpl::ColumnRecord::add_tag_column()
{
  add(_tag_column);
}

Gtk::TreeModelColumn<Glib::ustring>& TreeViewImpl::ColumnRecord::tag_column()
{
  return _tag_column;
}

int TreeViewImpl::ColumnRecord::add_string(Gtk::TreeView *tree, const std::string &title, bool editable)
{
  Gtk::TreeModelColumn<Glib::ustring> *column= new Gtk::TreeModelColumn<Glib::ustring>();
  
  columns.push_back(column);
  add(*column);
        
  if (editable)
    tree->append_column_editable(title, *column);
  else
    tree->append_column(title, *column);
  
  return columns.size()-1;
}


int TreeViewImpl::ColumnRecord::add_integer(Gtk::TreeView *tree, const std::string &title, bool editable)
{
  Gtk::TreeModelColumn<int> *column= new Gtk::TreeModelColumn<int>();
  
  columns.push_back(column);
  add(*column);

  if (editable)
    tree->append_column_editable(title, *column);
  else
    tree->append_column(title, *column);

  return columns.size()-1;
}

int TreeViewImpl::ColumnRecord::add_long_integer(Gtk::TreeView *tree, const std::string &title, bool editable)
{
#ifdef NO_INT64_COLUMNS
  Gtk::TreeModelColumn<Glib::ustring> *column= new Gtk::TreeModelColumn<Glib::ustring>();
#else
  Gtk::TreeModelColumn<boost::int64_t> *column= new Gtk::TreeModelColumn<boost::int64_t>();
#endif
  
  columns.push_back(column);
  add(*column);

  if (editable)
    tree->append_column_editable(title, *column);
  else
    tree->append_column(title, *column);

  return columns.size()-1;
}


int TreeViewImpl::ColumnRecord::add_check(Gtk::TreeView *tree, const std::string &title, bool editable)
{
  Gtk::TreeModelColumn<bool> *column= new Gtk::TreeModelColumn<bool>();
  
  columns.push_back(column);
  add(*column);

  if (editable)
    tree->append_column_editable(title, *column);
  else
    tree->append_column(title, *column);

  return columns.size()-1;
}


int TreeViewImpl::ColumnRecord::add_pixbuf(Gtk::TreeView *tree, const std::string &title)
{
  Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > *column= new Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> >();
  columns.push_back(column);
  add(*column);

  tree->append_column(title, *column);

  return columns.size()-1;
}


TreeViewImpl::TreeViewImpl(TreeView *self, mforms::TreeOptions opts)
  : ViewImpl(self), _row_height(-1)
{
  _swin.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  _swin.set_shadow_type(Gtk::SHADOW_IN);

  _conn = _tree.get_selection()->signal_changed().connect(sigc::mem_fun(self, &TreeView::changed));
  _tree.signal_row_activated().connect(sigc::mem_fun(this, &TreeViewImpl::on_activated));
  _tree.signal_button_press_event().connect_notify(sigc::mem_fun(this, &TreeViewImpl::on_button_event));
  _tree.set_reorderable(opts & mforms::TreeAllowReorderRows);

  _swin.add(_tree);
  _swin.show_all();
  //_tree->set_headers_visible(opts & mforms::TreeShowHeader);
}


void TreeViewImpl::string_edited(const Glib::ustring &path, const Glib::ustring &new_text, int column)
{
  if (_list_store)
  {
    Gtk::TreePath tree_path = to_list_path(Gtk::TreePath(path));
    Gtk::TreeRow row= *_list_store->get_iter(tree_path);

    if (dynamic_cast<TreeView*>(owner)->cell_edited(atoi(tree_path.to_string().c_str()), column, new_text))
      row[_columns.get<Glib::ustring>(column)]= new_text;
  }
}
  

void TreeViewImpl::toggle_edited(const Glib::ustring &path, int column)
{
  if (_list_store)
  {
    Gtk::TreePath tree_path = to_list_path(Gtk::TreePath(path));
    Gtk::TreeRow row= *_list_store->get_iter(Gtk::TreePath(tree_path));
    std::string new_text = row[_columns.get<bool>(column)] ? "0" : "1";

    if (dynamic_cast<TreeView*>(owner)->cell_edited(atoi(tree_path.to_string().c_str()), column, new_text))
      row[_columns.get<bool>(column)]= !row[_columns.get<bool>(column)];
  }
}

void TreeViewImpl::on_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column)
{
  mforms::TreeView* tv = dynamic_cast<mforms::TreeView*>(owner); // owner is from deeply hidden class TreeViewImpl->ViewImpl->ObjectImpl.owner
  if (tv)
  {
    Gtk::TreePath tree_path = to_list_path(path);
    tv->row_activated(tree_path.front(), 0); // TODO XXX pass column index here
  }
}

void TreeViewImpl::on_button_event(GdkEventButton *event)
{
  if (event->button == 3)
  {
    mforms::TreeView* tv = dynamic_cast<mforms::TreeView*>(owner); 
    if (tv->get_context_menu())
    {
      tv->get_context_menu()->popup_at(tv, event->x, event->y);
    }
  }
}

int TreeViewImpl::add_column(TreeColumnType type, const std::string &name, int initial_width, bool editable)
{
  int column;
  switch (type)
  {
  case StringColumnType:
    column= _columns.add_string(&_tree, name, editable);
    if (editable)
    {
      ((Gtk::CellRendererText*)_tree.get_column(column)->get_first_cell_renderer())->signal_edited().
        connect(sigc::bind(sigc::mem_fun(this, &TreeViewImpl::string_edited), column));
    }
    break;
  case IntegerColumnType: 
    column= _columns.add_integer(&_tree, name, editable);
    if (editable)
    {
      ((Gtk::CellRendererText*)_tree.get_column(column)->get_first_cell_renderer())->signal_edited().
        connect(sigc::bind(sigc::mem_fun(this, &TreeViewImpl::string_edited), column));
    }
    break;
  case LongIntegerColumnType: 
    column= _columns.add_long_integer(&_tree, name, editable);
    if (editable)
    {
      ((Gtk::CellRendererText*)_tree.get_column(column)->get_first_cell_renderer())->signal_edited().
        connect(sigc::bind(sigc::mem_fun(this, &TreeViewImpl::string_edited), column));
    }
    break;
  case CheckColumnType: 
    column= _columns.add_check(&_tree, name, editable);
    if (editable)
    {
      ((Gtk::CellRendererToggle*)_tree.get_column(column)->get_first_cell_renderer())->signal_toggled().
        connect(sigc::bind(sigc::mem_fun(this, &TreeViewImpl::toggle_edited), column));
    }
    break;
  case IconColumnType:
    column = _columns.add_pixbuf(&_tree, name);
    break;
  }
  
  _tree.get_column(column)->set_resizable(true);
  if (initial_width > 0)
    _tree.get_column(column)->set_fixed_width(initial_width);
  
  return column;
}


void TreeViewImpl::end_columns()
{
  _columns.add_tag_column();
  _list_store= Gtk::ListStore::create(_columns);
  _tree.set_model(_list_store);
}



bool TreeViewImpl::create(TreeView *self, mforms::TreeOptions opt)
{
  return new TreeViewImpl(self, opt) != 0;
}

int TreeViewImpl::add_column(TreeView *self, TreeColumnType type, const std::string &name, int width, bool editable)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();

  return tree->add_column(type, name, width, editable);
}

void TreeViewImpl::end_columns(TreeView *self)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();

  tree->end_columns();
}

void TreeViewImpl::clear_rows(TreeView *self)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();

  if (tree->_list_store)
    tree->_list_store->clear();
}

void TreeViewImpl::delete_row(TreeView *self, int row)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();
  Gtk::TreePath path;
  
  path.append_index(row);

  if (tree->_list_store)
    tree->_list_store->erase(tree->_list_store->get_iter(path));
}

int TreeViewImpl::add_row(TreeView *self)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();

  if (tree->_list_store)
    return tree->_list_store->get_path(tree->_list_store->append()).front();
  
  return -1;
}

int TreeViewImpl::get_selected(TreeView *self)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();
  int idx = -1;
  
  if (tree->_tree.get_selection()->get_selected())
  {
    const Gtk::TreePath path(tree->to_list_iter(tree->_tree.get_selection()->get_selected()));
    if (path.gobj() && !path.empty())
    {
      idx = path.front();
    }
  }
  
  return idx;
}

void TreeViewImpl::set_selected(TreeView* self, const int idx)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();

  tree->_conn.block();
  if (idx < 0)
    tree->_tree.get_selection()->unselect_all();
  else
  {
    Gtk::TreePath path;
    path.append_index(idx);
    path = tree->to_sort_path(path);
    tree->_tree.get_selection()->select(path);
  }
  tree->_conn.unblock();
}

int TreeViewImpl::count(TreeView *self)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();

  if (tree->_list_store)
    return tree->_list_store->children().size();
  
  return 0;
}

void TreeViewImpl::set_string(TreeView *self, int row, int column, const std::string &value)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();
  Gtk::TreeRow tree_row;
  Gtk::TreePath path;

  path.append_index(row);

  if (tree->_list_store)
  {
    tree_row= *tree->_list_store->get_iter(path);
  }
  else
    return;

  if (dynamic_cast<Gtk::CellRendererPixbuf*>(tree->_tree.get_column(column)->get_first_cell_renderer()))
  {
    if (tree->_pixbufs.find(value) == tree->_pixbufs.end())
    {
      try
      {
        Glib::RefPtr<Gdk::Pixbuf> pixbuf = Gdk::Pixbuf::create_from_file(mforms::App::get()->get_resource_path(value));
        if (pixbuf)
        {
          int w, h;
          int size = tree->_row_height < 0 ? 22 : tree->_row_height - 2;
          w = pixbuf->get_width();
          h = pixbuf->get_height();
          if (w > size || h > size)
          {
            if (h > w)
            {
              w = size*w/h;
              h = size;
            }
            else
            {
              h = size*h/w;
              w = size;
            }
            tree->_pixbufs[value] = pixbuf->scale_simple(w, h, Gdk::INTERP_BILINEAR);
          }
          else
            tree->_pixbufs[value] = pixbuf;
        }
      }
      catch (const Glib::Error &exc)
      {
        log_error("can't load icon: %s", exc.what().c_str());
      }
    }
    tree_row[tree->_columns.get<Glib::RefPtr<Gdk::Pixbuf> >(column)]= tree->_pixbufs[value];
  }
  else
    tree_row[tree->_columns.get<Glib::ustring>(column)]= value;
}

void TreeViewImpl::set_integer(TreeView *self, int row, int column, int value)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();

  if (tree)
  {
    if (dynamic_cast<Gtk::CellRendererToggle*>(tree->_tree.get_column(column)->get_first_cell_renderer()))
    {
      set_check(self, row, column, value);
      return;
    }

    Gtk::TreeRow tree_row;
    Gtk::TreePath path;

    path.append_index(row);

    if (tree->_list_store)
      tree_row= *tree->_list_store->get_iter(path);
    else
      return;

    tree_row[tree->_columns.get<int>(column)]= value;
  }
}

void TreeViewImpl::set_long(TreeView *self, int row, int column, boost::int64_t value)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();

  if (tree)
  {
    if (dynamic_cast<Gtk::CellRendererToggle*>(tree->_tree.get_column(column)->get_first_cell_renderer()))
    {
      set_check(self, row, column, value);
      return;
    }

    Gtk::TreeRow tree_row;
    Gtk::TreePath path;

    path.append_index(row);

    if (tree->_list_store)
      tree_row= *tree->_list_store->get_iter(path);
    else
      return;
#ifdef NO_INT64_COLUMNS
    tree_row[tree->_columns.get<Glib::ustring>(column)]= base::strfmt("%lli", value);
#else
    tree_row[tree->_columns.get<boost::int64_t>(column)]= value;
#endif
  }
}


void TreeViewImpl::set_check(TreeView *self, int row, int column, bool check)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();
  Gtk::TreeRow tree_row;
  Gtk::TreePath path;

  path.append_index(row);

  if (tree->_list_store)
    tree_row= *tree->_list_store->get_iter(path);
  else
    return;

  tree_row[tree->_columns.get<bool>(column)]= check;
}


std::string TreeViewImpl::get_string(TreeView *self, int row, int column)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();
  Gtk::TreeRow tree_row;
  Gtk::TreePath path;

  path.append_index(row);

  if (tree->_list_store)
    tree_row= *tree->_list_store->get_iter(path);
  else
    return "";

  return ((Glib::ustring)tree_row[tree->_columns.get<Glib::ustring>(column)]).c_str();
}

int TreeViewImpl::get_int(TreeView *self, int row, int column)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();
  Gtk::TreeRow tree_row;
  Gtk::TreePath path;

  path.append_index(row);

  if (tree->_list_store)
    tree_row= *tree->_list_store->get_iter(path);
  else
    return 0;

  return tree_row[tree->_columns.get<int>(column)];
}
 
boost::int64_t TreeViewImpl::get_long(TreeView *self, int row, int column)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();
  Gtk::TreeRow tree_row;
  Gtk::TreePath path;

  path.append_index(row);

  if (tree->_list_store)
    tree_row= *tree->_list_store->get_iter(path);
  else
    return 0;
#ifdef NO_INT64_COLUMNS
  Glib::ustring text = tree_row[tree->_columns.get<Glib::ustring>(column)];
  return strtoll(text.c_str(), NULL, 0);
#else
  return tree_row[tree->_columns.get<boost::int64_t>(column)];
#endif
}
 

bool TreeViewImpl::get_check(TreeView *self, int row, int column)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();
  Gtk::TreeRow tree_row;
  Gtk::TreePath path;

  path.append_index(row);

  if (tree->_list_store)
    tree_row= *tree->_list_store->get_iter(path);
  else
    return false;

  return tree_row[tree->_columns.get<bool>(column)];
}

std::string TreeViewImpl::get_row_tag(TreeView *self, int row)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();
  Glib::ustring ret;

  if (tree->_list_store)
  {
    Gtk::TreePath path;
    path.append_index(row);

    const Gtk::TreeIter iter = tree->_list_store->get_iter(path);
    if (iter)
    {
      const Gtk::TreeRow row = *iter;
      ret = row[tree->_columns.tag_column()];
    }
  }

  return ret;
}

void TreeViewImpl::set_row_tag(TreeView *self, int row, const std::string &value)
{
  TreeViewImpl* tree = self->get_data<TreeViewImpl>();

  if (tree->_list_store)
  {
    Gtk::TreePath path;
    path.append_index(row);

    Gtk::TreeIter iter = tree->_list_store->get_iter(path);
    if (iter)
    {
      Gtk::TreeRow tree_row = *tree->_list_store->get_iter(path);
      tree_row[tree->_columns.tag_column()] = value;
    }
  }
}

static int str_cmp(const Gtk::TreeModel::iterator& it1, const Gtk::TreeModel::iterator& it2, Gtk::TreeModelColumn< Glib::ustring> *col)
{
  const Glib::ustring s1 = (*it1).get_value(*col);
  const Glib::ustring s2 = (*it2).get_value(*col);

  if (s1.is_ascii() && s2.is_ascii())
    return strcmp(s1.c_str(), s2.c_str());
  else
    return s1.compare(s2) ;
}

void TreeViewImpl::set_allow_sorting(TreeView* self, bool flag)
{
  TreeViewImpl* impl = self->get_data<TreeViewImpl>();
  Gtk::TreeView *tv = &(impl->_tree);

  tv->set_headers_clickable(flag);
  if (flag)
  {
    if (!impl->_sort_model)
      impl->_sort_model = Gtk::TreeModelSort::create(impl->_list_store);

    const int ncols = impl->_columns.size() - 1;
    for (int i = 0; i < ncols; ++i)
    {
      Gtk::TreeViewColumn* col = impl->_tree.get_column(i);
      Gtk::TreeModelColumnBase *mcol = impl->_columns.get_column(i);
      if (mcol->type() == G_TYPE_STRING)
      {
        impl->_sort_model->set_sort_func(*mcol, sigc::bind(sigc::ptr_fun(str_cmp), (Gtk::TreeModelColumn< Glib::ustring>*)mcol));
      }

      if (mcol && col)
      {
        col->signal_clicked().connect(sigc::bind(sigc::mem_fun(impl, &TreeViewImpl::header_clicked),mcol,col));
      }
    }

    // temporarily disable selection change signal, gtk emits it when setting model
    impl->_conn.disconnect();
    tv->set_model(impl->_sort_model);
    impl->_conn = impl->_tree.get_selection()->signal_changed().connect(sigc::mem_fun(self, &TreeView::changed));
  }
}

void TreeViewImpl::freeze_refresh(TreeView* self, bool flag)
{
  //TreeViewImpl* impl = self->get_data<TreeViewImpl>();
  //Gtk::TreeView *tv = impl->_tree;

  //tv->set_headers_clickable(!flag);
  //if (!flag)
  //  tv->set_model(impl->_list_store);
  //else
  //  tv->unset_model();
}

Gtk::TreeModel::iterator TreeViewImpl::to_sort_iter(const Gtk::TreeModel::iterator &it)
{
  return (_tree.get_headers_clickable()) ? _sort_model->convert_child_iter_to_iter(it) : it;
}

Gtk::TreeModel::Path TreeViewImpl::to_sort_path(const Gtk::TreeModel::Path &path)
{
  return (_tree.get_headers_clickable()) ? _sort_model->convert_child_path_to_path(path) : path;
}

Gtk::TreeModel::iterator TreeViewImpl::to_list_iter(const Gtk::TreeModel::iterator &it)
{
  return (_tree.get_headers_clickable()) ? _sort_model->convert_iter_to_child_iter(it) : it;
}

Gtk::TreeModel::Path TreeViewImpl::to_list_path(const Gtk::TreeModel::Path &path)
{
  return (_tree.get_headers_clickable()) ? _sort_model->convert_path_to_child_path(path) : path;
}

void TreeViewImpl::header_clicked(Gtk::TreeModelColumnBase* cbase, Gtk::TreeViewColumn* col)
{
  if (!(col && cbase))
    return;

  // Get sort order if anything, if absent set to ASC
  void* data = col->get_data("sord");
  Gtk::SortType sort_order = (Gtk::SortType)((long)data);
  if (sort_order == Gtk::SORT_ASCENDING)
    sort_order = Gtk::SORT_DESCENDING;
  else
    sort_order = Gtk::SORT_ASCENDING;

  const std::vector<Gtk::TreeViewColumn*> cols = _tree.get_columns();
  for (int i = cols.size() - 1; i >= 0; --i)
  {
    if (cols[i] != col)
      cols[i]->set_sort_indicator(false);
  }

  // Call set_sort_column
  _sort_model->set_sort_column(*cbase, sort_order);
  col->set_sort_indicator(true);
  col->set_sort_order(sort_order);
  col->set_data("sord", (void*)sort_order);
}


void TreeViewImpl::set_row_height(TreeView *self, int height)
{
  TreeViewImpl* impl = self->get_data<TreeViewImpl>();
  impl->_row_height = height;
}


void TreeViewImpl::init()
{
  ::mforms::ControlFactory *f = ::mforms::ControlFactory::get_instance();

  f->_treeview_impl.create= &TreeViewImpl::create;
  f->_treeview_impl.add_column= &TreeViewImpl::add_column;
  f->_treeview_impl.end_columns= &TreeViewImpl::end_columns;
  f->_treeview_impl.clear_rows= &TreeViewImpl::clear_rows;
  f->_treeview_impl.delete_row= &TreeViewImpl::delete_row;
  f->_treeview_impl.add_row= &TreeViewImpl::add_row;
  f->_treeview_impl.get_selected= &TreeViewImpl::get_selected;
  f->_treeview_impl.set_selected= &TreeViewImpl::set_selected;
  f->_treeview_impl.count= &TreeViewImpl::count;
  f->_treeview_impl.set_string= &TreeViewImpl::set_string;
  f->_treeview_impl.set_int= &TreeViewImpl::set_integer;
  f->_treeview_impl.set_long= &TreeViewImpl::set_long;
  f->_treeview_impl.set_check= &TreeViewImpl::set_check;
  f->_treeview_impl.get_string= &TreeViewImpl::get_string;
  f->_treeview_impl.get_int= &TreeViewImpl::get_int; 
  f->_treeview_impl.get_long= &TreeViewImpl::get_long; 
  f->_treeview_impl.get_check= &TreeViewImpl::get_check;
  f->_treeview_impl.set_row_tag= &TreeViewImpl::set_row_tag;
  f->_treeview_impl.get_row_tag= &TreeViewImpl::get_row_tag;
  f->_treeview_impl.set_allow_sorting= &TreeViewImpl::set_allow_sorting;
  f->_treeview_impl.freeze_refresh= &TreeViewImpl::freeze_refresh;
  f->_treeview_impl.set_row_height= &TreeViewImpl::set_row_height;
}

}
}
