// src/gui_builder.cc - Copyright 2005, 2006, University
//               of Padova, dept. of Pure and Applied
//               Mathematics
//
// This file is part of SGPEMv2.
//
// This 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; either version 2 of the License, or
// (at your option) any later version.
//
// SGPEMv2 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 SGPEMv2; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

#include "config.h"
#include "gettext.h"

#include "gui_builder.hh"

#include "configure_policy_dialog.hh"
#include "graphical_preferences_editor.hh"
#include "holt_container_window.hh"
#include "holt_widget.hh"
#include "ready_queue_widget.hh"
#include "schedulables_tree_widget.hh"
#include "simulation_widget.hh"
#include "resources_widget.hh"
#include "jump_to_dialog.hh"

#include <sgpemv2/templates/sequences.tcc>

#include <sgpemv2/cpu_policy_exception.hh>
#include <sgpemv2/null_policy_exception.hh>
#include <sgpemv2/cpu_policies_gatekeeper.hh>
#include <sgpemv2/cpu_policy_manager.hh>
#include <sgpemv2/resource_policies_gatekeeper.hh>
#include <sgpemv2/resource_policy_manager.hh>
#include <sgpemv2/history.hh>
#include <sgpemv2/policy_parameters.hh>
#include <sgpemv2/simulation.hh>
#include <sgpemv2/serializers_gatekeeper.hh>
#include <sgpemv2/serializer.hh>

#include <gdkmm/pixbuf.h>
#include <glibmm/markup.h>
#include <glibmm/ustring.h>
#include <gtkmm/aboutdialog.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/menutoolbutton.h>
#include <gtkmm/filechooserdialog.h>
#include <gtkmm/main.h>
#include <gtkmm/menuitem.h>
#include <gtkmm/checkmenuitem.h>
#include <gtkmm/radiomenuitem.h>
#include <gtkmm/radiobuttongroup.h>
#include <gtkmm/scrolledwindow.h>
#include <gtkmm/statusbar.h>
#include <gtkmm/stock.h>

#include <cassert>
#include <iostream>

using namespace sgpem;
using Gnome::Glade::Xml;

void
GuiBuilder::on_edit_preferences_activate()
{
	new PreferencesEditor(); //FIXME: are we leaking this way? (definitely yes. use
                           // a refptr member data?)
}

void
GuiBuilder::on_simulation_jump_to_activate()
{
  using Gnome::Glade::Xml;
  
  Glib::RefPtr<Xml> jump_to_dialog_glade = Xml::create(GLADEDIR "/jump-to-dialog.glade");
    
  JumpToDialog* jump_to_dialog = NULL;
  jump_to_dialog_glade->get_widget_derived("JumpToDialog", jump_to_dialog);

  jump_to_dialog->run();
}

void
GuiBuilder::on_view_show_threads_activate()
{
  _show_threads = !_show_threads;
  _simulation_widget->set_show_threads(_show_threads);
  _simulation_widget->resize_redraw();
  _holt_container.get_holt_widget().set_show_threads(_show_threads);
  _holt_container.get_holt_widget().update(Simulation::get_instance());
}

void
GuiBuilder::on_view_show_holt_graph_activate()
{
  if(_holt_container.is_visible())
  {
    _holt_container.hide();
  }
  else
  {
    _holt_container.show();
  }
}


void
GuiBuilder::on_file_new_activate()
{
  Simulation& sim = Simulation::get_instance();
	History& history = sim.get_history();
  const Environment& env = history.get_environment_at(0);

  if(!(_filename.empty() && env.get_processes().empty() && env.get_resources().empty()))
    {
       Gtk::MessageDialog want_to_save(get_initial_window(),
                         _("<b>Want to save?</b>\nYou'll lose your changes if you don't."),
                         true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true);
       if(want_to_save.run() == Gtk::RESPONSE_YES)
          on_file_save_activate();
    }

 	sim.stop();
  history.clear();
  _filename = "";
}



