#include "menu_manager.h"
#include <gtkmm.h>
#include "workbench/wb_context_ui.h"
#include "grt/common.h"
#include <cctype>
#include <algorithm>
#include "tree_model.h"

//------------------------------------------------------------------------------
static void del_menu(Gtk::Widget& w, Gtk::MenuShell* bar)
{
  bar->remove(w);
  delete &w;
}

//------------------------------------------------------------------------------
static bool delayed_del_popup(Gtk::Menu* menu)
{
  menu->foreach(sigc::bind(sigc::ptr_fun(&del_menu), menu));
  delete menu;
  return false;  
}

//------------------------------------------------------------------------------
static void del_popup(Gtk::Menu* menu)
{
  Glib::signal_timeout().connect_seconds(sigc::bind(sigc::ptr_fun(&delayed_del_popup), menu), 1);
}

//------------------------------------------------------------------------------
MenuManager::~MenuManager()
{
  if ( _menu_holder )
  {
    Gtk::MenuBar* menubar = static_cast<Gtk::MenuBar*>(_menu_holder->get_child());
    menubar->foreach(sigc::bind(sigc::ptr_fun(&del_menu), menubar));
  }
}

//------------------------------------------------------------------------------
void MenuManager::add_menu_item(const bec::MenuItem& cur_item, Gtk::Menu* menu, Gtk::MenuItem* item)
{
  Gtk::Menu     *submenu = Gtk::manage(new Gtk::Menu());
  item->set_name(cur_item.name);
  
  menu->append(*item);
  
  item->set_sensitive(cur_item.enabled);
  
  if (!cur_item.shortcut.empty())
  {
    guint accel_key= 0;
    Gdk::ModifierType accel_mods = (Gdk::ModifierType)0;
    std::string shortcut;
    
    // convert the accelerator format from Control+X to <control>x which is recognized by gtk
    std::vector<std::string> parts(bec::split_string(cur_item.shortcut, "+"));

    if (parts.size() > 0)
    {
      shortcut= parts.back();
      parts.pop_back();

      while (parts.size() > 0)
      {
        std::string mod= parts.back();
        parts.pop_back();
        std::transform(mod.begin(), mod.end(), mod.begin(), (int(*)(int))std::tolower);
        if ("modifier" == mod)
          mod = "control";
        shortcut= "<"+mod+">"+shortcut;
      }
      if (!shortcut.empty())
        Gtk::AccelGroup::parse(shortcut, accel_key, accel_mods);
    }

    if (accel_key != 0)
      item->add_accelerator("activate", _accel_group, accel_key, accel_mods, Gtk::ACCEL_VISIBLE);
  }
  
  
  if ( process_menuitem(cur_item, item, submenu) )
//    item->set_submenu(*Gtk::manage(submenu));
    item->set_submenu(*submenu);
  else  
    delete submenu;

  item->show();
}

//------------------------------------------------------------------------------
// Return true if we filled menu with smth
bool MenuManager::process_menuitem(const bec::MenuItem &cur_item, Gtk::MenuItem* cur_menu_item, Gtk::Menu* menu)
{
  bool ret = false;

  //g_message("%i '%s' '%s' '%s'", cur_item.type, cur_item.caption.c_str(), cur_item.name.c_str(), cur_item.shortcut.c_str());
  
  const bec::MenuItemList submenu = _be->get_menu_items(cur_item.name);

  if ( submenu.size() > 0 )
  {
    const bec::MenuItemList::const_iterator  last_item_it = submenu.end();
          bec::MenuItemList::const_iterator  cur_item_it  = submenu.begin();

    for ( ; last_item_it != cur_item_it; cur_item_it++ )
    {
      switch ( cur_item_it->type )
      {
        case bec::MenuAction:
        case bec::MenuUnavailable:
        {
          Gtk::MenuItem *item = Gtk::manage(new Gtk::MenuItem(cur_item_it->caption, true));
          if ( item )
          {
            add_menu_item(*cur_item_it, menu, item); // May call process_menuitem to add submenus
            item->signal_activate().connect(sigc::bind(sigc::mem_fun(*this, &MenuManager::item_activated),item));
          }
          ret = true;
          break;
        }
        case bec::MenuCascade:
        {
          Gtk::MenuItem *item = Gtk::manage(new Gtk::MenuItem(cur_item_it->caption, true));
          add_menu_item(*cur_item_it, menu, item); // May call process_menuitem to add submenus
          ret = true;
          break;
        }
        case bec::MenuRadio:
        {
          //g_message("%s: fake impl of menuradioitem", __FUNCTION__);
        }
        case bec::MenuCheck:
        {
          Gtk::CheckMenuItem *item = Gtk::manage(new Gtk::CheckMenuItem(cur_item_it->caption, true));
          if ( item )
          {
            add_menu_item(*cur_item_it, menu, item); // May call process_menuitem to add submenus
            item->set_active(cur_item_it->checked);
            item->signal_activate().connect(sigc::bind(sigc::mem_fun(*this, &MenuManager::item_activated),item));
          }
          ret = true;
          break;
        }
        case bec::MenuSeparator:
        {
          Gtk::SeparatorMenuItem *sep = Gtk::manage(new Gtk::SeparatorMenuItem());
          menu->append(*sep);
          sep->show();
          ret = true;
          break;
        }
        default:
        {
          g_message("%s: WARNING! unhandled menuitem type %i, '%s'", __FUNCTION__, cur_item_it->type, cur_item_it->name.c_str());
          break;
        }
      }
    }
  }      

  return ret;
}


