/*
 *
 * Copyright (c) 2007
 * Adrian Michel
 * http://www.tradery.com
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.  Adrian Michel makes no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
*/


package com.tradery.contentmodel;

import com.tradery.contract.Contract;

import java.util.Enumeration;
import java.util.Vector;
import java.util.Dictionary;
import java.util.Hashtable;

class StateMachineAll implements AbstractStateMachine
{
  private Dictionary _d = null;
  private int _index;


  private class XX
  {
    private String _symbol = null;
    private boolean _optional;
    private int _index;

    XX( String symbol, boolean optional, int index )
    {
      if( Contract.REQUIRE )
      {
        Contract.require( symbol != null );
      }

      _symbol = symbol;
      _optional = optional;
      _index = index;
    }

    String getSymbol()
    {
      return _symbol;
    }

    boolean isOptional()
    {
      return _optional;
    }

    int getIndex()
    {
      return _index;
    }
  }

  static class State implements AbstractState
  {
    private Vector _v = null;
    private int _size;
    private StateMachineAll _sm = null;

    private class YY
    {
      private boolean _wasEntered;
      private boolean _optional;
      private XX _xx = null;

      YY( boolean optional, XX xx )
      {
        _wasEntered = false;
        _optional = optional;
        _xx = xx;
      }

      boolean wasEntered()
      {
        return _wasEntered;
      }

      boolean isOptional()
      {
        return _optional;
      }

      void setEntered()
      {
        _wasEntered = true;
      }

      String getSymbol()
      {
        return _xx.getSymbol();
      }

    }

    State( StateMachineAll sm, int size, Enumeration e )
    {
      if( Contract.REQUIRE )
      {
        Contract.require( sm != null, "The StateMachineAll associated with a state object cannot be null" );
        Contract.require( size >= 0, "The number of symbols in a state machine cannot be negative" );
        Contract.require( e != null, "Enumeration of XX elements cannot be null in StateMachineAll.State" );
      }

      _size = size;
      _v = new Vector( size );
      _v.setSize( size );
      int k = _v.size();
        // init the vector
      while( e.hasMoreElements() )
      {
        XX xx = (XX)e.nextElement();

        _v.setElementAt( new YY( xx.isOptional(), xx ), xx.getIndex() );
      }

      _sm = sm;
    }

    public Enumeration getValidTransitions()
    {
      return new ValidTransitionsEnumeration( _v );
    }

    private class ValidTransitionsEnumeration implements Enumeration
    {
      int n;
      Vector _v = null;

      ValidTransitionsEnumeration( Vector v )
      {
        if( Contract.REQUIRE )
          Contract.require( v != null, "vector cannot be null in ValidTransitionsEnumeration constructor" );

        n = 0;
        _v = v;
        nextIndex();
      }

      private void nextIndex()
      {
        for(; n < _v.size(); n++ )
        {
          YY yy = (YY)_v.elementAt( n );
          if( !yy.wasEntered() )
            break;
        }
      }

      public boolean hasMoreElements()
      {
        return n < _v.size();
      }

      public Object nextElement()
      {
        Object o = null;
        if( hasMoreElements() )
        {
          YY yy = (YY)_v.elementAt( n++ );
          o = yy.getSymbol();
        }
        nextIndex();
        return o;
      }
    }



    public boolean doTransition( String symbol )
    {
      if( Contract.REQUIRE )
      {
        Contract.require( symbol != null, "The symbol cannot be null in StateMachineAll.State.doTransition()" );
      }

      int n = _sm.getIndex( symbol );

      if( Contract.CHECK )
      {
        Contract.check( n < _v.size() );
      }

      if( n < 0 )
          // the symbol is not in the language described by the state machine
        return false;
      else
      {
        YY yy = (YY)_v.elementAt( n );
        if( yy.wasEntered() )
        {
            // cannot add the same symbol twice - only supports optional
          return false;
        }
        else
        {
            // mark the symbol as true (existent)
          yy.setEntered();
          return true;
        }
      }
    }

    public boolean isValidTransition( String symbol )
    {
      if( Contract.REQUIRE )
        Contract.require( symbol != null, "The symbol cannot be null in StateMachineAll.State.doTransition()" );

      int n = _sm.getIndex( symbol );

      if( Contract.CHECK )
        Contract.check( n < _v.size() );

      if( n < 0 )
          // the symbol is not in the language described by the state machine
        return false;
      else
      {
        return !( (YY)_v.elementAt( n ) ).wasEntered();
      }
    }


    public boolean canTerminate()
    {
      for( int n = 0; n < _v.size(); n++ )
      {
        YY yy = (YY)_v.elementAt( n );

        if( !yy.wasEntered() && !yy.isOptional() )
          return false;
      }

      return true;
    }
  }

  StateMachineAll()
  {
    _d = new Hashtable();
    _index = 0;
  }

  boolean addTransition( String symbol, boolean optional )
  {
    if( Contract.REQUIRE )
      Contract.require( symbol != null, "Cannot add a null symbol to the StateMachineAll" );

      // return false if the symbol already there
    return _d.put( symbol, new XX( symbol, optional, _index++ ) ) == null;
  }
  // returns a new state object set to the start state
  public AbstractState getInitialState()
  {
    return new State( this, _d.size(), _d.elements() );
  }

  public void dump()
  {
    /** @todo implement the state machine all dump */
  }

  public void setFinalStates( Object o )
  {
  }

  int getIndex( String symbol )
  {
    XX xx = (XX)_d.get( symbol );
    if( xx == null )
      // symobl not in language
      return -1;
    else
      return xx.getIndex();
  }
}