// 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 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 "gettext.h"

#include "simulation_widget.hh"

#include "cairo_elements.hh"

#include <sgpemv2/history.hh>
#include <sgpemv2/simulation.hh>
#include <sgpemv2/string_utils.hh>

#include <cassert>

#ifndef NDEBUG
#include <iostream>
#endif

using namespace sgpem;


SimulationWidget::SimulationWidget(Simulation& simulation)
    : Glib::ObjectBase("sgpem_SimulationWidget"),
    SimulationObserver(),
    HistoryObserver(),
    CairoWidget(),
    _simulation(&simulation),
    _x_unit(10), _y_unit(10),
    _n_proc(0), _n_thr(0)
{
  // Register this SimulationObserver:
  _simulation->attach(*this);

  // Register this HistoryObserver:
  _simulation->get_history().attach(*this);

  count_elements();

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

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

    /**
      * left margin in x units
      */
    _xu_left_graph_margin = 11.0;

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

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

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

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



    _ready_process_gradient   = 0;
    _running_process_gradient = 0;
    _blocked_process_gradient = 0;

    _partial_redraw = false;
    _last_drawn = 0;
}


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

void
SimulationWidget::update(const Simulation& /* changed_simulation */ )
{
  // Force redraw
  //count_elements();
  // nedd redraw only last
  _partial_redraw = true;
  // resize_redraw();
}

void
SimulationWidget::update(const History&  changed_history  )
{
  // Force redraw
  _last_drawn = 0;
  _partial_redraw = false;
  // MOVED in calc_drawing_size(...)
  // count_elements();
  resize_redraw();  
}


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

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


void
SimulationWidget::draw_widget(cairo_t* ctx)
{
  if(_n_proc<1) // nothing to draw
  {
    cairo_move_to(ctx, 20.0, 20.0);
    cairo_show_text(ctx, _("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_t* 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
  /*
  cairo_rectangle(ctx, 0, top_margin,
    left_graph_margin - left_margin,
    _n_proc*process_height + _n_thr*thread_height);
  // - set the clip region
  cairo_clip(ctx); 
  */
  // draw schedulables names
  double ypos = top_margin + _y_unit / 2.0;  // margin + half of text height
  const Environment::Processes& processes = hist.get_last_environment().get_processes();
  Environment::Processes::const_iterator proc_iter = processes.begin();
  // - draw processes names
  while(proc_iter!=processes.end())
  {
    Process* p = (*proc_iter);
    proc_iter++;
    ypos += process_bar_spacing + process_bar_height/2.0; // white row before text
    cairo_move_to(ctx, left_margin, ypos);
    cairo_show_text(ctx,p->get_name().c_str());
    ypos += process_bar_height/2.0; // height of process bar
    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++;
        ypos += thread_height/2.0;
        cairo_move_to(ctx, left_margin+_x_unit, ypos);
        cairo_show_text(ctx,t->get_name().c_str());
        ypos += thread_height/2.0; // height of thread bar
      } // ~  while(thr_iter!=tvect.end())
    } // ~  if(_show_threads)
    ypos += process_bar_spacing; // white row after text
  } // ~  while(proc_iter!=processes.end())
  // cairo_reset_clip(ctx); // remove clip region
}

void
SimulationWidget::draw_grid(cairo_t* ctx)
{
  // std::cout << " SimulationWidget::draw_grid  p=" << _n_proc << " t=" << _n_thr << std::endl;
  // useful constants
  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_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_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 (every) )HOR line
    cairo_move_to(ctx, left_graph_margin, ypos);
    cairo_rel_line_to(ctx, 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 thread)
      ypos += thread_height * nt; 
    } // ~  if(_show_threads)
  } // ~  while(proc_iter!=processes.end())
  // draw last HOR line
  cairo_move_to(ctx, left_graph_margin, ypos);
  cairo_rel_line_to(ctx, graph_width, 0);

  // - draw left VER line
  cairo_move_to(ctx, left_graph_margin, top_margin);
  cairo_rel_line_to(ctx, 0, graph_height);

  if(_simulation->get_state()==Simulation::state_stopped)
  {
    // - draw right VER line
    cairo_move_to(ctx, left_graph_margin + graph_width, top_margin);
    cairo_rel_line_to(ctx, 0, graph_height);
    
  }

  cairo_stroke(ctx);

  // Draw a vertical line every fifth step
  cairo_save(ctx);
  cairo_set_line_width(ctx, 0.125 * cairo_get_line_width(ctx));
  cairo_set_source_rgb(ctx, 0.3, 0.3, 0.3);
	for(double step = 2.0 + _x_unit; step < graph_width - _x_unit; step += 5 * _x_unit)
  {
		cairo_new_path(ctx);
    cairo_move_to(ctx, left_graph_margin + step, top_margin);
    cairo_rel_line_to(ctx, 0, graph_height);
    cairo_stroke(ctx); 
  }
  cairo_restore(ctx);


  // draw and write time line
  cairo_save(ctx);
  cairo_set_source_rgb(ctx, 0, 0, 0);
  cairo_set_line_width(ctx, 0.25*cairo_get_line_width(ctx));

  cairo_move_to(ctx, left_graph_margin,
      top_margin + graph_height + 2.0 * _y_unit);
  cairo_show_text(ctx,"T");

  for(unsigned int t=0; t<=hist_front; t++)
  {
    cairo_move_to(ctx, left_graph_margin + (t+1)*_x_unit,
      top_margin + graph_height);
    cairo_rel_line_to(ctx, 0, 0.5 * _y_unit);
    Glib::ustring val;
    to_string<int>(t, val);
    cairo_move_to(ctx, left_graph_margin + (t+1)*_x_unit,
      top_margin + graph_height + 2.0 * _y_unit);
    cairo_show_text(ctx,val.c_str());
  } // ~  for(int t=0; t<=pos; t++)
  cairo_stroke(ctx);
  cairo_restore(ctx);
}

