// src/simulation_widget.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 3 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, see http://www.gnu.org/licenses/.


#include "gettext.h"
#include "simulation_widget.hh"
#include "cairo_elements.hh"

#include "sgpemv2/history.hh"
#include "sgpemv2/process.hh"
#include "sgpemv2/thread.hh"
#include "sgpemv2/simulation.hh"
#include "sgpemv2/string_utils.hh"

#include <glibmm/refptr.h>
#include <gtkmm/action.h>
#include <gtkmm/actiongroup.h>
#include <gtkmm/menu.h>
#include <gtkmm/uimanager.h>

#include <cairomm/matrix.h>

#include <cassert>

#ifndef NDEBUG
#include <iostream>
#endif

using namespace sgpem;
using namespace Gtk;
using namespace Glib;

SimulationWidget::SimulationWidget(Simulation& simulation)
    : Glib::ObjectBase("sgpem_SimulationWidget"),
    HistoryObserver(),
    CairoWidget(),
    _simulation(&simulation),
    _x_unit(10), _y_unit(10),
    _n_proc(0), _n_thr(0)
{  
  simulation.get_history().attach(*this);

  // define top margin in y units
  _yu_top_margin = 1.0;

  // define left margin in x units
  _xu_left_margin = 1.0;

  // define graph left margin in x units
  _xu_left_graph_margin = 11.0;

  // define process bar spacing in y units
  _yu_process_bar_spacing = 1.0;

  // define process bar height in y units
  _yu_process_bar_height = 2.0;

  // define thread bar spacing in y units
  _yu_thread_bar_spacing = 0.5;

  // define thread bar height in y units
  _yu_thread_bar_height = 1.0;

  _partial_redraw = false;
  _last_drawn = 0;  
}


SimulationWidget::~SimulationWidget()
{
  // Unregister this HistoryObserver:
  _simulation->get_history().detach(*this);
}

