#include "gtk_helpers.h"
#include "image_cache.h"

#include "config.h"

#include <gtkmm/image.h>
#include <gtkmm/filechooserdialog.h>
#include <gtkmm/stock.h>
#include <gtkmm/combobox.h>
#include <gtkmm/comboboxtext.h>
#include <gtkmm/comboboxentrytext.h>
#include <gtkmm/menu.h>
#include <gtkmm/eventbox.h>
#include <gtkmm/paned.h>
#include "text_list_columns_model.h"

#include "treemodel_wrapper.h"

// This list_model is used for all functions which operate on GTKListStore
static TextListColumnsModel _wb_list_model;

Glib::RefPtr<Gtk::ListStore> get_empty_model()
{
  static Glib::RefPtr<Gtk::ListStore> empty_list_store;
  if (!empty_list_store)
    empty_list_store = Gtk::ListStore::create(_wb_list_model);

  return empty_list_store;
}

//------------------------------------------------------------------------------
Gtk::HBox &create_icon_label(const std::string &icon, const std::string &text)
{
  Gtk::HBox *hbox= Gtk::manage(new Gtk::HBox(false, 0));
  
  Gtk::Image *image= Gtk::manage(new Gtk::Image(ImageCache::get_instance()->image_from_filename(icon)));
  Gtk::Label *label= Gtk::manage(new Gtk::Label(text));
  
  label->set_use_markup(true);
  
  hbox->pack_start(*image);
  hbox->pack_start(*label, true, true);
  
  hbox->show_all();
  
  return *hbox;
}


//------------------------------------------------------------------------------
Glib::RefPtr<Gtk::ListStore> model_from_string_list(const std::vector<std::string>& list, TextListColumnsModel* columns)
{
  Glib::RefPtr<Gtk::ListStore> model = Gtk::ListStore::create(*columns);
  
  std::vector<std::string>::const_iterator last = list.end();
  
  for (std::vector<std::string>::const_iterator iter = list.begin(); iter != last; ++iter)
    (*model->append())[columns->item] = *iter;
  
  return model;
}


//------------------------------------------------------------------------------
Glib::RefPtr<Gtk::ListStore> model_from_string_list(const std::vector<std::string>& list, TextListColumnsModel** columns)
{
  if ( columns )
    *columns = &_wb_list_model;

  return model_from_string_list(list, &_wb_list_model);
}


//------------------------------------------------------------------------------
Glib::RefPtr<Gtk::ListStore> model_from_string_list(const std::list<std::string>& list, TextListColumnsModel** columns)
{
  if ( columns )
    *columns = &_wb_list_model;

  Glib::RefPtr<Gtk::ListStore> model = Gtk::ListStore::create(_wb_list_model);
  
  std::list<std::string>::const_iterator last = list.end();
  
  for (std::list<std::string>::const_iterator iter = list.begin(); iter != last; ++iter)
    (*model->append())[_wb_list_model.item] = *iter;
  
  return model;
}


//------------------------------------------------------------------------------
void recreate_model_from_string_list(Glib::RefPtr<Gtk::ListStore> model, const std::vector<std::string>& list)
{
  model->clear();
  
  std::vector<std::string>::const_iterator last = list.end();
  
  for (std::vector<std::string>::const_iterator iter = list.begin(); iter != last; ++iter)
    (*model->append())[_wb_list_model.item] = *iter;
}


//------------------------------------------------------------------------------
void setup_combo_for_string_list(Gtk::ComboBox *combo)
{
  Gtk::CellRendererText *cell= Gtk::manage(new Gtk::CellRendererText());
  combo->pack_end(*cell, true);
  combo->add_attribute(*cell, "text", 0);
}

//------------------------------------------------------------------------------
std::string get_selected_combo_item(Gtk::ComboBox *combo)
{
  Gtk::TreeIter iter= combo->get_active();
  if (iter)
  {
    Gtk::TreeRow row= *iter;
    std::string item= row[_wb_list_model.item];
    
    return item;
  }
  return "";
}

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

bool set_selected_combo_item(Gtk::ComboBox *combo, const std::string &value)
{
  Glib::RefPtr<Gtk::TreeModel> store(combo->get_model());

  for (Gtk::TreeIter end = store->children().end(), iter = store->children().begin();
      iter != end; ++iter)
  {
    Gtk::TreeRow row= *iter;
    std::string item = row[_wb_list_model.item];
    if (item == value)
    {
      combo->set_active(iter);
      return true;
    }
  }
  return false;
}

