/* 
 * Copyright (c) 2011, 2014, 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_toolbar.h"
#include "../lf_view.h"
#include <gtkmm/box.h>
#include "image_cache.h"
#include "base/wb_iterators.h"
#include "base/log.h"
DEFAULT_LOG_DOMAIN("mforms.linux")

class ColorComboColumns : public Gtk::TreeModel::ColumnRecord
{
public:
  Gtk::TreeModelColumn<std::string> color;
  Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > image;
  
  ColorComboColumns()
  {
    add(color);
    add(image);
  }
};

static ColorComboColumns *color_combo_columns = 0;

//------------------------------------------------------------------------------
static void process_ctrl_action(Gtk::Widget* w, mforms::ToolBarItem* item)
{
  const int ignore_signal = (long)w->get_data("ignore_signal");
  if (!ignore_signal && item)
    item->callback();
}

//------------------------------------------------------------------------------
namespace
{
template <typename T>
T cast(void *ptr)
{
  return dynamic_cast<T>((Gtk::Widget*)ptr);
}
}

static Gtk::Orientation toolbar_orientation_from_type(const mforms::ToolBarType type)
{
  Gtk::Orientation dir = Gtk::ORIENTATION_HORIZONTAL;

  if (type == mforms::ToolPickerToolBar)
    dir = Gtk::ORIENTATION_VERTICAL;

  return dir;
}

//==============================================================================
//
//==============================================================================

struct ToolBarImpl : public mforms::gtk::ViewImpl
{
  mutable Gtk::Box           *_toolbar;
  const mforms::ToolBarType   _toolbar_type;

  virtual Gtk::Widget *get_outer() const { return _toolbar; }

  ToolBarImpl(mforms::ToolBar* self, const mforms::ToolBarType type)
        : mforms::gtk::ViewImpl(self)
        , _toolbar(0)
        , _toolbar_type(type)
  {
    const bool is_horiz = toolbar_orientation_from_type(type) == Gtk::ORIENTATION_HORIZONTAL;
    _toolbar = is_horiz ? static_cast<Gtk::Box*>(new Gtk::HBox())
                        : static_cast<Gtk::Box*>(new Gtk::VBox());
    _toolbar->show();
  }

  virtual ~ToolBarImpl()
  {
    delete _toolbar;
  }

 protected:
  virtual void set_padding_impl(int left, int top, int right, int bottom);
};

//------------------------------------------------------------------------------
Gtk::Widget *mforms::widget_for_toolbar(mforms::ToolBar *toolbar)
{
  return toolbar->get_data< ::ToolBarImpl>()->get_outer();
}


//------------------------------------------------------------------------------
Gtk::Widget *mforms::widget_for_toolbar_item_named(mforms::ToolBar *toolbar, const std::string& name)
{
  mforms::ToolBarItem *item = toolbar->find_item(name);
  if (item)
  {
    Gtk::Widget *w = cast<Gtk::Widget*>(item->get_data_ptr());
#if GTK_VERSION_LT(2, 16)
    if (item->get_type() == SearchFieldItem)
      w = cast<Gtk::Widget*>(w->get_data("entry"));
#endif
    return w;
  }
  return 0;
}

//------------------------------------------------------------------------------
bool mforms::gtk::ToolBarImpl::create_tool_bar(mforms::ToolBar *item, mforms::ToolBarType type)
{
  return (new ::ToolBarImpl(item, type)) != 0;
}

//------------------------------------------------------------------------------
void mforms::gtk::ToolBarImpl::insert_item(mforms::ToolBar *toolbar, int index, mforms::ToolBarItem *item)
{
  ::ToolBarImpl *impl = toolbar->get_data< ::ToolBarImpl >();
  Gtk::Widget* w = cast<Gtk::Widget*>(item->get_data_ptr());
  if (!w)
    return;

  if (item && item->get_type() == SeparatorItem)
  {
    Gtk::Alignment *align = dynamic_cast<Gtk::Alignment*>(w);
    if (align)
    {
      Gtk::Separator* sep = 0;
      if (toolbar_orientation_from_type(impl->_toolbar_type) == Gtk::ORIENTATION_HORIZONTAL)
        sep = new Gtk::HSeparator;
      else
        sep = new Gtk::VSeparator;

      if (sep)
      {
        sep->show();
        align->add(*Gtk::manage(sep));
        align->show();
      }
    }
  }

  const int size = impl->_toolbar->get_children().size();
  if (index < 0 || index >= size)
    index = -1;
  if (impl && w)
  {
    bool expand = item->get_expandable();
    bool fill   = false;
    if (item->get_type() == mforms::ExpanderItem)
      expand = fill = true;

    impl->_toolbar->pack_start(*Gtk::manage(w), expand, fill);
    impl->_toolbar->reorder_child(*w, index);

    w->show_all();
  }
}

//------------------------------------------------------------------------------
void mforms::gtk::ToolBarImpl::remove_item(mforms::ToolBar *toolbar, mforms::ToolBarItem *item)
{
  ::ToolBarImpl *impl = toolbar->get_data< ::ToolBarImpl >();
  Gtk::Widget* w = item ? cast<Gtk::Widget*>(item->get_data_ptr()) : 0;

  if (impl)
  {
    if (w)
    {
      impl->_toolbar->remove(*w);
    }
    else
    {
      typedef Glib::ListHandle<Gtk::Widget*>    WList;
      WList list = impl->_toolbar->get_children();
      for (base::const_range<WList> it(list); it; ++it)
        impl->_toolbar->remove(*(*it));
    }
  }
}

//------------------------------------------------------------------------------
bool mforms::gtk::ToolBarImpl::create_tool_item(mforms::ToolBarItem *item, ToolBarItemType type)
{
  Gtk::Widget *w = 0;

  switch ( type )
  {
    case mforms::TextActionItem:
    case mforms::ActionItem:
    {
      Gtk::Button *btn = Gtk::manage(new Gtk::Button());
      btn->set_focus_on_click(false);
      btn->set_border_width(0);
      btn->set_relief(Gtk::RELIEF_NONE);
      btn->signal_clicked().connect(sigc::bind(sigc::ptr_fun(process_ctrl_action), btn, item));

      w = btn;
      break;
    }
    case mforms::ToggleItem:
    {
      Gtk::ToggleButton *btn = Gtk::manage(new Gtk::ToggleButton());
      btn->set_focus_on_click(false);
      btn->set_relief(Gtk::RELIEF_NONE);
      btn->signal_toggled().connect(sigc::bind(sigc::ptr_fun(process_ctrl_action), btn, item));
      btn->set_inconsistent(false);

      w = btn;
      break;
    }
    case mforms::SeparatorItem:
    {
      Gtk::Alignment *align = Gtk::manage(new Gtk::Alignment(0.5, 0.5));
      align->set_padding(2, 1, 2, 1);
      w = align;
      break;
    }
    case mforms::SearchFieldItem:
    {
#if GTK_VERSION_GE(2,16)
      Gtk::Entry *entry = Gtk::manage(new Gtk::Entry());
      w= entry;
      entry->set_icon_from_stock(Gtk::Stock::FIND);
#else
      Gtk::HBox *hbox = Gtk::manage(new Gtk::HBox(false, 0));
      w = hbox;
      Gtk::Image *image = Gtk::manage(new Gtk::Image(Gtk::Stock::FIND, Gtk::ICON_SIZE_MENU));
      Gtk::Entry *entry = Gtk::manage(new Gtk::Entry());

      hbox->pack_start(*image, false, true);
      hbox->pack_start(*entry, true, true);
      hbox->set_data("entry", entry);
      hbox->show_all();
#endif
      entry->signal_activate().connect(sigc::bind(sigc::ptr_fun(process_ctrl_action), entry, item));
      break;
    }
    case mforms::SelectorItem:
    {
      Gtk::ComboBoxText *ct  = Gtk::manage(new Gtk::ComboBoxText());
      ct->signal_changed().connect(sigc::bind(sigc::ptr_fun(process_ctrl_action), ct, item));

      w = ct;
      break;
    }
    case mforms::ColorSelectorItem:
    {
      if (!color_combo_columns)
      {
        color_combo_columns = new ColorComboColumns();
      }
      Gtk::ComboBox *ct  = Gtk::manage(new Gtk::ComboBox());

      ct->pack_start(color_combo_columns->image);
      ct->signal_changed().connect(sigc::bind(sigc::ptr_fun(process_ctrl_action), ct, item));

      w = ct;

      break;
    }
    case mforms::ExpanderItem:
    case mforms::LabelItem:
    {
      Gtk::Label *label = Gtk::manage(new Gtk::Label("", 0.0, 0.5));
      w = label;
      break;
    }
    case mforms::ImageBoxItem:
    {
      Gtk::Image *image = Gtk::manage(new Gtk::Image());
      w = image;
      break;
    }
    case mforms::SegmentedToggleItem:
    {
      Gtk::ToggleButton *btn = Gtk::manage(new Gtk::ToggleButton());
      btn->set_focus_on_click(false);
      btn->set_relief(Gtk::RELIEF_NONE);
      btn->signal_toggled().connect(sigc::bind(sigc::ptr_fun(process_ctrl_action), btn, item));
      btn->set_inconsistent(false);

      w = btn;
      break;
    }

  }

  if (w)
  {
    w->show();
  }
  else
    log_error("create_tool_item, widget is 0 for passed type %i\n", type);

  item->set_data(w);

  return w != 0;
}

//------------------------------------------------------------------------------
static void free_icon(gpointer icon_ptr)
{
  Gtk::Image* img = reinterpret_cast<Gtk::Image*>(icon_ptr);
  if (img)
  {
    delete img;
  }
}

//------------------------------------------------------------------------------
void mforms::gtk::ToolBarImpl::set_item_icon(mforms::ToolBarItem *item, const std::string& image_path)
{
  Gtk::Button* btn = cast<Gtk::Button*>(item->get_data_ptr());
  if (btn)
  {
    static ImageCache *images = ImageCache::get_instance();
    Gtk::Image *img = new Gtk::Image(images->image_from_path(image_path));
    if (img)
    {
      btn->set_image(*img);
      btn->set_data("icon", img, free_icon);
      img->show();
    }
  }
}

//------------------------------------------------------------------------------
static void swap_icons(Gtk::ToggleButton* btn)
{
  Gtk::Image *img = 0;

  if (btn->get_active())
    img = cast<Gtk::Image*>(btn->get_data("alt_icon"));
  else
    img = cast<Gtk::Image*>(btn->get_data("icon"));

  img->show();
  btn->set_image(*img);
}

//------------------------------------------------------------------------------
void mforms::gtk::ToolBarImpl::set_item_alt_icon(mforms::ToolBarItem *item, const std::string& image_path)
{
  Gtk::ToggleButton* btn = cast<Gtk::ToggleButton*>(item->get_data_ptr());
  if (btn)
  {
    static ImageCache *images = ImageCache::get_instance();
    Gtk::Image *img = new Gtk::Image(images->image_from_path(image_path));
    if (img)
    {
      btn->set_data("alt_icon", img, free_icon);
      btn->signal_toggled().connect(sigc::bind(sigc::ptr_fun(swap_icons), btn));
    }
  }
}

//------------------------------------------------------------------------------
void mforms::gtk::ToolBarImpl::set_item_text(mforms::ToolBarItem *item, const std::string& label)
{
  const mforms::ToolBarItemType type = item->get_type();

  switch (type)
  {
    case mforms::TextActionItem:
    case mforms::ActionItem:
    case mforms::SegmentedToggleItem:
    case mforms::ToggleItem:
    {
      Gtk::Button* btn = cast<Gtk::Button*>(item->get_data_ptr());
      btn->add_label(label);
      btn->set_name(label);
      break;
    }
    case mforms::LabelItem:
    {
      Gtk::Label* lbl = cast<Gtk::Label*>(item->get_data_ptr());
      if (lbl)
      {
        lbl->set_markup("<small>"+label+"</small>");
        lbl->set_name(label);
      }
      break;
    }
    case mforms::SelectorItem:
    {
      Gtk::ComboBoxText* ct = cast<Gtk::ComboBoxText*>(item->get_data_ptr());
      if (ct)
        ct->set_active_text(label);
      break;
    }
    case mforms::ColorSelectorItem:
    {
      Gtk::ComboBox* combo = cast<Gtk::ComboBox*>(item->get_data_ptr());
      if (combo)
      {
        Glib::RefPtr<Gtk::TreeModel>  model = combo->get_model();
        if (model)
        {
          const Gtk::TreeModel::Children   children = model->children();
          const Gtk::TreeIter              last     = children.end();
          Gtk::TreeRow                     row;

          for (Gtk::TreeIter it = children.begin(); it != last; ++it)
          {
            row = *it;
            if (row.get_value(color_combo_columns->color) == label)
            {
              combo->set_active(it);
              break;
            }
          }
        }
      }
      break;
    }
    case mforms::SearchFieldItem:
    {
      Gtk::Entry* e = cast<Gtk::Entry*>(item->get_data_ptr());
      if (e)
        e->set_text(label);
      break;
    }
    case mforms::SeparatorItem:
    case mforms::ExpanderItem:
    case mforms::ImageBoxItem:
      break;
  }
}

//------------------------------------------------------------------------------
std::string mforms::gtk::ToolBarImpl::get_item_text(mforms::ToolBarItem *item)
{
  std::string text;

  switch (item->get_type())
  {
    case mforms::SelectorItem:
    {
      Gtk::ComboBoxText* ct = cast<Gtk::ComboBoxText*>(item->get_data_ptr());
      if (ct)
        text = ct->get_active_text();
      break;
    }
    case mforms::ColorSelectorItem:
    {
      const Gtk::ComboBox* combo = cast<Gtk::ComboBox*>(item->get_data_ptr());
      if (combo)
      {
        const Gtk::TreeIter iter = combo->get_active();
        const Gtk::TreeRow  row  = *iter;
        text = row.get_value(color_combo_columns->color);
      }
      break;
    }
    case mforms::SearchFieldItem:
    {
      Gtk::Entry* e = cast<Gtk::Entry*>(item->get_data_ptr());
      if (e)
        text = e->get_text();
      break;
    }
    default:
    {
      Gtk::Widget* btn = cast<Gtk::Widget*>(item->get_data_ptr());
      if (btn)
        text = btn->get_name();
    }
  }

  return text;
}

//------------------------------------------------------------------------------
void mforms::gtk::ToolBarImpl::set_item_enabled(mforms::ToolBarItem *item, bool is_on)
{
  Gtk::Widget* w = cast<Gtk::Widget*>(item->get_data_ptr());
  if (w)
    w->set_sensitive(is_on);
}

//------------------------------------------------------------------------------
bool mforms::gtk::ToolBarImpl::get_item_enabled(mforms::ToolBarItem *item)
{
  bool ret = false;
  Gtk::Widget* w = cast<Gtk::Widget*>(item->get_data_ptr());
  if (w)
    ret = w->get_sensitive();

  return ret;
}

//------------------------------------------------------------------------------
void mforms::gtk::ToolBarImpl::set_item_checked(mforms::ToolBarItem *item, bool toggled)
{
  Gtk::ToggleButton* btn = cast<Gtk::ToggleButton*>(item->get_data_ptr());
  if (btn)
  {
    btn->set_data("ignore_signal", (void*)1);
    btn->set_active(toggled);
    btn->set_data("ignore_signal", (void*)0);
  }
}

//------------------------------------------------------------------------------
bool mforms::gtk::ToolBarImpl::get_item_checked(mforms::ToolBarItem *item)
{
  bool ret = false;

  Gtk::ToggleButton* btn = cast<Gtk::ToggleButton*>(item->get_data_ptr());
  if (btn)
    ret = btn->get_active();

  return ret;
}

//------------------------------------------------------------------------------
void mforms::gtk::ToolBarImpl::set_item_tooltip(mforms::ToolBarItem *item, const std::string& text)
{
  Gtk::Widget* w = cast<Gtk::Widget*>(item->get_data_ptr());
  if (w)
  {
    #if GTK_VERSION_GT(2,10)
    w->set_tooltip_text(text);
    #endif
  }
}


//------------------------------------------------------------------------------
void mforms::gtk::ToolBarImpl::set_selector_items(ToolBarItem* item, const std::vector<std::string>& values)
{
  if (item->get_type() == mforms::SelectorItem)
  {
    Gtk::ComboBoxText* w = cast<Gtk::ComboBoxText*>(item->get_data_ptr());
    if (w)
    {
      w->set_data("ignore_signal", (void*)1);
      #if ((GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION >= 24) || GTKMM_MAJOR_VERSION > 2)
      w->remove_all();
      #else
      w->clear_items();
      #endif
      const int size = values.size();
      for (int i = 0; i < size; ++i)
        w->append_text(values[i]);

      if (w->get_active_row_number() < 0 && !values.empty())
        w->set_active_text(values[0]);

      w->set_data("ignore_signal", 0);
    }
  }
  else if (item->get_type() == mforms::ColorSelectorItem)
  {
    Gtk::ComboBox* w = cast<Gtk::ComboBox*>(item->get_data_ptr());
    if (w)
    {
      w->set_data("ignore_signal", (void*)1);

      Glib::RefPtr<Gtk::ListStore> model = Gtk::ListStore::create(*color_combo_columns);
      Glib::RefPtr<Gdk::Colormap>  colormap = w->get_colormap();

      const int size = values.size();
      for (int i = 0; i < size; ++i)
      {
        Gtk::TreeRow row = *model->append();
        Gdk::Color color(values[i]);

        colormap->alloc_color(color);

        Glib::RefPtr<Gdk::Pixbuf> pixbuf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, 16, 14);
        pixbuf->fill(color.get_pixel() << 8);

        row[color_combo_columns->color] = values[i];
        row[color_combo_columns->image] = pixbuf;
      }

      w->set_model(model);

      if (w->get_active_row_number() < 0)
        w->set_active(0);

      w->set_data("ignore_signal", 0);
    }
  }
}

//------------------------------------------------------------------------------
void ::ToolBarImpl::set_padding_impl(int left, int top, int right, int bottom)
{
  _toolbar->set_border_width(left);
}

//------------------------------------------------------------------------------
void mforms::gtk::lf_toolbar_init()
{
  ::mforms::ControlFactory *f = ::mforms::ControlFactory::get_instance();

  f->_tool_bar_impl.create_tool_bar = mforms::gtk::ToolBarImpl::create_tool_bar;
  f->_tool_bar_impl.insert_item = mforms::gtk::ToolBarImpl::insert_item;
  f->_tool_bar_impl.remove_item = mforms::gtk::ToolBarImpl::remove_item;
  f->_tool_bar_impl.create_tool_item = mforms::gtk::ToolBarImpl::create_tool_item;
  f->_tool_bar_impl.set_item_icon = mforms::gtk::ToolBarImpl::set_item_icon;
  f->_tool_bar_impl.set_item_alt_icon = mforms::gtk::ToolBarImpl::set_item_alt_icon;
  f->_tool_bar_impl.set_item_text = mforms::gtk::ToolBarImpl::set_item_text;
  f->_tool_bar_impl.get_item_text = mforms::gtk::ToolBarImpl::get_item_text;
  f->_tool_bar_impl.set_item_enabled = mforms::gtk::ToolBarImpl::set_item_enabled;
  f->_tool_bar_impl.get_item_enabled = mforms::gtk::ToolBarImpl::get_item_enabled;
  f->_tool_bar_impl.set_item_checked = mforms::gtk::ToolBarImpl::set_item_checked;
  f->_tool_bar_impl.get_item_checked = mforms::gtk::ToolBarImpl::get_item_checked;
  f->_tool_bar_impl.set_item_tooltip = mforms::gtk::ToolBarImpl::set_item_tooltip;
  f->_tool_bar_impl.set_selector_items = mforms::gtk::ToolBarImpl::set_selector_items;
}
