<?php
error_reporting(E_ALL);

class FSMException extends Exception {}
/**
 * Класс описывает конечный автомат
 * В качестве действий вызывает внешние функции
 */
class FSM {
	/**
	 * @var array карта переходов
	 */
	private $_arMap;
	/**
	 * @var string исходное состояние
	 */
	private $_sCurrState;
	/**
	 * @var array переход по умолчанию
	 */
	private $_arDefTransition;
	
	/**
	 * Конструктор
	 * @param string $sCurrState исходное состояние
	 */
	public function __construct($sCurrState)
	{
		$this->_arMap = array();
		$this->_arDefTransition = array();
		$this->_sCurrState = $sCurrState;
	}
	/**
	 * Добавляет переход
	 * @param string $sInput вход
	 * @param string $sCurrState текущее состояние
	 * @param string $sAction имя вызываемой фукнции
	 * @param string $sNextState следующее состояние
	 */
	public function AddTransition($sInput, $sCurrState, $sAction, $sNextState)
	{
		$this->_arMap[$sInput . ' ' . $sCurrState] = array($sAction, $sNextState);
	}
	/**
	 * Добавляет набор переходов
	 * @param array $sInput входы
	 * @param string $sCurrState текущее состояние
	 * @param string $sAction имя вызываемой фукнции
	 * @param string $sNextState следующее состояние
	 */
	public function AddTransitions($arInputs, $sCurrState, $sAction, $sNextState)
	{
		foreach ($arInputs as $sInput) {
			$this->AddTransition($sInput, $sCurrState, $sAction, $sNextState);
		}
	}
	/**
	 * Устанавливает переход по умолчанию при известных состояниях
	 * @param string $sCurrState текущее состояние
	 * @param string $sAction имя вызываемой фукнции
	 * @param string $sNextState следующее состояние
	 * 
	 */
	public function AddDefaultTransition($sCurrState, $sAction, $sNextState)
	{
		$this->_arMap['' . $sCurrState] = array($sAction, $sNextState);
	}
	/**
	 * Устанавливет переход по умолчанию
	 */
	public function SetDefaultTransition($sAction, $sNextState)
	{
		$this->_arDefTransition = array($sAction, $sNextState);
	}
	/**
	 * Шаг перехода
	 * @param string $sChar
	 */
	public function GoStep($sChar)
	{
		$this->_Transit($sChar);
	}
	/**
	 * @param string $sState
	 */
	public function SetState($sState)
	{
		$this->_sCurrState = $sState;
	}
	/**
	 * Производит переход из одного состояния в другое,
	 * вызывая соответсвующую пользовательскую функцию
	 * @param string $sInput
	 */
	private function _Transit($sInput)
	{
		if(!empty($this->_arMap[$sInput . ' ' . $this->_sCurrState])) {
			call_user_func($this->_arMap[$sInput . ' ' . $this->_sCurrState][0], $sInput);
			$this->_sCurrState = $this->_arMap[$sInput . ' ' . $this->_sCurrState][1];
		}
		else {
			if(!empty($this->_arMap['' . $this->_sCurrState])) {
				call_user_func($this->_arMap['' . $this->_sCurrState][0], $sInput);
				$this->_sCurrState = $this->_arMap['' . $this->_sCurrState][1];
			} 
			else {
				if(count($this->_arDefTransition) != 2) {
					throw new FSMException(__METHOD__ . '=>Default transition is not set');
				}
				call_user_func($this->_arDefTransition[0], $sInput);
				$this->_sCurrState = $this->_arDefTransition[1];
			}
		}
	}
}


class Kostyl_Bb_Parser {

	private $_fsm;
	private $_fsm2;
	
	public $_a;

	public function __construct()
	{
		$this->_fsm = new Kostyl_Bb_Parser_Fsm('0');
		$this->_fsm->addDefaultTransition('0', $this->_startBb(), '0');
		$this->_fsm2 = new Fsm('0');
		$this->_fsm2->AddDefaultTransition('0', array($this, 'startBb'), '0');
	}
	
	
	private function _extractClosure($name)
	{
		echo phpversion();
		$method = $this->_reflectionClass->getMethod($name);
		var_dump(get_class_methods ($method));
		exit;
		return $method->getClosure();
	}
	
