<?php

/**
 * Output converter for FSM from GraphViz
 *
 * This class converts a loaded FSM to a GraphViz DOT file.
 *
 * @copyright  (c) 2007-2012 Ouest Systèmes Informatiques
 * @author     Frederic G. MARAND
 * @license    CeCILL 2.0
 * @link       http://wiki.audean.com/fsm/fsm
 * @since      FSM 1.6
 */

namespace OSInet\Finite_State_Machine;

class Grapher {
  const STATE_SHAPE = 'octagon';
  const EVENT_SHAPE = 'egg';

  /**
   * The FSM being plotted.
   *
   * @var Machine
   */
  public $m;

  /**
   * The Image_Graphviz canvas.
   *
   * @var Image_Graphviz
   */
  public $g;

  /**
   * Debug level. >= 0.
   *
   * @var int
   */
  public $debug = NULL;
  /**
   * A hash of state name to GraphViz name.
   *
   * @var array
   */
  public $states = array();

  /**
   * @param Machine $m
   */
  public function __construct(Machine $m, $values = array()) {
    $this->m = $m;

    $default_values = array(
      'debug' => 0,
    );
    $values = array_intersect_key($values, $default_values);
    $values += $default_values;
    foreach ($values as $key => $value) {
      $this->$key = $value;
    }

    // PEAR is not currently PSR-0 compliant.
    // As a consequence, strict PSR-0 autoloading will not work for it.
    require 'Image/GraphViz.php';
    $this->g = new \Image_Graphviz();
  }

  public function render() {
    $i = 0;
    $g = &$this->g;
    // First make sure all states have labeled nodes, so that forward
    // declarations of states occurring in transitions find the proper state id.
    foreach ($this->m->fTransitions as $state => $events) {
      $label = $state;
      $state .= "_$i";
      $this->states[$label] = $state;
      $g->addNode($state, array(
        'label' => $label,
        'shape' => self::STATE_SHAPE,
      ));
      $i++;
    }
    foreach ($this->m->fTransitions as $state => $events) {
      $state_id = $this->states[$state];
      foreach ($events as $event => $outcomes) {
        if ($this->debug) {
          echo "State $state Event $event " . count($outcomes) . " outcomes\n";
          print_r($outcomes);
        }
        if (0 && count($outcomes) == 1) {
          $outcome = reset($outcomes);
          // Only one outcome: we do not care for the result.
          echo "State $state event $event, outcome:"; print_r($outcome); echo "\n";
          $edge = array($state_id => $this->states[$outcome[0]]);
          $edge_label = "\n:" . $outcome[1];
          $edge_attributes = empty($outcome[1])
            ? array()
            : array('label' => $edge_label);
          $g->addEdge($edge, $edge_attributes);
        }
        else {
          $event_label = $event;
          $event_id = "{$state_id}_$event";
          $g->addNode($event_id, array(
            'label' => $event_label,
            'shape' => self::EVENT_SHAPE,
          ));
          $g->addEdge(array($state_id => $event_id));
          foreach ($outcomes as $result => $next) {
            list($next_state, $next_action) = $next;
            $edge = array($event_id => $this->states[$next_state])  ;
            $edge_label_parts = array();
            if (!empty($result)) {
              $edge_label_parts[] = "[$result]";
            }
            if (!empty($next_action)) {
              $edge_label_parts[] = ":$next_action";
            }
            if (empty($edge_label_parts)) {
              echo "No label!\n";
              $g->addEdge($edge);
            }
            else {
              $edge_label = implode("\n", $edge_label_parts);
              $edge_attributes = array(
                'label' => $edge_label,
              );
              $g->addEdge($edge, $edge_attributes);
            }
          }
        }
      }
    }

    return $g->parse();
  }
}