// src/holt_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 "cairo_elements.hh"

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

#include "holt_widget.hh"

#include "sgpemv2/templates/sequences.tcc"

#include <cairomm/cairomm.h>

#include <cmath>
#include <cassert>

#ifndef NDEBUG
#include <iostream>
#endif

#include <sstream>

using namespace sgpem;
using namespace std;

#ifndef M_SQRT1_2
  #define M_SQRT1_2 0.70710678118654752440
#endif

#ifndef M_PI
  #define M_PI 3.14159265358979323846
#endif


HoltNode::HoltNode(Vec2 pt)
: _radius(20)
{
  _pos = pt;
}


HoltNode::~HoltNode()
{
}


Vec2 HoltNode::set_position(Vec2 pt)
{
  Vec2 pt_old = _pos;
  _pos = pt;
  return pt_old;
}

Vec2 HoltNode::get_position()
{
  return _pos;
}

double HoltNode::set_radius(double radius)
{
  double old_rad = _radius;
  _radius = radius;
  return old_rad;
}

double HoltNode::get_radius()
{
  return _radius;
}


HoltResource::HoltResource(const Resource& resource, resource_key_t resource_key, Vec2 pt)
: HoltNode(pt),
  _resource(&resource),
  _resource_key(resource_key),
  _used_places(0)
{
}

HoltResource::~HoltResource()
{
}

void HoltResource::draw(Cairo::RefPtr<Cairo::Context>& cr)
{
  static const Color white(1, 1, 1);

  static const float x_percent = 3.5f / 4;
  static const float y_percent = 4.5f / 5;

  CairoElements ce(cr);

  // outline
  cr->set_source_rgb (0, 0, 0);

  // draw rectangle
  Rectangle area(_pos.real() - _radius,  _pos.imag() - _radius, 2*_radius, 2*_radius);
  ce.draw_3dcube(area, white, x_percent, y_percent);
  cr->save();

  // clip text outside region
  cr->rectangle(_pos.real() - _radius,
                _pos.imag() + _radius * (1 - 2.0 * y_percent),
                2 * _radius * x_percent,
                2 * _radius * y_percent);
  cr->clip();

  // draw text
  Cairo::TextExtents extents;
  cr->get_text_extents(_resource->get_name(), extents);
  double xpos = _pos.real() + _radius * (x_percent-1) - extents.width/2.0;
  double ypos = _pos.imag() + _radius * (1 - y_percent); // + extents.height/2;

  // left aligned if too large
  if(xpos<_pos.real() - _radius)
    xpos = _pos.real() - _radius;
  cr->move_to(xpos, ypos );
  cr->show_text(_resource->get_name());

  // show used/total places
  Glib::ustring used;
  to_string<int>((int)_used_places, used);
  Glib::ustring total;
  to_string<int>((int)_resource->get_places(), total);

  Glib::ustring msg = used + "/" + total;
  cr->get_text_extents(msg, extents);
  xpos = _pos.real() + _radius * (x_percent-1) - extents.width/2.0;
  ypos = _pos.imag() + _radius * (1 - y_percent) + extents.height*2;
  cr->move_to(xpos, ypos);
  cr->show_text(msg);

  // stroke all
  cr->stroke();

  cr->restore();
}

Vec2 HoltResource::get_intersection_to(Vec2 pt)
{
  Vec2 final = _pos;
  Vec2 segment = pt - _pos;
  double len = std::abs(segment);
  if(len>0)
  {
    // segment to unary modulus vector (versor)
    segment /= len;
    if(abs(segment.real())>=M_SQRT1_2)
    {
      // direction East(>0) or West(<0)
      segment *= _radius/abs(segment.real());
    }
    else
    {
      // direction North(<0) or South(>0)
      segment *= _radius/abs(segment.imag());
    }
    final = _pos + segment;
  }
  return final;
}



void
HoltResource::allocate(unsigned int places)
{
  _used_places += places;
}