//------------------------------------------------------------------------------
void set_glib_string(Glib::ValueBase& value, const std::string& str, bool escape_nuls)
{
  GValue *gval = value.gobj();

  g_value_init(gval, G_TYPE_STRING);
  if (escape_nuls)
  {
    std::string tmp;
    std::string::size_type p = 0, e;
    std::string::size_type length = str.length();
    // skip the \0 bytes so that data is displayed as in OSX
    while (p < length)
    {
       e = str.find('\0', p);
       if (e == std::string::npos)
         break;
       tmp.append(str.data()+p, e-p);
       p = e+1;
    }
    if (p < length)
      tmp.append(str.data()+p);
    g_value_set_string(gval, tmp.c_str());
  }
  else
    g_value_set_string(gval, str.c_str());
}

//------------------------------------------------------------------------------
void set_glib_int(Glib::ValueBase& value, const int i)
{
  GValue *gval = value.gobj();

  g_value_init(gval, G_TYPE_INT);
  g_value_set_int(gval, i);
}

//------------------------------------------------------------------------------
void set_glib_bool(Glib::ValueBase& value, const bool b)
{
  GValue *gval = value.gobj();

  g_value_init(gval, G_TYPE_BOOLEAN);
  g_value_set_boolean(gval, b);
}

//------------------------------------------------------------------------------
void set_glib_double(Glib::ValueBase& value, const double d)
{
  GValue *gval = value.gobj();

  g_value_init(gval, G_TYPE_DOUBLE);
  g_value_set_double(gval, d);
}

//------------------------------------------------------------------------------
void fill_combo_from_string_list(Gtk::ComboBox* combo, const std::vector<std::string>& list)
{
  std::vector<std::string>::const_iterator it   = list.begin();
  std::vector<std::string>::const_iterator last = list.end();

  Glib::RefPtr<Gtk::ListStore> store(Glib::RefPtr<Gtk::ListStore>::cast_dynamic(combo->get_model()));
  if (!store)
  {
    store = get_empty_model();
    combo->set_model(store);
  }

  store->clear();

  for (; last != it; ++it )
  {
    Gtk::TreeIter iter = store->append();
    Gtk::TreeRow row= *iter;
    row.set_value(0, *it);
  }
}

//------------------------------------------------------------------------------
void fill_combo_from_string_list(Gtk::ComboBoxEntryText* combo, const std::vector<std::string>& list)
{
  std::vector<std::string>::const_iterator it   = list.begin();
  std::vector<std::string>::const_iterator last = list.end();

  for (; last != it; ++it )
    combo->append_text(*it);
}

//------------------------------------------------------------------------------
static std::string file_chooser_impl(const bool is_for_save, const std::string &filter)
{
  std::string filename;
  Gtk::FileChooserDialog dialog("Please choose a file",
          is_for_save ? Gtk::FILE_CHOOSER_ACTION_SAVE : Gtk::FILE_CHOOSER_ACTION_OPEN);
  dialog.set_transient_for(*get_mainwindow());

  //Add response buttons the the dialog:
  dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
  dialog.add_button(is_for_save ? Gtk::Stock::SAVE : Gtk::Stock::OPEN, Gtk::RESPONSE_OK);

  if (!filter.empty())
  {
    Gtk::FileFilter filter_any;
    //filter_any.set_name("Any files");
    filter_any.add_pattern(filter);
    dialog.add_filter(filter_any);
  }
  const int result = dialog.run();

  switch(result)
  {
    case(Gtk::RESPONSE_OK):
    {
      filename = dialog.get_filename();
      break;
    }
    default:
      break;
  }
  
  return filename;
}

//------------------------------------------------------------------------------
std::string open_file_chooser(const std::string &filter)
{
  return file_chooser_impl(false, filter); // false - is not for save
}

std::string save_file_chooser(const std::string &filter)
{
  return file_chooser_impl(true, filter); // true - is for save
}