void
GuiBuilder::on_file_open_activate()
{
  Glib::ustring msg;
  try
  {
    std::vector<Serializer*> serializers =
      SerializersGatekeeper::get_instance().get_registered();

    // FIXME using the first serializer available, this
    // will need to be changed when multiple serializers will
    // be made available
    Serializer& serializer = *serializers.at(0);

    Simulation& sim = Simulation::get_instance();

    // open file dialog...
    Gtk::FileChooserDialog dialog("Please choose a file", Gtk::FILE_CHOOSER_ACTION_OPEN);
    dialog.set_transient_for(get_initial_window());
    if(!_filename.empty()) // Please test the following line extensively:
      dialog.set_current_folder(Glib::path_get_dirname(_filename));

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

    //Add filters, so that only certain file types can be selected:
    for(std::vector<Serializer*>::const_iterator iter=serializers.begin(); iter!=serializers.end(); iter++)
    {
      Serializer* ser = *iter;
      Gtk::FileFilter filter_sgpem;
      filter_sgpem.set_name(ser->get_filename_description());
      filter_sgpem.add_pattern(Glib::ustring("*.") + ser->get_filename_extension());
      dialog.add_filter(filter_sgpem);
    }

    Gtk::FileFilter filter_any;
    filter_any.set_name("Any files");
    filter_any.add_pattern("*");
    dialog.add_filter(filter_any);

    //Show the dialog and wait for a user response:
    int result = dialog.run();
    if(result==Gtk::RESPONSE_OK)
    {
      _filename = dialog.get_filename();
      sim.stop(); // It would work anyhow, but it'd look strange
      History& history = sim.get_history();
      serializer.restore_snapshot(_filename, history);
      msg = "File: " + _filename + " loaded.";
    } // end - if(result==Gtk::RESPONSE_OK)

  }
  catch (std::out_of_range e)
  {
    // FIXME: this should be an error dialog
    msg = _("ERROR: No registered serializer available");
  }
  catch (SerializerError e)
  {
    // FIXME: this should be an error dialog
    msg = _("ERROR: ") + Glib::ustring(e.what());
  }
  if(!msg.empty())
  {
    Gtk::Statusbar* sbar = _refXml->get_widget("MainStatusBar", sbar);
    sbar->push(msg);
  }
}

void
GuiBuilder::on_file_save_activate()
{
  if(_filename.empty())
    {
      on_file_saveas_activate();
      return;
    }

  // else:

  Glib::ustring msg;
  try
    {
      std::vector<Serializer*> serializers =
        SerializersGatekeeper::get_instance().get_registered();
      
      // FIXME using the first serializer available, this
      // will need to be changed when multiple serializers will
      // be made available
      Serializer& serializer = *serializers.at(0);
      History& history = Simulation::get_instance().get_history();
      
      serializer.save_snapshot(_filename, history);
      msg = "File: " + _filename + " saved.";
    }
  catch (std::out_of_range e)
    {
      // FIXME: this should be an error dialog
      msg = _("ERROR: No registered serializer available");
    }
  catch (SerializerError e)
    {
      // FIXME: this should be an error dialog
      msg = _("ERROR: ") + Glib::ustring(e.what());
    }
  
  if(!msg.empty())
    {
      Gtk::Statusbar* sbar = _refXml->get_widget("MainStatusBar", sbar);
      sbar->push(msg);
    }
}


void
GuiBuilder::on_file_saveas_activate()
{
  Glib::ustring msg;
  try
  {
    std::vector<Serializer*> serializers =
      SerializersGatekeeper::get_instance().get_registered();

    // FIXME using the first serializer available, this
    // will need to be changed when multiple serializers will
    // be made available
    Serializer& serializer = *serializers.at(0);
    History& history = Simulation::get_instance().get_history();

    // open file dialog...
    Gtk::FileChooserDialog dialog("Please choose a file", Gtk::FILE_CHOOSER_ACTION_SAVE);
    dialog.set_transient_for(get_initial_window());

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

    //Add filters, so that only certain file types can be selected:
    Gtk::FileFilter filter_sgpem;
    filter_sgpem.set_name(serializer.get_filename_description());
    filter_sgpem.add_pattern(Glib::ustring("*.") + serializer.get_filename_extension());
    dialog.add_filter(filter_sgpem);
  
    Gtk::FileFilter filter_any;
    filter_any.set_name("Any files");
    filter_any.add_pattern("*");
    dialog.add_filter(filter_any);
  
    //Show the dialog and wait for a user response:
    int result = dialog.run();
    if(result==Gtk::RESPONSE_OK)
    {
      _filename = dialog.get_filename();

      // Append standard extension if none (or a different one) is provided
      std::string ext = std::string(".") + serializer.get_filename_extension();
      if(_filename.size() < ext.size() || _filename.substr(_filename.size() - ext.size()) != ext)
        _filename += ext;

      serializer.save_snapshot(_filename, history);

      msg = "File: " + _filename + " saved.";
    } // end - if(result==Gtk::RESPONSE_OK)

  }
  catch (std::out_of_range e)
  {
    // FIXME: This should be an error dialog:
    msg = _("ERROR: No registered serializer available");
  }
  catch (SerializerError e)
  {
    // FIXME: This should be an error dialog:
    msg = _("ERROR: ") + Glib::ustring(e.what());
  }
  
  if(!msg.empty())
  {
    Gtk::Statusbar* sbar = _refXml->get_widget("MainStatusBar", sbar);
    sbar->push(msg);
  }
}