void
SimulationWidget::on_realize()
{
   CairoWidget::on_realize();

  // Register for receiving click events:
  RefPtr<Gdk::Window> gdk_win = get_window();
  gdk_win->set_events(gdk_win->get_events() | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
}

void
SimulationWidget::update(const History&  /* changed_history */  )
{
  // Force redraw and resize
  _last_drawn = 0;
  _partial_redraw = false;
  resize_redraw();
}


bool
SimulationWidget::get_show_threads() const
{
  return _show_threads;
}

bool
SimulationWidget::set_show_threads(bool show)
{
  bool old_show = _show_threads;
  _show_threads = show;
  return old_show;
}


void
SimulationWidget::draw_widget(Cairo::RefPtr<Cairo::Context>& ctx)
{
  if(_n_proc<1) // nothing to draw
  {
    ctx->move_to(2.0*_x_unit, 2.0*_y_unit);
    ctx->show_text(_("Nothing to see here... add some processes! Right-click on the Schedulables view in this window."));
    return;
  }

  draw_names(ctx);
  draw_grid(ctx);
  draw_bars(ctx);
}


void
SimulationWidget::draw_names(Cairo::RefPtr<Cairo::Context>& ctx)
{
  // show processes (and thread) names...

  // useful constants
  const History& hist              = _simulation->get_history();
  const double top_margin          = _yu_top_margin * _y_unit;
  const double left_margin         = _x_unit;
  const double left_graph_margin   = _xu_left_graph_margin * _x_unit;
  const double process_bar_spacing = _yu_process_bar_spacing * _y_unit;
  const double process_bar_height  = _yu_process_bar_height * _y_unit;
  const double process_height      = (_yu_process_bar_height + 2*_yu_process_bar_spacing) * _y_unit;
  const double thread_height       = (2.0*_yu_thread_bar_spacing+_yu_thread_bar_height) * _y_unit;
  // const double graph_height        = _n_proc * process_height + (_show_threads?_n_thr:0) * thread_height;

  // set a rectangular clip region to cut long names
  // - set the rectangle
  ctx->rectangle(0, top_margin,
    left_graph_margin - left_margin,
    _n_proc*process_height + _n_thr*thread_height);
  // - set the clip region
  ctx->clip(); 

  // text start position: margin + half of text height
  double ypos = top_margin + _y_unit / 2.0;  
  const Environment::Processes& processes = hist.get_last_environment().get_processes();
  Environment::Processes::const_iterator proc_iter = processes.begin();
  // - draw all processes names
  while(proc_iter!=processes.end())
  {
    // take pointer to current process
    Process* p = (*proc_iter);
    proc_iter++;

    // move to bar center adding a white row before
    ypos += process_bar_spacing + process_bar_height/2.0; 
    ctx->move_to(left_margin, ypos);
        // show process name
    ctx->show_text(p->get_name());

    // calc position to next row skipping height of process bar
    ypos += process_bar_height/2.0; 

    // - draw (if needed) threads names
    if(_show_threads)
    {
      const std::vector<Thread*>& tvect = p->get_threads();
      std::vector<Thread*>::const_iterator thr_iter = tvect.begin();
      while(thr_iter!=tvect.end())
      {
        Thread* t = (*thr_iter);
        thr_iter++;
        // move to bar center adding white space as needed
        ypos += thread_height/2.0;
        ctx->move_to(left_margin+_x_unit, ypos);
        // show thread name
        ctx->show_text(t->get_name());

        // calc position to next thread bar begin
        ypos += thread_height/2.0; // height of thread bar
      } // ~  while(thr_iter!=tvect.end())
    } // ~  if(_show_threads)

    // calc position to next process bar begin
    ypos += process_bar_spacing; // white row after text
  } // ~  while(proc_iter!=processes.end())

  // remove clip region
  ctx->reset_clip();
}

void
SimulationWidget::draw_grid(Cairo::RefPtr<Cairo::Context>& ctx)
{
  const History& hist              = _simulation->get_history();

  const unsigned int hist_front    = hist.get_front() == 0 ? 0 : hist.get_front() - 1;
  const double top_margin          = _yu_top_margin * _y_unit;
  const double left_graph_margin   = _xu_left_graph_margin * _x_unit;
  const double process_height      = (_yu_process_bar_height + 2*_yu_process_bar_spacing) * _y_unit;
  const double thread_height       = (2.0*_yu_thread_bar_spacing+_yu_thread_bar_height) * _y_unit;
  const double graph_width         = (2.0 + hist_front) * _x_unit;
  const double graph_height        = _n_proc * process_height + (_show_threads?_n_thr:0) * thread_height;

  // draw graph grid
  double ypos = top_margin;
  const Environment::Processes& processes = hist.get_last_environment().get_processes();
  Environment::Processes::const_iterator proc_iter = processes.begin();
  
  // - draw all HOR lines
  while(proc_iter!=processes.end())
  {
    Process* p = (*proc_iter);
    proc_iter++;
    // draw one HOR line per process
    ctx->move_to(left_graph_margin, ypos);
    ctx->rel_line_to(graph_width, 0);

    // calc next line position
    ypos += process_height; // skip a process heigh
    if(_show_threads)
    {
      int nt = p->get_threads().size();
      // calc next line position (if show thread enabled)
      ypos += thread_height * nt; 
    } // ~  if(_show_threads)
  } // ~  while(proc_iter!=processes.end())

  // draw last HOR line
  ctx->move_to(left_graph_margin, ypos);
  ctx->rel_line_to(graph_width, 0);

  // - draw left VER line
  ctx->move_to(left_graph_margin, top_margin);
  ctx->rel_line_to(0, graph_height);

  // right close the graph only if simulation stopped
  if(_simulation->get_state()==Simulation::state_stopped)
  {
    // - draw right VER line
    ctx->move_to(left_graph_margin + graph_width, top_margin);
    ctx->rel_line_to(0, graph_height);
  }

  // trace lines on output context
  ctx->stroke();

  // Draw a grey vertical line every fifth step
  ctx->save();
  ctx->set_line_width(0.125 * ctx->get_line_width());
  ctx->set_source_rgb(0.3, 0.3, 0.3);
  for(double step = _x_unit; step < graph_width - _x_unit; step += 5 * _x_unit)
  {
    ctx->begin_new_path();
    ctx->move_to(left_graph_margin + step, top_margin);
    ctx->rel_line_to(0, graph_height);
    ctx->stroke(); 
  }
  ctx->restore();


  // draw and write time line
  ctx->save();
  ctx->set_source_rgb(0, 0, 0);
  ctx->set_line_width(0.25*ctx->get_line_width());

  // "T" label
  ctx->move_to(left_graph_margin,
      top_margin + graph_height + 2.0 * _y_unit);
  ctx->show_text("T");

  // ruler drawing cycle
  for(unsigned int t=0; t<=hist_front; t++)
  {
    // tick
    ctx->move_to(left_graph_margin + (t+1)*_x_unit,
      top_margin + graph_height);
    ctx->rel_line_to(0, 0.5 * _y_unit);

    // value
    Glib::ustring val;
    to_string<int>(t, val);
    ctx->move_to(left_graph_margin + (t+1)*_x_unit,
      top_margin + graph_height + 2.0 * _y_unit);
    ctx->show_text(val);
  } // ~  for(int t=0; t<=pos; t++)
  ctx->stroke();
  ctx->restore();
}

void
SimulationWidget::draw_bars(Cairo::RefPtr<Cairo::Context>& ctx)
{
  // useful constants
  const History& hist              = _simulation->get_history();
  unsigned int hist_front          = hist.get_front();
  const double top_margin          = _yu_top_margin * _y_unit;
  const double left_graph_margin   = _xu_left_graph_margin * _x_unit;
  const double process_bar_spacing = _yu_process_bar_spacing * _y_unit;
  const double process_bar_height  = _yu_process_bar_height * _y_unit;
  const double thread_bar_spacing  = _yu_thread_bar_spacing * _y_unit;
  const double thread_bar_height   = _yu_thread_bar_height * _y_unit;
  const double thread_height       = (2.0*_yu_thread_bar_spacing+_yu_thread_bar_height) * _y_unit;
  unsigned int from_time;

  // need to use gradients then make they
  make_gradients();

  if(_partial_redraw && _last_drawn>0)
  {
    from_time = _last_drawn;
  }
  else
  {
    from_time = 1;
  }


#ifndef NDEBUG
  std::cout << " SimulationWidget::draw_bars from:" << from_time << "  to:" << hist_front << std::endl;
#endif

  // time cycle
  for(unsigned int t=from_time; t<=hist_front; t++)
  {  
    // We draw the interval [0, front), entirely, and 
    // the instant "front" as a briefer `pulse'
    float width_percent = 1.0f;
    if(t == hist_front)
      width_percent = 0.1f;

    // draw schedulables bars
    double xpos = left_graph_margin + t * _x_unit;  // left start of first process
    double ypos = top_margin;  // vertical start of first process
    const Environment::Processes& processes = hist.get_environment_at(t).get_processes();
    Environment::Processes::const_iterator proc_iter = processes.begin();

    // - draw each process' bar
    while(proc_iter!=processes.end())
    {
      Process* p = (*proc_iter);
      proc_iter++;
      ypos += process_bar_spacing; // white row before bar

      // show a single process rectangle
      draw_instant_rect(ctx, xpos, ypos,
                        _x_unit * width_percent, process_bar_height, 
                        p->get_state());

      // skip height of process bar
      ypos += process_bar_height;

      // paint threads' bars if needed
      if(_show_threads)
      {
        const std::vector<Thread*>& tvect = p->get_threads();
        std::vector<Thread*>::const_iterator thr_iter = tvect.begin();
        while(thr_iter!=tvect.end())
        {
          Thread* t = (*thr_iter);
          thr_iter++;

          // show a single thread rectangle
          draw_instant_rect(ctx, xpos, ypos + thread_bar_spacing,
                          _x_unit * width_percent, thread_bar_height, t->get_state());

          // skip height of thread bar
          ypos += thread_height; 
        } // ~  while(thr_iter!=tvect.end())
      } // ~  if(_show_threads)
      ypos += process_bar_spacing; // white row after bar
    } // ~  while(proc_iter!=processes.end())
  } // ~  for(int t=0; t<=hist_front; t++)

  _last_drawn = hist_front;
  _partial_redraw = false;
}

// draw single rectangle using gradients
void
SimulationWidget::draw_instant_rect(Cairo::RefPtr<Cairo::Context>& ctx, double x, double y,
                        double w, double h, Schedulable::state state)
{
  using namespace Cairo;

  switch(state)
  {
    case Schedulable::state_running:
      ctx->save();
      ctx->set_source(_running_process_gradient);

      // translate the gradient at desired position
      _running_process_gradient->set_matrix(translation_matrix(0, -y));

      // put the filled rectangle
      ctx->rectangle(x, y, w, h);
      ctx->fill();
      ctx->restore();
      break;

    case Schedulable::state_ready:
      ctx->save();
      ctx->set_source(_ready_process_gradient);

      // translate the gradient at desired position
      _ready_process_gradient->set_matrix(translation_matrix(0, -y));

      // put the filled rectangle
      ctx->rectangle(x, y, w, h);
      ctx->fill();
      ctx->restore();
      break;

    case Schedulable::state_blocked:
      ctx->save();
      ctx->set_source(_blocked_process_gradient);

      // translate the gradient at desired position
      _blocked_process_gradient->set_matrix(translation_matrix(0, -y));

      // put the filled rectangle
      ctx->rectangle(x, y, w, h);
      ctx->fill();
      ctx->restore();
      break;

    case Schedulable::state_future:
      return;  // don't draw
      break;

    case Schedulable::state_terminated:
      return;  // don't draw
      break;
  } // ~  switch(state)
}


void
SimulationWidget::calc_drawing_size(Cairo::RefPtr<Cairo::Context>& ctx, size_t& width, size_t& height)
{
  if(!_simulation)
    return;

  // evaluate number of processes and threads
  count_elements();

  int pos = _simulation->get_history().get_front();

  // units are defined in terms of text dimensions
  Cairo::TextExtents extents;
  Glib::ustring val("999");
  ctx->get_text_extents(val, extents);
  if(_x_unit < extents.width)
    _x_unit = extents.width;
  if(_y_unit < extents.height)
    _y_unit = extents.height;

  // left margin, labels, graph
  width  = (size_t)((_xu_left_graph_margin + 3.0 + pos) * _x_unit);
  // top margin, processes*height and space evaluation
  height = (size_t)((_yu_top_margin +
    (_yu_process_bar_spacing*2.0+_yu_process_bar_height)
    * _n_proc + 3.0) * _y_unit);

  // optional threads*height and space evaluation
  if(_show_threads)
    height += (size_t)
      (_n_thr * (_yu_thread_bar_spacing*2.0+_yu_thread_bar_height) * _y_unit);
}



// evaluate number of processes and threads
void
SimulationWidget::count_elements()
{
  _n_proc = _n_thr = 0;

  const History& hist = _simulation->get_history();
  const Environment& env = hist.get_last_environment();

  // count processes
  const Environment::Processes& pvect = env.get_processes();
  _n_proc = pvect.size();

  // iterate trough processes to compute threads number
  Environment::Processes::const_iterator proc_iter = pvect.begin();
  while(proc_iter!=pvect.end())
  {
    Process* p = (*proc_iter);
    proc_iter++;
    if(_show_threads)
      _n_thr += p->get_threads().size();
  }

}

void
SimulationWidget::make_gradients()
{
  // linear gradients are referred to 0, 0
  // must be translated if used elsewhere
  using Cairo::LinearGradient;

  _ready_process_gradient = LinearGradient::create(0, 0, 0, _yu_process_bar_height * _y_unit);
  // yellow
  _ready_process_gradient->add_color_stop_rgb(0.0, 1.00, 0.7, 0.0);
  _ready_process_gradient->add_color_stop_rgb(0.3, 1.00, 0.9, 0.0);
  _ready_process_gradient->add_color_stop_rgb(1.0, 1.00, 0.7, 0.0);

  _running_process_gradient = LinearGradient::create(0, 0, 0, _yu_process_bar_height * _y_unit);
  // green
  _running_process_gradient->add_color_stop_rgb(0.0, 0.0, 0.8, 0.0);
  _running_process_gradient->add_color_stop_rgb(0.3, 0.0, 1.0, 0.0);
  _running_process_gradient->add_color_stop_rgb(1.0, 0.0, 0.65, 0.0);

  _blocked_process_gradient = LinearGradient::create(0, 0, 0, _yu_process_bar_height * _y_unit);
  // red
  _blocked_process_gradient->add_color_stop_rgb(0.0, 0.85, 0.0, 0.0);
  _blocked_process_gradient->add_color_stop_rgb(0.3, 1.0, 0.5, 0.5);
  _blocked_process_gradient->add_color_stop_rgb(1.0, 0.65, 0.0, 0.0);
}


// brings a popup menu to select scaling
bool 
SimulationWidget::on_button_press_event(GdkEventButton* event)
{
  // activate on right button press
  if((event->type & Gdk::BUTTON_PRESS) == Gdk::BUTTON_PRESS && (event->button == 3))
  {
    Glib::ustring ui_info = 
    "<ui>"
    "  <popup name='PopupMenu'>";

    CairoWidget::scaling_mode scaling = get_scaling_mode();

    RefPtr<ActionGroup> action_group = Gtk::ActionGroup::create();

    // adds action and menu item for "no scaling"
    if(scaling!=CairoWidget::scaling_none)
    {
      action_group->add( Gtk::Action::create("ScalingNone", "_No scaling"),
      sigc::mem_fun(*this, &SimulationWidget::_on_no_scaling) );
      ui_info += "    <menuitem action='ScalingNone'/>";
    }

    // adds action and menu item for "fit in window"
    if(scaling!=CairoWidget::scaling_min)
    {
      // scaling==scaling_none
      action_group->add( Gtk::Action::create("ScalingFit", "_Fit in window"),
      sigc::mem_fun(*this, &SimulationWidget::_on_fit_scaling) );
      ui_info += "    <menuitem action='ScalingFit'/>";
    }

    // adds action and menu item for "stretch in window"
    if(scaling!=CairoWidget::scaling_all)
    {
      // scaling==scaling_none
      action_group->add( Gtk::Action::create("ScalingStretch", "_Stretch in window"),
      sigc::mem_fun(*this, &SimulationWidget::_on_stretch_scaling) );
      ui_info += "    <menuitem action='ScalingStretch'/>";
    }
 
    RefPtr<UIManager> UIManager = Gtk::UIManager::create();
    UIManager->insert_action_group(action_group);

    ui_info += 
    "  </popup>"
    "</ui>";

    // insert menu
    UIManager->add_ui_from_string(ui_info);
    Gtk::Menu* menu = dynamic_cast<Gtk::Menu*>(UIManager->get_widget("/PopupMenu"));

    // run the menu
    menu->popup(event->button, event->time);
    return true; //It has been handled.
  }
  else
    return false;
}

void
SimulationWidget::_on_no_scaling()
{
#ifndef NDEBUG
  std::cout << "before SimulationWidget::_on_no_scaling mode=" << get_scaling_mode() << std::endl;
#endif
  set_scaling_mode(CairoWidget::scaling_none);
#ifndef NDEBUG
  std::cout << "after SimulationWidget::_on_no_scaling mode=" << get_scaling_mode() << std::endl;
#endif
  resize_redraw();
  // set_size_request (20, 20); // force container redimensioning 

}

void
SimulationWidget::_on_fit_scaling()
{
#ifndef NDEBUG
  std::cout << "before SimulationWidget::_on_fit_scaling mode=" << get_scaling_mode() << std::endl;
#endif
  set_scaling_mode(CairoWidget::scaling_min);
#ifndef NDEBUG
  std::cout << "after SimulationWidget::_on_fit_scaling mode=" << get_scaling_mode() << std::endl;
#endif
  resize_redraw();
  // set_size_request (20, 20); // force container redimensioning 
}

void
SimulationWidget::_on_stretch_scaling()
{
#ifndef NDEBUG
  std::cout << "before SimulationWidget::_on_stretch_scaling mode=" << get_scaling_mode() << std::endl;
#endif
  set_scaling_mode(CairoWidget::scaling_all);
#ifndef NDEBUG
  std::cout << "after SimulationWidget::_on_stretch_scaling mode=" << get_scaling_mode() << std::endl;
#endif
  resize_redraw();
  // set_size_request (20, 20); // force container redimensioning 
}