unsigned int
HoltResource::get_allocated()
{
  return _used_places;
}




HoltSchedulable::HoltSchedulable(const Schedulable& schedulable, Vec2 pt)
: HoltNode(pt)
{
  _schedulable = &schedulable;
}

HoltSchedulable::~HoltSchedulable()
{
}

void HoltSchedulable::draw(Cairo::RefPtr<Cairo::Context>& cr)
{
  static const Color red(1, 0, 0);
  static const Color yellow(1, 0.8, 0);
  static const Color green(0, 1, 0);

  cr->save(); // save context state

  const Point center(_pos.real(), _pos.imag());
  const Color* color = &red;

  // draw circle
  CairoElements cel(cr);
  // filling
  switch(_schedulable->get_state())
  {
    case Schedulable::state_running:
      color = &green;
      break;
    case Schedulable::state_ready:
      color = &yellow;
      break;
    case Schedulable::state_blocked:
      color = &red;
      break;
    case Schedulable::state_future:
    case Schedulable::state_terminated:
      // should never get here
      assert((_schedulable->get_state() & (Schedulable::state_future | Schedulable::state_terminated)) == 0);
  }

  cel.draw_3dsphere(center, _radius, *color);

  // clip text outside region
  cr->arc(_pos.real(), _pos.imag(), _radius, 0, 2*M_PI);
  cr->clip();

  // draw text
  Cairo::TextExtents extents;
  cr->get_text_extents(_schedulable->get_name(), extents);
  double text_width = extents.width;
  if(text_width>_radius*2.0) text_width=_radius*2.0;
  cr->move_to(_pos.real() - text_width/2, _pos.imag() + extents.height/2);
  cr->show_text(_schedulable->get_name());

  // stroke all
  cr->stroke();

  cr->restore(); // restore context state
}

Vec2 HoltSchedulable::get_intersection_to(Vec2 pt)
{
  // schedulables ar circles
  // intersection is a radius from center then...
  Vec2 final = _pos;

  // take segment
  Vec2 segment = pt - _pos;
  double len = std::abs(segment);

  // and normalize to radius
  if(len>0)
  {
    segment *= _radius/len;
    final = _pos + segment;
  }
  return final;
}



HoltRequest::HoltRequest(HoltSchedulable& hp, HoltResource& hr, Request::state state, unsigned int places)
: _hp(&hp),
  _hr(&hr),
  _state(state)
{
  hr.allocate(places);
}

HoltRequest::~HoltRequest()
{
}

void HoltRequest::draw(Cairo::RefPtr<Cairo::Context>& cr)
{
  Vec2 resource = _hp->get_intersection_to(_hr->get_position());
  Vec2 schedulable = _hr->get_intersection_to(_hp->get_position());

  cr->save();

  switch(_state)
  {
    case Request::state_unallocable:
      // red
      cr->set_source_rgb(1, 0, 0);
      arrow(cr, schedulable, resource);
      break;

    case Request::state_allocated:
      // green
      cr->set_source_rgb(0, 1, 0);
      arrow(cr, resource, schedulable);
      break;

    case Request::state_future:
      break;

    case Request::state_exhausted:
      break;

    case Request::state_allocable:
      // yellow
      cr->set_source_rgb(1, 0.7, 0);
      arrow(cr, schedulable, resource);
      break;
  }

  // stroke all and restore status
  cr->stroke();
  cr->restore();
}

