/**
 *  Набор вспомогательных классов для ExtJS
 */
 
Ext.namespace('Ext.ux.Utils');

/**
 *  Очередь сообщений
 */
Ext.ux.Utils.EventQueue = function(handler, scope)
{
  if (!handler) {
    throw 'Handler is required.';
  }
  this.handler = handler;
  this.scope = scope || window;
  this.queue = [];
  this.is_processing = false;
  
  this.postEvent = function(event, data)
  {
    data = data || null;
    this.queue.push({event: event, data: data});
    if (!this.is_processing) {
      this.process();
    }
  }
  
  this.process = function()
  {
    while (this.queue.length > 0) {
      this.is_processing = true;
      var event_data = this.queue.shift();
      this.handler.call(this.scope, event_data.event, event_data.data);
    }
    this.is_processing = false;
  }
}

/**
 *  Конечный автомат Мили
 *  
 *  Формат таблицы переходов / выходов:
 *  {
 *    'state_1' : {
 *      'event_1' : [
 *        {
 *          p|predicate: function,    // transition predicate, optional, default to true.
 *          a|action: function|array, // transition action, optional, default to Ext.emptyFn.
 *          s|state: 'state_x',       // new state, optional, default to current state
 *          scope: object             // predicate and action scope, optional, default to 
 *                                    // trans_table_scope or window
 *        }
 *      ]
 *    },
 *
 *    'state_2' : {
 *      ...
 *    }
 *    ...
 *  }
 *
 *  @param  string|number initial_state начальное состояние
 *  @param  object  trans_table таблица переходов / выходов
 *  @param  trans_table_scope объект для которого будут вызваны методы из таблицы переходов / выходов
 */
Ext.ux.Utils.FSA = function(initial_state, trans_table, trans_table_scope)
{
  this.current_state = initial_state;
  this.trans_table = trans_table || {};
  this.trans_table_scope = trans_table_scope || window;
  Ext.ux.Utils.FSA.superclass.constructor.call(this, this.processEvent, this);
}

Ext.extend(Ext.ux.Utils.FSA, Ext.ux.Utils.EventQueue, {

  current_state : null,
  trans_table : null,  
  trans_table_scope : null,
  
  state : function()
  {
    return this.current_state;
  },
  
  processEvent : function(event, data)
  {
    var transitions = this.currentStateEventTransitions(event);
    if (!transitions) {
      throw "State '" + this.current_state + "' has no transition for event '" + event + "'.";
    }
    for (var i = 0; i < transitions.length; i++) {
      var transition = transitions[i];

      var predicate = transition.predicate || transition.p || true;
      var action = transition.action || transition.a || Ext.emptyFn;
      var new_state = transition.state || transition.s || this.current_state;
      var scope = transition.scope || this.trans_table_scope;
      
      if (predicate === true || predicate.call(scope, data, event, this)) {
        if (Ext.type(action) == 'array') {
          for (var j = 0; j < action.length; j++) {
            action[j].call(scope, data, event, this);
          }
        }
        else {
          action.call(scope, data, event, this);
        }
        this.current_state = new_state;
        return;  
      }
    }
    
    throw "State '" + this.current_state + "' has no transition for event '" + event + "' in current context";
  },
  
  currentStateEventTransitions : function(event)
  {
    return this.trans_table[this.current_state] ? 
      this.trans_table[this.current_state][event] || false
      :
      false;
  }
});