//------------------------------------------------------------------------------
static void expand_node(bec::TreeModel* tm, const bec::NodeId& node, Gtk::TreeView* tv)
{
  // Expand the node itself if needed
  if (tm->is_expandable(node))
  {
    if (tm->is_expanded(node))
    {
      const int             node_depth = node.depth();
      Gtk::TreeModel::Path  path;

      for (int i = 0; i < node_depth; ++i)
          path.push_back(node[i]);
 
      tv->expand_row(path, true);
    }

    // Apply the same thing for all node's children
    const int children_count = tm->count_children(node);
    if ( children_count > 0 )
    {
      for (int i = 0; i < children_count; ++i)
      {
        bec::NodeId child = tm->get_child(node, i);
        expand_node(tm, child, tv);
      }
    }
  }
}

//------------------------------------------------------------------------------
void expand_tree_nodes_as_in_be(const Glib::RefPtr<TreeModelWrapper> &model, Gtk::TreeView *tv)
{
  model->block_expand_collapse_signals();
  //TODO: This vv will work once grt_value_tree.h will have correct support for storing expanded/collapsed states 
  //bec::TreeModel *tm = static_cast<bec::TreeModel*>(model->get_be_model());
  //expand_node(tm, tm->get_root(), tv);

  // The code below is a workaround while we do not have working support for store/retrive expanded state of the node
  ExpandedRowsStorage* rows = model->expanded_rows_storage();
  // As: The insert members shall not affect the validity of iterators and references 
  // to the container, and the erase members shall invalidate only iterators and 
  // references to the erased elements. We need to have temp vector of values of invalid rows
  std::vector<ExpandedRowsStorage::key_type> invalid_rows;
  if ( rows )
  {
    ExpandedRowsStorage::const_iterator       row  = rows->begin();
    const ExpandedRowsStorage::const_iterator last = rows->end();
    
    for (; last != row; ++row)
    {
      if (!tv->expand_row(Gtk::TreeModel::Path(*row), false))
        invalid_rows.push_back(*row);
    }
    
    std::vector<ExpandedRowsStorage::key_type>::const_iterator i_row  = invalid_rows.begin();
    std::vector<ExpandedRowsStorage::key_type>::const_iterator i_last = invalid_rows.end();
    for(; i_last != i_row; ++i_row)
      rows->erase(*i_row);
  }
  model->unblock_expand_collapse_signals();
}

//------------------------------------------------------------------------------
//std::string run_string_dialog(const std::string& title, const std::string& init_value)
//{
//  Gtk::Entry entry;
//  Gtk::Dialog dlg;
//  entry.set_text(init_value);
//  entry.show();
//  dlg.add_action_widget(entry, 0xff);
//  dlg.set_title(title);
//  dlg.set_position(Gtk::WIN_POS_MOUSE);
//  dlg.set_transient_for(*get_mainwindow());
//  const int result = dlg.run();
//
//  std::string ret = init_value;
//  switch (result)
//  {
//    case 0xff: // for the magic number 0xff see above add_action_widget
//      ret = entry.get_text();
//      break;
//    default: break;
//  }
//  
//  return ret;
//}


static void populate_popup_menu(const bec::MenuItemList &items, const int time, 
                    const sigc::slot<void, std::string> &activate_slot, Gtk::Menu *popup)
{
  popup->foreach(sigc::mem_fun(popup, &Gtk::Container::remove));

        bec::MenuItemList::const_iterator  cur_item  = items.begin();
  const bec::MenuItemList::const_iterator  last_item = items.end();

  for ( ; last_item != cur_item; cur_item++ )
  {
    Gtk::MenuItem *item = Gtk::manage(new Gtk::MenuItem(bec::replace_string(cur_item->caption, "_", "__"), true));
    item->set_name(cur_item->name);
    item->set_sensitive(cur_item->enabled);
    // not support in Gtk from Ubuntu 8.04
    //item->set_use_underline(false);

    //g_message("run_popup: %s", cur_item->caption.c_str());

    switch ( cur_item->type )
    {
      case bec::MenuAction:
      case bec::MenuUnavailable:
      {
        if ( item )
          item->signal_activate().connect(sigc::bind(activate_slot, cur_item->name));
        break;
      }
      case bec::MenuCascade:
      {
        Gtk::Menu *submenu= Gtk::manage(new Gtk::Menu());
        item->set_submenu(*submenu);
        populate_popup_menu(cur_item->subitems, time, activate_slot, submenu);
        break;
      }
      case bec::MenuRadio:
      {
        //g_message("%s: fake impl of menuradioitem", __FUNCTION__);
      }
      case bec::MenuCheck:
      {
        Gtk::CheckMenuItem* citem = Gtk::manage(new Gtk::CheckMenuItem(cur_item->caption, true));
        item = citem;
        citem->set_active(cur_item->checked);
        citem->signal_activate().connect(sigc::bind(activate_slot, cur_item->name));
        break;
      }
      case bec::MenuSeparator:
      {
        delete item;
        item = Gtk::manage(new Gtk::SeparatorMenuItem());
        break;
      }
      default:
      {
        g_message("%s: WARNING! unhandled menuitem type %i, '%s'", __FUNCTION__, cur_item->type, cur_item->name.c_str());
        break;
      }
    }
    
    popup->append(*item);
    item->show();
  }

  popup->show(); 
}