void
GuiBuilder::on_configure_cpu_policy()
{
  using namespace Gtk;

  Simulation& sim    = Simulation::get_instance();
  CPUPolicy* policy  = sim.get_policy();

  if(policy == NULL)
    {
      MessageDialog warn(get_initial_window(),
                         _("<b>No CPU policy is currently selected.</b>\nPlease choose one before trying to configure it."),
                         true, MESSAGE_WARNING, BUTTONS_OK, true);
      warn.run();
    }
  
  PolicyParameters& params = policy->get_parameters();
  ConfigurePolicyDialog config_dialog(_("Configuring CPU Policy ") + policy->get_name(), 
                                      get_initial_window(), policy->get_description(), params);

  if(config_dialog.run() == RESPONSE_OK)
    {
      sim.stop();
      sim.get_history().reset();
    }
}


void
GuiBuilder::on_configure_resource_policy()
{
  using namespace Gtk;

  Simulation&     sim     = Simulation::get_instance();
  ResourcePolicy* policy  = sim.get_resource_policy();

  if(policy == NULL)
    {
      MessageDialog warn(get_initial_window(),
                         _("<b>No CPU policy is currently selected.</b>\nPlease choose one before trying to configure it."),
                         true, MESSAGE_WARNING, BUTTONS_OK, true);
      warn.run();
      return;
    }

  PolicyParameters& params = policy->get_parameters();
  ConfigurePolicyDialog config_dialog(_("Configuring CPU Policy ") + policy->get_name(), 
                                      get_initial_window(), policy->get_description(), params);

  if(config_dialog.run() == RESPONSE_OK)
    {
      sim.stop();
      sim.get_history().reset();
    } 
}


void
GuiBuilder::populate_with_cpu_policies(Gtk::Menu& menu)
{
  using namespace Gtk;

  // NOTE: Please note that this code relies on the fact that a given
  // policy never "disappears" at runtime. A *GatekeeperObserver should 
  // be needed to avoid dangling pointers if this behaviour changes.

  typedef std::vector<CPUPolicyManager*> Managers;
  typedef std::vector<CPUPolicy*>        Policies;

  RadioButtonGroup group;

  CPUPoliciesGatekeeper& pgk = CPUPoliciesGatekeeper::get_instance();
  const Managers& managers = pgk.get_registered();

  for(Iseq<Managers::const_iterator> m_it = iseq(managers); m_it; ++m_it)
    {
      const Policies& policies = (*m_it)->get_avail_policies();
      for(Iseq<Policies::const_iterator> p_it = iseq(policies); p_it; ++p_it)
        {
          RadioMenuItem& menuitem = *manage(new RadioMenuItem(group, (*p_it)->get_name()));
          menuitem.signal_activate().connect(sigc::bind(sigc::mem_fun(*this, &GuiBuilder::on_selected_cpu_policy), *p_it));
          menu.append(menuitem);
          menuitem.show();
        }
    }

  // Activate the first policy available if possible
  Menu::MenuList& items = menu.items();
  if(!items.empty())
    menu.activate_item(items.front());
}


void
GuiBuilder::on_selected_cpu_policy(CPUPolicy* pol)
{
  using namespace Gtk;

  Statusbar* sbar;
  _refXml->get_widget("MainStatusBar", sbar);

  try
    {
      Simulation::get_instance().set_policy(pol);
      if(pol != NULL) {
        sbar->push(_("Selected CPU policy ") + pol->get_name());
        return;
      }
    }
  catch(CPUPolicyException& e)
    {
      Simulation::get_instance().set_policy(NULL);
      MessageDialog error(get_initial_window(),
                          Glib::ustring(_("<b>Impossible to select this CPU Policy.</b>\n")) + e.what(),
                          true, MESSAGE_ERROR, BUTTONS_OK, true);
      error.run();
    }  
  
  // If we got here, no policy is selected.
  sbar->push(_("No CPU policy selected. Please select one."));
}