void HoltRequest::arrow(Cairo::RefPtr<Cairo::Context>& cr, Vec2 first, Vec2 second)
{
  // option to draw the line a little translated
  // this allow to draw two counterflow arrows
  // without overlap
  bool traslate = true;
  double line_width = cr->get_line_width();
  if(traslate)
  {
    // if needed calc parallel versor of arrow
    Vec2 direction = second - first;
    direction /= std::abs(direction); // set unary modulus

    // and rotate it 90 degrees
    Vec2 dir_rotation(0.0, 1.0);
    direction *= dir_rotation;

    // translation modulus equals line width
    direction *= line_width;
    first += direction;
    second += direction;
  }

  // draw main line
  cr->move_to(second.real(), second.imag());
  cr->line_to(first.real(), first.imag());

  // some calculation to draw arrowpoint...
  // take a short line parallel to main line
  Vec2 arrowside = second - first;
  arrowside *= 5.0*line_width/std::abs(arrowside);
  // make a rotation component
  Vec2 deviation(5.0, 1.0); deviation /= std::abs(deviation);
  // make left and right sides rotating that first short line
  Vec2 side1 = arrowside * deviation;
  Vec2 side2 = arrowside * conj(deviation);
  // draw one side
  cr->move_to(first.real(), first.imag());
  cr->line_to(first.real()+side1.real(), first.imag()+side1.imag());
  // draw the other side
  cr->move_to(first.real(), first.imag());
  cr->line_to(first.real()+side2.real(), first.imag()+side2.imag());
}








HoltWidget::HoltWidget(Simulation& simulation)
    : Glib::ObjectBase("sgpem_HoltWidget"),
    SimulationObserver(),
    HistoryObserver(),
    CairoWidget(),
    _simulation(&simulation),
    _radius(20),
    _n_proc(0),
    _n_res(0),
    _arrange_mode(arrange_horizontal),
    _show_threads(false)

