<?php
/**
 *
 * This version relies on OSInet FSM >= 1.5
 *
 * @copyright  (c) 2007 OSI
 * @license    Licensed under the CeCILL 2.0
 * @version    CVS: $Id: Background_Application.php,v 1.1 2007-05-08 21:48:13 marand Exp $
 * @link       http://wiki.audean.com/
 * @since      Not applicable yet
 */

error_reporting(E_ALL|E_STRICT);

/**
 * Base application class for an application able to perform background work
 * based on an OSInet FSM (finite state machine).
 * Concrete implementations should include a constructor defining the
 * backgroundGoals array along the FSM graph.
 */
abstract class Background_Application
  {
  /**
   * Trace operation to stdout
   *
   * @var boolean
   */
  public $backgroundTrace = FALSE ;

  /**
   * This is the FSM sequencing the background operation
   * @var Finite_State_Machine
   */
  public $finiteStateMachine;

  /**
   * event name for background work along the FSM
   * @var string
   */
  public $backgroundPath = NULL;

  /**
   * gtk::idle_add/idle_remove id
   * @var int
   */
  protected $_backgroundId;

  /**
   * path/state pairs
   *
   * These define the various background trajectories available for background
   * work within the FSM graph. In each pair, the "path" is the constant event
   * string applying until a goal is reached, and the "goal" is the terminating
   * state for the background work.
   * Sample:
   * $backgroundGoals = array('up' => 'live', 'down' => 'final');
   *
   * @var array
   */
  public $backgroundGoals = NULL;

  /**
   * Perform background work. Must return TRUE to be invoked again.
   * Do NOT loop inside: this freezes the event loop. Return instead, the
   * function will be invoked again anyway.
   *
   * It has to be public, otherwise gtk can't invoke it after idle_add.
   *
   * Returning false disables background work without removing it.
   *
   * @throws Exception [if background work is requested without a goals array]
   * @return boolean
   */
  public function backgroundDo()
    {
    $ret = TRUE;
    while(Gtk::events_pending())
      Gtk::main_iteration();

    if (!is_array($this->backgroundGoals))
      throw new Exception('Background_Application needs an array of goals to be set before background work can run.');

    $msg = "background work: ";
    $event = $this->backgroundPath;
    if (!array_key_exists($event, $this->backgroundGoals))
      {
      $msg = "Nothing to do for now. Stop idling";
      $ret = FALSE;
      }
    elseif ($this->finiteStateMachine->get_state() != $this->backgroundGoals[$event])
      {
      $msg .= "state " . $this->finiteStateMachine->get_state() . "($event)";
      $result = $this->finiteStateMachine->apply_event($event);
      $msg .= "[$result->fsm_return] => $result->fsm_state";
      if (!empty($result->fsm_action))
        $msg .= " / $result->fsm_action";
      }
    else
      {
      $msg .= "End of scheduled work";
      $this->backgroundStop();
      $ret = FALSE;
      }

    if ($this->backgroundTrace)
      echo $msg . PHP_EOL;

    return $ret;
    }

  /**
   * Activate background processing along an event path
   *
   * @throws Exception [if background work is requested without a goals array]
   * @param string $path
   * @return void
   */
  function backgroundStart($path = NULL)
    {
    if (!is_array($this->backgroundGoals))
      throw new Exception('Background_Application needs an array of goals to be set before background work can be started.');

    $this->backgroundPath = $path;
    $this->_backgroundId = Gtk::idle_add(array($this, 'backgroundDo'));
    }

  /**
   * Stop idle processing and remove idle processor
   * @return void
   */
  function backgroundStop()
    {
    Gtk::idle_remove($this->_backgroundId);
    $this->_backgroundId = NULL;
    }
  }