void
SimulationWidget::draw_bars(cairo_t* ctx)
{
  //std::cout << " SimulationWidget::draw_bars " << std::endl;
  // show processes (and thread) bars...

  // 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_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 thread_bar_spacing  = _yu_thread_bar_spacing * _y_unit;
  const double thread_bar_height   = _yu_thread_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;
  unsigned int from_time;
  // 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;


  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

  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 processes names
    while(proc_iter!=processes.end())
    {
      Process* p = (*proc_iter);
      proc_iter++;
      ypos += process_bar_spacing; // white row before bar
  
      draw_instant_rect(ctx, xpos, ypos,
                        _x_unit * width_percent, process_bar_height, 
                        p->get_state());
      ypos += process_bar_height; // height of process bar
      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++;
          draw_instant_rect(ctx, xpos, ypos + thread_bar_spacing,
                          _x_unit * width_percent, thread_bar_height, t->get_state());
          ypos += thread_height; // height of thread bar
        } // ~  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;
}

void
SimulationWidget::draw_instant_rect(cairo_t* ctx, double x, double y,
                        double w, double h, Schedulable::state state)
{
  cairo_matrix_t matrix;
  
  switch(state)
  {
    case Schedulable::state_running:
      // cairo_set_source_rgb(ctx, 0, 1, 0);
      cairo_save(ctx);
      cairo_set_source(ctx, _running_process_gradient);
      // cairo_matrix_init_scale(&matrix, 1.0, y);
      // cairo_matrix_translate(&matrix, 0, -y);
      cairo_matrix_init_translate(&matrix, 0, -y);
      cairo_pattern_set_matrix (_running_process_gradient, &matrix);
      cairo_rectangle(ctx, x, y, w, h);
      cairo_fill(ctx);
      cairo_restore(ctx);
      break;
    
    case Schedulable::state_ready:
      // cairo_set_source_rgb(ctx, 1, 1, 0);
      cairo_save(ctx);
      cairo_set_source(ctx, _ready_process_gradient);
      cairo_matrix_init_translate(&matrix, 0, -y);
      // cairo_matrix_scale(&matrix, 1.0, y);
      cairo_pattern_set_matrix (_ready_process_gradient, &matrix);
      cairo_rectangle(ctx, x, y, w, h);
      cairo_fill(ctx);
      cairo_restore(ctx);
      break;
    
    case Schedulable::state_blocked:
      // cairo_set_source_rgb(ctx, 1, 0, 0);
      cairo_save(ctx);
      cairo_set_source(ctx, _blocked_process_gradient);
      cairo_matrix_init_translate(&matrix, 0, -y);
      cairo_pattern_set_matrix (_blocked_process_gradient, &matrix);
      cairo_rectangle(ctx, x, y, w, h);
      cairo_fill(ctx);
      cairo_restore(ctx);
      break;
    
    case Schedulable::state_future:
      return;  // don't draw
      break;
    case Schedulable::state_terminated:
      return;  // don't draw
      // cairo_set_source_rgb(ctx, 0, 0, 0);
      break;
  } // ~  switch(state)
}