{
  // Register this SimulationObserver:
  _simulation->attach(*this);

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


HoltWidget::~HoltWidget()
{
  // Free allocated memory
  const HoltResources& const_holt_resources = _holt_resources;
  for(Iseq<HoltResources::const_iterator> it = iseq(const_holt_resources); it; ++it)
    delete it->second;

  for_each_in(_holt_schedulables, [] (auto *p)  { delete p; });
  for_each_in(_holt_requests, [] (auto *p)  { delete p; });


  // Unregister this HistoryObserver:
  _simulation->get_history().detach(*this);

  // Unregister this SimulationObserver:
  _simulation->detach(*this);
}



double
HoltWidget::get_radius()
{
  return _arrange_mode;
}

double
HoltWidget::set_radius(double radius)
{
  double old_radius = _radius;
  if(radius>0)
    _radius = radius;
  return old_radius;
}

HoltWidget::arrange_mode
HoltWidget::get_arrange_mode()
{
  return _arrange_mode;
}

HoltWidget::arrange_mode
HoltWidget::set_arrange_mode(arrange_mode mode)
{
  arrange_mode old_mode = _arrange_mode;
  _arrange_mode = mode;
  arrange();
  return old_mode;
}

void
HoltWidget::arrange()
{
#ifndef NDEBUG
  std::cout << "HoltWidget::arrange" << endl;
#endif

  // Differents graph's dispositions are made easy by
  // usual complex number algebra:
  //
  // v1 += v2 - translation
  // v1 *= d  - scaling
  // v1 *= v2 - rotating if abs(v2)==1.0
  // v1 *= v2 - rotating and scaling by modulus if abs(v2)!=1.0
  //
  // where v1, v2 are Vec2 (complex<double>), d is double
  //

  // to draw a linear disposition the algorithm starts with
  // first desired position and sums an inc vetor each step
  //
  // to draw a circular disposition the center and radius are taken
  // then every position is calculated summing to center a radius (inc)
  // vector. The inc vector is rotated each step
  //

  Vec2 pos; // position of current HoltNode derived object (all)
  Vec2 inc; // increment (vertical and horizontal) or radius (circular)
  Vec2 cen; // center of rotation (circular only)
  Vec2 mul; // rotation multiplier (circular only)

  // vectors building
  if(_arrange_mode==arrange_horizontal)
  {
    // first position
    pos = Vec2(2*_radius, 2*_radius);
    // increment
    inc = Vec2(3*_radius, 0);
  }
  else if(_arrange_mode==arrange_vertical)
  {
    // first position
    pos = Vec2(2*_radius, 2*_radius);
    // increment
    inc = Vec2(0, 3*_radius);
  }
  else // _arrange_mode==arrange_circular
  {
    int sx = _draw_w; // get_width();
    int sy = _draw_h; // get_height();
    int nelem = _holt_resources.size()+_holt_schedulables.size();
    // take minimum between x and y
    /*
    if(sx<=sy)
      inc = Vec2((sx/2-2*_radius), 0);
    else
      inc = Vec2((sy/2-2*_radius), 0);
    */
    if(nelem>1)
    {
      // take minimum between x and y
      if(sx<=sy)
        inc = Vec2(-(sx/2-2*_radius), 0);
      else
        inc = Vec2(-(sy/2-2*_radius), 0);
    }
    else
    {
      // zero or one object always at center
      inc = Vec2(0, 0);
    }


    // calc rotation angle
    if(nelem>0)
      mul = Vec2(cos(2*M_PI/(double)nelem), sin(2*M_PI/(double)nelem));
    else
      mul = Vec2(0,0);

    cen = Vec2(2*_radius+std::abs(inc), 2*_radius+std::abs(inc));
    // cen = Vec2(sx/2.0, sy/2.0);
    pos = inc + cen;
  }

  // positions all resources
  HoltResources::const_iterator riter = _holt_resources.begin();
  while(riter!=_holt_resources.end())
  {
    HoltResource* hr = (*riter).second;
    hr->set_position(pos);
    hr->set_radius(_radius);

    // calc next position
    if(_arrange_mode!=arrange_circular)
    {
      pos += inc;
    }
    else // _arrange_mode==arrange_circular
    {
      inc *= mul;
      pos = cen + inc;
    }
    riter++;
  }

  // positions all schedulables
  // if linear disposition then must reevaluate start and increment
  // when circular continue with previous values to end circle
  if(_arrange_mode==arrange_horizontal)
  {
    pos = Vec2(2*_radius, 8*_radius);
    inc = Vec2(3*_radius, 0);
  }
  else if(_arrange_mode==arrange_vertical)
  {
    pos = Vec2(8*_radius, 2*_radius);
    inc = Vec2(0, 3*_radius);
  }
  typedef HoltProcesses::const_iterator holt_proc_iterator;
  holt_proc_iterator piter = _holt_schedulables.begin();
  while(piter!=_holt_schedulables.end())
  {
    HoltSchedulable* hp = (*piter);
    hp->set_position(pos);
    hp->set_radius(_radius);

    if(_arrange_mode!=arrange_circular)
    {
      pos += inc;
    }
    else // _arrange_mode==arrange_circular
    {
      inc *= mul;
      pos = cen + inc;
    }
    piter++;
  }

}


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

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





void
HoltWidget::update(const Simulation& /*changed_simulation*/)
{
}


void
HoltWidget::update(const History& /*changed_history*/)
{
#ifndef NDEBUG
  std::cout << "HoltWidget::update - History" << endl;
#endif
  // take new data
  acquire();

  // Force redraw
  resize_redraw();
}




void
HoltWidget::acquire()
{
#ifndef NDEBUG
  std::cout << "HoltWidget::acquire" << endl;
#endif

  // clear all previous collected items
  const HoltResources& const_holt_resources = _holt_resources;
  for(Iseq<HoltResources::const_iterator> it = iseq(const_holt_resources); it; ++it)
    delete it->second;
  _holt_resources.clear();

  for_each_in(_holt_schedulables, [] (auto *p)  { delete p; });
  _holt_schedulables.clear();

  for_each_in(_holt_requests, [] (auto *p)  { delete p; });
  _holt_requests.clear();

  _n_res = _n_proc = 0;

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

  // arbitrary position (will be changed by arrange())
  Vec2 pos(2*_radius, 2*_radius);
  const Environment::Resources& rvect = env.get_resources();

  // scan all resources making an HolResource object
  Environment::Resources::const_iterator riter = rvect.begin();
  while(riter!=rvect.end())
  {
    resource_key_t index = (*riter).first;
    Resource* r = (*riter).second;

    // create HolResource object and insert it in container
    HoltResource *hr = new HoltResource(*r, index, pos);
    HoltResources::iterator temp = _holt_resources.insert(std::pair<resource_key_t,HoltResource*>(index, hr)).first;

    // count resources
    _n_res++;

    // skip to next
    riter++;

    // calc next position
    pos += Vec2(4*_radius, 0);
  }

  pos = Vec2(2*_radius, 8*_radius);

    // iter trough processes
  const Environment::Processes& pvect = env.get_processes();
  Environment::Processes::const_iterator proc_iter = pvect.begin();
  while(proc_iter!=pvect.end())
  {
    Process* p = (*proc_iter);
    proc_iter++;

    // create a new HoltScedulable per process
    // if running or ready or blocked and
    // show threads is disabled
    Schedulable::state proc_state = p->get_state();
    if(proc_state==Schedulable::state_running
      || proc_state==Schedulable::state_ready
      || proc_state==Schedulable::state_blocked )
    {
      HoltSchedulable *hp;
      if(!_show_threads)
      {
        hp = new HoltSchedulable(*p, pos);
        _holt_schedulables.push_back(hp);
        pos += Vec2(4*_radius, 0);
        _n_proc++;
      }

      // iter trough threads, requests, subrequests
      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++;

        // create a new HoltScedulable per process
        // if running or ready or blocked and
        // show threads is disabled
        Schedulable::state thr_state = t->get_state();
        if(thr_state==Schedulable::state_running
          || thr_state==Schedulable::state_ready
          || thr_state==Schedulable::state_blocked )
        {

          if(_show_threads)
          {
            hp = new HoltSchedulable(*t, pos);
            _holt_schedulables.push_back(hp);
            pos += Vec2(4*_radius, 0);
            _n_proc++;
          }
          // iter trough requests
          const std::vector<Request*>& rvect = t->get_requests();
          std::vector<Request*>::const_iterator req_iter = rvect.begin();
          while(req_iter!=rvect.end())
          {
            Request* r = (*req_iter);
            req_iter++;
            // os << "      request arrival_time: " << r->get_instant() << endl;

            // iter trough subrequests
            const std::vector<SubRequest*>& srvect = r->get_subrequests();
            std::vector<SubRequest*>::const_iterator subr_iter = srvect.begin();
            while(subr_iter!=srvect.end())
            {

              SubRequest* sr = (*subr_iter);
              subr_iter++;

              // create a new HoltRequest per subrequest
              // if unallocable or allocated or allocable
              // tieing it with current process or thread
              Request::state subr_state = sr->get_state();
              if(subr_state==Request::state_unallocable
                || subr_state==Request::state_allocated
                || subr_state==Request::state_allocable )
              {
                Environment::Resources::const_iterator pos = env.get_resources().find(sr->get_resource_key());
                HoltResources::const_iterator hpos = _holt_resources.find(sr->get_resource_key());
                if (pos != env.get_resources().end() && hpos!=_holt_resources.end())
                {
                  // associates process (or thread) with resource)
                  unsigned int nplaces = 0;
                  if(sr->get_state()==Request::state_allocated)
                    nplaces++;
                  HoltRequest *hreq = new HoltRequest(*hp, *(hpos->second), sr->get_state(), nplaces);
                  _holt_requests.push_back(hreq);
                }
              } // ~ if(subr_state==Request::state_unallocable ... etc
            } // ~ while(subr_iter!=srvect.end())
          } // ~ while(req_iter!=rvect.end())
        } // ~ if(thr_state==Schedulable::state_running ...
      } // ~ while(thr_iter!=tvect.end())
    } // ~ if(proc_state==Schedulable::state_running ...etc

  }

}