void run_popup_menu(const bec::MenuItemList &items, const int time, 
                    const sigc::slot<void, std::string> &activate_slot, Gtk::Menu *popup)
{
  populate_popup_menu(items, time, activate_slot, popup);

  popup->popup(3, time);
}

//--------------------------------------------------------------------------------
/*
Gtk::Widget *create_closeable_tab(const Glib::ustring &title,
                                  const sigc::slot<void> &close_callback,
                                  Gtk::Label **title_label)
{
  Gtk::HBox *hbox= Gtk::manage(new Gtk::HBox(false, 1));
  Gtk::Label *label= Gtk::manage(new Gtk::Label("\342\234\225"));
  Gtk::EventBox *evbox= Gtk::manage(new Gtk::EventBox());
  Gtk::Label *text_label= Gtk::manage(new Gtk::Label(title));

  evbox->add(*label);
  label->show();
  hbox->pack_start(*text_label);
  hbox->pack_start(*evbox);

  hbox->show_all();
  
  if (title_label)
    *title_label = text_label;

  return hbox;
}
*/

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

void swap_panned_children(Gtk::Paned *paned, bool fixed_size_1)
{
  Gtk::Widget *w1 = paned->get_child1();
  Gtk::Widget *w2 = paned->get_child2();
    
  w1->reference();
  w2->reference();
  
  paned->remove(*w1);
  paned->remove(*w2);
  
  paned->pack1(*w2, true, fixed_size_1);
  paned->pack2(*w1, true, !fixed_size_1);
  
  w1->unreference();
  w2->unreference();
}

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


static bool disallow_select(const Glib::RefPtr<Gtk::TreeModel> &model, const Gtk::TreeModel::Path &path, bool selected)
{
  return false;
}

static bool allow_select(const Glib::RefPtr<Gtk::TreeModel> &model, const Gtk::TreeModel::Path &path, bool selected)
{
  return true;
}

static void handle_button_press(GdkEventButton *event, Gtk::TreeView *tree)
{
  Gtk::TreeModel::Path path;
  Gtk::TreeViewColumn *column;
  int cx, cy;

  if (tree->get_path_at_pos(event->x, event->y, path, column, cx, cy))
  {
    if (tree->get_selection()->is_selected(path) && (event->state & 0xff) == 0)
    {
      tree->get_selection()->set_select_function(sigc::ptr_fun(disallow_select));
    }
  }
}


static void handle_button_release(GdkEventButton *event, Gtk::TreeView *tree)
{
  tree->get_selection()->set_select_function(sigc::ptr_fun(allow_select));
}


void fix_broken_gtk_selection_handling(Gtk::TreeView *tree)
{
  tree->signal_button_press_event().connect_notify(sigc::bind(sigc::ptr_fun(handle_button_press), tree));
  tree->signal_button_release_event().connect_notify(sigc::bind(sigc::ptr_fun(handle_button_release), tree));
}

//------------------------------------------------------------------------------
void gtk_paned_set_pos_ratio(Gtk::Paned* paned, const float ratio)
{
  const int min_pos = paned->property_min_position();
  const int max_pos = paned->property_max_position();
  const int diff = (max_pos - min_pos) * ratio;
  if (ratio >= 1.0)
    paned->set_position(max_pos);
  else
    paned->set_position(min_pos + diff);
}

//------------------------------------------------------------------------------
float gtk_paned_get_pos_ratio(Gtk::Paned* paned)
{
  const float min_pos = paned->property_min_position();
  const float max_pos = paned->property_max_position();

  return (paned->get_position() - min_pos) / (max_pos - min_pos);
}