void
GuiBuilder::populate_with_resource_policies(Gtk::Menu& menu)
{
  using namespace Gtk;

  // NOTE: Please note that this code relies on the fact that a given
  // policy never "disappears" at runtime. A *GatekeeperObserver should 
  // be needed to avoid dangling pointers if this behaviour changes.

  typedef std::vector<ResourcePolicyManager*> Managers;
  typedef std::vector<ResourcePolicy*>        Policies;

  RadioButtonGroup group;

  ResourcePoliciesGatekeeper& pgk = ResourcePoliciesGatekeeper::get_instance();
  const Managers& managers = pgk.get_registered();

  for(Iseq<Managers::const_iterator> m_it = iseq(managers); m_it; ++m_it)
    {
      const Policies& policies = (*m_it)->get_avail_policies();
      for(Iseq<Policies::const_iterator> p_it = iseq(policies); p_it; ++p_it)
        {
          RadioMenuItem& menuitem = *manage(new RadioMenuItem(group, (*p_it)->get_name()));
          menuitem.signal_activate().connect(sigc::bind(sigc::mem_fun(*this, &GuiBuilder::on_selected_resource_policy), *p_it));
          menu.append(menuitem);
          menuitem.show();
        }
    }

  // Activate the first policy available if possible
  Menu::MenuList& items = menu.items();
  if(!items.empty())
    menu.activate_item(items.front());
}


void
GuiBuilder::on_selected_resource_policy(ResourcePolicy* pol)
{
  using namespace Gtk;
  Simulation::get_instance().set_resource_policy(pol);
  Statusbar* sbar;
  _refXml->get_widget("MainStatusBar", sbar);
  if(pol == NULL)
    sbar->push(_("Current resource policy deselected."));
  else
    sbar->push(_("Selected resource policy ") + pol->get_name());
}

void
GuiBuilder::on_toggle_simulation_mode()
{
  Gtk::CheckMenuItem* continuous_mode;
  _refXml->get_widget("MenuItem.Simulation.ContinuousMode", continuous_mode);
  if(continuous_mode->get_active() == true)
    Simulation::get_instance().set_mode(Simulation::mode_continuous);
  else
    Simulation::get_instance().set_mode(Simulation::mode_step_by_step);
}