void
HoltWidget::draw_widget(Cairo::RefPtr<Cairo::Context>& ctx)
{
#ifndef NDEBUG
  std::cout << "HoltWidget::draw_widget" << endl;
#endif

  // dispose objects
  arrange();

  // draw text: T = n as title
  if(_draw_w>0)
  {
    const History& hist = _simulation->get_history();
    const unsigned int hist_front = hist.get_front() == 0 ? 0 : hist.get_front() - 1;
    Cairo::TextExtents extents;
    stringstream ss;
    ss << "T = " << hist_front;
    ctx->get_text_extents(ss.str(), extents);
    ctx->move_to(_draw_w/2 - extents.width/2.0, extents.height*2.0);
    ctx->show_text(ss.str());
    ctx->stroke();
  }

  // draw all resources
  typedef HoltResources::const_iterator holt_res_iterator;
  holt_res_iterator riter = _holt_resources.begin();
  while(riter!=_holt_resources.end())
  {
    HoltResource* hr = (*riter).second;
    hr->draw(ctx);
    riter++;
  }

  // draw all schedulables
  typedef HoltProcesses::const_iterator holt_proc_iterator;
  holt_proc_iterator piter = _holt_schedulables.begin();
  while(piter!=_holt_schedulables.end())
  {
    HoltSchedulable* hp = (*piter);
    hp->draw(ctx);
    piter++;
  }

  // draw all arrows
  typedef HoltRequests::const_iterator holt_requ_iterator;
  holt_requ_iterator reqiter = _holt_requests.begin();
  while(reqiter!=_holt_requests.end())
  {
    HoltRequest* hreq = (*reqiter);
    hreq->draw(ctx);
    reqiter++;
  }

}