//------------------------------------------------------------------------------
void MenuManager::rebuild(Gtk::Alignment* menu_holder)
{
  _menu_holder = menu_holder;
  Gtk::MenuBar* menubar = static_cast<Gtk::MenuBar*>(menu_holder->get_child());
  //menubar->foreach(sigc::mem_fun(menubar, &Gtk::Container::remove));
  menubar->foreach(sigc::bind(sigc::ptr_fun(&del_menu), menubar));
  
  bec::MenuItemList menus = _be->get_main_menus();
  
        bec::MenuItemList::const_iterator  cur_item  = menus.begin();
  const bec::MenuItemList::const_iterator  last_item = menus.end();

  for ( ; last_item != cur_item; cur_item++ )
  {
    Gtk::MenuItem *item = Gtk::manage(new Gtk::MenuItem(cur_item->caption, true));
    Gtk::Menu     *menu = Gtk::manage(new Gtk::Menu());

    if ( process_menuitem(*cur_item, item, menu) )
    {
      //item->set_submenu(*Gtk::manage(menu));
      item->set_submenu(*menu);
    }
    else
      delete menu;
      
    menubar->append(*item);
    item->show();
  }
  
  // TODO: Check for memleaks here
  menu_holder->show_all();
}

//------------------------------------------------------------------------------
void MenuManager::item_activated(const Gtk::MenuItem* item)
{
  if ( item )
  {
    g_message("%s: %p -> '%s'", __FUNCTION__, item, item->get_name().c_str());
    _be->activate_command(item->get_name());
  }
}

//------------------------------------------------------------------------------
static void return_position(int &x, int &y, bool &push_in, int xx, int yy)
{
  xx= x;
  yy= y;
  push_in= false;
}


//------------------------------------------------------------------------------
void MenuManager::run_popup(const bec::MenuItemList& items, int x, int y, const int time)
{
  Gtk::Menu     *popup = Gtk::manage(new Gtk::Menu());

        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(sigc::mem_fun(*this, &MenuManager::item_activated),item));
        break;
      }
      case bec::MenuCascade:
        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_toggle().connect(sigc::bind(sigc::mem_fun(*this, &MenuManager::item_activated),item));
        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();  
  popup->signal_unmap().connect(sigc::bind(sigc::ptr_fun(&del_popup), popup));

  if (x < 0 && y < 0)
    popup->popup(sigc::bind(sigc::ptr_fun(return_position), x, y), 3, time);
  else
    popup->popup(3, time);
}

void MenuManager::run_popup(const bec::MenuItemList& items, const int time)
{
  run_popup(items, -1, -1, time);
}


void MenuManager::run_popup(const bec::MenuItemList& items, int x, int y, const int time, sigc::slot<void, Gtk::MenuItem*> action)
{
  Gtk::Menu     *popup = new Gtk::Menu();

        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 = new Gtk::MenuItem(bec::replace_string(cur_item->caption, "_", "__"), true);
    item->set_name(cur_item->name);
    item->set_sensitive(cur_item->enabled);

    switch ( cur_item->type )
    {
      case bec::MenuAction:
      case bec::MenuUnavailable:
      {
        if ( item )
          item->signal_activate().connect(sigc::bind(action, item));
        break;
      }
      case bec::MenuCascade:
        break;
      case bec::MenuRadio:
      {}
      case bec::MenuCheck:
      {
        Gtk::CheckMenuItem* citem = Gtk::manage(new Gtk::CheckMenuItem(bec::replace_string(cur_item->caption, "_", "__"), true));
        item = citem;
        citem->set_active(cur_item->checked);
        citem->signal_toggle().connect(sigc::bind(action, item));
        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();  
  popup->signal_unmap().connect(sigc::bind(sigc::ptr_fun(&del_popup), popup));

  if (x < 0 && y < 0)
    popup->popup(sigc::bind(sigc::ptr_fun(return_position), x, y), 3, time);
  else
    popup->popup(3, time);
}

//------------------------------------------------------------------------------
static void process_menu_actions(Gtk::MenuItem* item
                                , bec::ListModel* model
                                , const std::vector<bec::NodeId>& nodes
                                , const sigc::slot<void, const std::string&, const std::vector<bec::NodeId>&> fe_menu_handler
                                )
{
  const std::string command = item->get_name();
  if (!model->activate_popup_item_for_nodes(command, nodes) && !fe_menu_handler.empty())
    fe_menu_handler(command, nodes);
}

//------------------------------------------------------------------------------
void MenuManager::run_and_forward_action(const bec::MenuItemList& items
                                        ,const int x
                                        ,const int y
                                        ,const int time
                                        ,bec::ListModel* model
                                        ,const std::vector<bec::NodeId>& nodes
                                        ,const sigc::slot<void, const std::string&, const std::vector<bec::NodeId>&> fe_menu_handler
                                        )
{
  if (items.size() > 0)
  {
    MenuManager::run_popup(items
                          ,x
                          ,y
                          ,time
                          ,sigc::bind(sigc::ptr_fun(process_menu_actions), model, nodes, fe_menu_handler)
                          );
  }
}