GuiBuilder::GuiBuilder(const std::string& gladefile)
  : _refXml(Xml::create(gladefile)), _controller(Simulation::get_instance(), _refXml),
    _holt_container(Simulation::get_instance()),
    _show_threads(true)
{
  using namespace Gtk;

  // ---------------- Menu items ------------------

  // Note: the Play, Pause and Stop menu items are already managed by sgpem::SimulationController.

  // file new dialog
  MenuItem* file_new = NULL;
  _refXml->get_widget("MenuItem.File.New", file_new);
  file_new->signal_activate().connect(sigc::mem_fun(*this, &GuiBuilder::on_file_new_activate));

  // file open dialog
  MenuItem* file_open = NULL;
  _refXml->get_widget("MenuItem.File.Open", file_open);
  file_open->signal_activate().connect(sigc::mem_fun(*this, &GuiBuilder::on_file_open_activate));

  // file save dialog
  MenuItem* file_save = NULL;
  _refXml->get_widget("MenuItem.File.Save", file_save);
  file_save->signal_activate().connect(sigc::mem_fun(*this, &GuiBuilder::on_file_save_activate));

  // file save dialog
  MenuItem* file_saveas = NULL;
  _refXml->get_widget("MenuItem.File.SaveAs", file_saveas);
  file_saveas->signal_activate().connect(sigc::mem_fun(*this, &GuiBuilder::on_file_saveas_activate));

  MenuItem* file_quit = NULL;
  _refXml->get_widget("MenuItem.File.Quit", file_quit);
  file_quit->signal_activate().connect(sigc::ptr_fun(&Main::quit));

	// preferences dialog
  MenuItem* edit_preferences;
  _refXml->get_widget("MenuItem.Edit.Preferences", edit_preferences);
  edit_preferences->signal_activate().connect(sigc::mem_fun(*this, &GuiBuilder::on_edit_preferences_activate));

  // JumpTo
  MenuItem* simulation_jump_to = NULL;
  _refXml->get_widget("MenuItem.Simulation.JumpTo", simulation_jump_to);
  simulation_jump_to->signal_activate().connect(sigc::mem_fun(*this, &GuiBuilder::on_simulation_jump_to_activate));

  // enable/disable show threads on widgets
  MenuItem* show_threads;
  _refXml->get_widget("MenuItem.View.ShowThreads", show_threads);
  show_threads->signal_activate().connect(sigc::mem_fun(*this, &GuiBuilder::on_view_show_threads_activate));

  // show/hide holt graph window
  MenuItem* show_holt_graph;
  _refXml->get_widget("MenuItem.View.ShowHoltGraph", show_holt_graph);
  show_holt_graph->signal_activate().connect(sigc::mem_fun(*this, &GuiBuilder::on_view_show_holt_graph_activate));



  CheckMenuItem* continuous_mode;
  _refXml->get_widget("MenuItem.Simulation.ContinuousMode", continuous_mode);
  continuous_mode->signal_toggled().connect(sigc::mem_fun(*this, &GuiBuilder::on_toggle_simulation_mode));


  // ---------------- Toolbar buttons ------------------

  // Note: the Play, Pause and Stop buttons are already managed by sgpem::SimulationController.

  // Open file
  ToolButton* toolb_open;
  _refXml->get_widget("ToolBar.Open", toolb_open);
  toolb_open->signal_clicked().connect(sigc::mem_fun(*this, &GuiBuilder::on_file_open_activate));

  // Save file
  ToolButton* toolb_save;
  _refXml->get_widget("ToolBar.Save", toolb_save);
  toolb_save->signal_clicked().connect(sigc::mem_fun(*this, &GuiBuilder::on_file_save_activate));

  // Configure CPU Policy
  MenuToolButton* cpu_policies_tb_menu;
  _refXml->get_widget("ToolBar.PolicySelector", cpu_policies_tb_menu);
  cpu_policies_tb_menu->signal_clicked().connect(sigc::mem_fun(*this, &GuiBuilder::on_configure_cpu_policy));
  cpu_policies_tb_menu->set_menu(*manage(new Menu()));
  populate_with_cpu_policies(*cpu_policies_tb_menu->get_menu());
  
  // Configure Resource Policy
  MenuToolButton* res_policies_tb_menu;
  _refXml->get_widget("ToolBar.ResourceScheduling", res_policies_tb_menu);
  res_policies_tb_menu->signal_clicked().connect(sigc::mem_fun(*this, &GuiBuilder::on_configure_resource_policy));
  res_policies_tb_menu->set_menu(*manage(new Menu()));
  populate_with_resource_policies(*res_policies_tb_menu->get_menu());

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

  // About dialog
  MenuItem* help_about;
  _refXml->get_widget("MenuItem.Help.About", help_about);
  AboutDialog* about_dialog = NULL;
  _refXml->get_widget("AboutDialog", about_dialog);
  help_about->signal_activate().connect(sigc::mem_fun(*about_dialog, &Window::show));
  about_dialog->set_wrap_license(true);
  about_dialog->set_logo(Gdk::Pixbuf::create_from_file(GLADEDIR "/logo.png"));


  // Insert the schedulables TreeView custom widget
  ScrolledWindow* schedulables_sw = NULL;
  _refXml->get_widget("SchedulablesScrolledWindow", schedulables_sw);
  SchedulablesTreeWidget* scheds_tree = manage(new SchedulablesTreeWidget());
  schedulables_sw->add(*scheds_tree);
  // we have to remember to manually show custom added widgets:
  scheds_tree->show();

  // Resources ListView widget
  ResourcesWidget* resources_widget = NULL;
  _refXml->get_widget_derived("Resources.Tree", resources_widget);
  resources_widget->show();


  // Main simulation widget  
  ScrolledWindow* simulation_window = NULL;
  _refXml->get_widget("SimulationScrolledWindow", simulation_window);
  //SimulationWidget& simulation_widget = *manage(new SimulationWidget(Simulation::get_instance()));
  _simulation_widget = manage(new SimulationWidget(Simulation::get_instance()));
  simulation_window->add(*_simulation_widget);
  _simulation_widget->set_show_threads(_show_threads);
  _simulation_widget->show();
  

  // ReadyQueue custom label widget
  ReadyQueueWidget& rq_widget = *manage(new ReadyQueueWidget(Simulation::get_instance().get_history()));
  HBox* rqb; 
  _refXml->get_widget("ReadyQueueBox", rqb);
  rqb->pack_start(rq_widget);
  rq_widget.show();


  // HoltGraph container window 
  _holt_container.set_keep_above();
  _holt_container.get_holt_widget().set_show_threads(_show_threads);
  _holt_container.show();
  
    // Statistics container window 
  _statistics_container.set_keep_above();  
  _statistics_container.show();
}


GuiBuilder::~GuiBuilder()
{}


Gtk::Window&
GuiBuilder::get_initial_window() const
{
  Gtk::Window* main_window = NULL;
  _refXml->get_widget("MainWindow", main_window);
  return *main_window;
}


void
GuiBuilder::open_file(const std::string& filename)
{
  // FIXME: to be written.
  // Debug line (erase me when done):
  std::cout << _("Filename to open: ") << filename << std::endl;
}