	private function _startBb()
	{	
		$parser = $this;
		$closure = function($char) use ($parser) {
			$a = 1 + 1;
			//$parser->_a = 1 + 1;
			//echo $parser->_a;
		};
		
		return $closure;
	}
	
	public function startBb($char)
	{
		$this->_a = 2 + 2;
		//echo $this->_a;
	}
	
	
	public function parse($text)
	{
		$this->_fsm->doStep('i');
	}
	
	public function parse2($text)
	{
		$this->_fsm2->GoStep('o'); //doStep('i');
	}
	
}

class Kostyl_Bb_Parser_Fsm {
	/**
	 * @var array 
	 */
	private $_map;
	/**
	 * @var string
	 */
	private $_currState;
	/**
	 * @var array
	 */
	private $_defTransition;
	/**
	 * @param string $startState
	 */
	public function __construct($startState)
	{
		$this->_map = array();
		$this->_defTransition = null;
		$this->_currState = $startState;
	}
	/**
	 * @param string $input
	 * @param string $currState
	 * @param string $closure
	 * @param string $nextState
	 */
	public function addTransition($input, $currState, $closure, $nextState)
	{
		$this->_map[$input . ' ' . $currState] = array($closure, $sNextState);
	}
	/**
	 * @param array $inputs
	 * @param string $currState
	 * @param string $closure
	 * @param string $nextState
	 */
	public function addTransitions(array $inputs, $currState, $closure, $nextState)
	{
		foreach ($inputs as $input) {
			$this->addTransition($input, $currState, $closure, $nextState);
		}
	}
	/**
	 * @param string $currState
	 * @param string $closure
	 * @param string $nextState
	 * 
	 */
	public function addDefaultTransition($currState, $closure, $nextState)
	{
		$this->_map['' . $currState] = array($closure, $nextState);
	}
	/**
	 * Устанавливет переход по умолчанию
	 */
	public function setDefaultTransition($closure, $nextState)
	{
		$this->_defTransition = array($closure, $nextState);
	}
	/**
	 * @param string $newState
	 */
	public function setState($newState)
	{
		$this->_currState = $newState;
	}
	/**
	 * @param string $input
	 */
	public function doStep($input)
	{
		$this->_transit($input);
	}
	/**
	 * @param string $input
	 */
	private function _transit($input)
	{	
		if ($transiton = $this->_findTransition($input)) {
			if ($this->_isTransition($transiton)) {
				$transiton[0]($input);
				$this->setState($transiton[1]);
			}
			else {
				throw new Exception('Wront transition by key "' . $firstKey . '"');
			}		
		}
		else {
			throw new Exception('Can not find transition');
		}
	}
	/**
	 * @param string $input
	 * @return array|null
	 */
	private function _findTransition($input)
	{
		$keyStable = $input . ' ' . $this->_currState;
		$keyUnknown = '' . $this->_currState;
		
		if (isset($this->_map[$keyStable])) {
			return $this->_map[$keyStable];
		}
		elseif (isset($this->_map[$keyUnknown])) {
			return $this->_map[$keyUnknown];
		}
		else {
			return $this->_defTransition;
		}
	}
	/**
	 * @return bool
	 */
	private function _isTransition($transition)
	{
		return (is_array($transition) && (count($transition) == 2)); //TODO is_closure $transition[0] && is_state $transition[1] ???
	}
}


$parser = new Kostyl_Bb_Parser();
$time = microtime(true);
$c = 100000;
for ($i = 0 ; $i < $c; $i++) {
	$parser->parse('1');
}
echo 'Closure: ' . (microtime(true) - $time) / $c . '<br>';	

$time = microtime(true);
for ($i = 0 ; $i < $c; $i++) {
	$parser->parse2('2');
}
echo 'Call user func: ' . (microtime(true) - $time) / $c . '<br>';	