void
HoltWidget::calc_drawing_size(Cairo::RefPtr<Cairo::Context>& ctx, size_t& width, size_t& height)
{
#ifndef NDEBUG
  cout << "Holt widget BEFORE calc_drawing_size width=" << width << " height=" << height << endl;
#endif

  // the disposition of objects take care that:
  // - an object has a 2.0*_radius diameter
  // - a space _radius wide is required around objects
  //
  // this is the reason because the expression 4 * _radius
  // recurs many times in the code below
  //
  int max   = _n_proc;
  if(_n_res>_n_proc)
    max = _n_res;
  Cairo::TextExtents extents;

  static const Glib::ustring val("Process 999   Resource 999");
  ctx->get_text_extents(val, extents);
  _radius = extents.width/4.0;

  switch(_arrange_mode)
  {
    case arrange_horizontal:
      // max number of items times the space required by one
      width  = (size_t) (max * 4 * _radius);
      // aspace of 4 * _radius between rows
      height = (size_t) (10 * _radius);
      break;

    case arrange_vertical:
      // aspace of 4 * _radius between columns
      width  = (size_t) (10 * _radius);
      // max number of items times the space required by one
      height = (size_t) (max * 4 * _radius);
      break;

    case arrange_circular:
      // lilltle more complex diameter calculation
      // the circle is divided in tot sectors
      int tot = (_n_proc + _n_res);
      // each sector is alpha wide
      double alpha = 0;
      if(tot>0)
        alpha = M_PI / tot;
      // each element is placed on vertex of a tot sides polygon
      // side length
      double side = 2 * sin(alpha/2);
      // calc of expected limiting circle diameter
      double diam = 4 * _radius;
      if(side>0)
      {
        diam = 4 * _radius / side;
      }
      if(diam<4 * _radius)
        diam = 4 * _radius;
      width  = height = (size_t) (diam + 4 * _radius);
      break;
  }
}

/*
void
HoltWidget::calc_widget_size(size_t& width, size_t& height)
{
  cout << "Holt widget BEFORE calc_widget_size width=" << width << " height=" << height << endl;
  width = height = 20; // default minimum size
}
*/