void
SimulationWidget::calc_drawing_size(cairo_t* ctx, size_t& width, size_t& height)
{
  if(!_simulation)
    return;

  count_elements();
  const History& hist = _simulation->get_history();
  // const Environment::Processes& processes = hist.get_last_environment().get_processes();
  int pos = _simulation->get_history().get_front();
  cairo_text_extents_t extents;

  Glib::ustring val("999");
  cairo_text_extents(ctx, val.c_str(), &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, 
  height = (size_t)((_yu_top_margin + (_yu_process_bar_spacing*2.0+_yu_process_bar_height) * _n_proc + 3.0) * _y_unit);
  if(_show_threads)
    height += (size_t) (_n_thr * (_yu_thread_bar_spacing*2.0+_yu_thread_bar_height) * _y_unit);
}




void
SimulationWidget::count_elements()
{
  _n_proc = _n_thr = 0;

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

    // iter trough processes
  const Environment::Processes& pvect = env.get_processes();
  _n_proc = pvect.size();
  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()
{

  _ready_process_gradient   = cairo_pattern_create_linear(0, 0, 0, _yu_process_bar_height * _y_unit);
  // yellow
  cairo_pattern_add_color_stop_rgb(_ready_process_gradient, 0.0, 1.00, 0.7, 0.0);
  cairo_pattern_add_color_stop_rgb(_ready_process_gradient, 0.3, 1.00, 0.9, 0.0);
  cairo_pattern_add_color_stop_rgb(_ready_process_gradient, 1.0, 1.00, 0.7, 0.0);
  
  _running_process_gradient = cairo_pattern_create_linear(0, 0, 0, _yu_process_bar_height * _y_unit);
  // green
  cairo_pattern_add_color_stop_rgb(_running_process_gradient, 0.0, 0.0, 0.8, 0.0);
  cairo_pattern_add_color_stop_rgb(_running_process_gradient, 0.3, 0.0, 1.0, 0.0);
  cairo_pattern_add_color_stop_rgb(_running_process_gradient, 1.0, 0.0, 0.65, 0.0);
  
  _blocked_process_gradient = cairo_pattern_create_linear(0, 0, 0, _yu_process_bar_height * _y_unit);
  // red
  cairo_pattern_add_color_stop_rgb(_blocked_process_gradient, 0.0, 0.85, 0.0, 0.0);
  cairo_pattern_add_color_stop_rgb(_blocked_process_gradient, 0.3, 1.0, 0.5, 0.5);
  cairo_pattern_add_color_stop_rgb(_blocked_process_gradient, 1.0, 0.65, 0.0, 0.0);
}

