content: package rabbitescape.engine.solution;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Iterator;
  import java.util.List;
  
  import rabbitescape.engine.World.CompletionState;
  import rabbitescape.engine.solution.SolutionExceptions.UnexpectedState;
  
  public class SolutionInterpreter
  {
      private static final int maxUntils = 1000;
  
      private final Iterator<SolutionCommand> commandIt;
      private int commandIndex;
  
      private final WonAssertCreator wonAssert;
      private WaitNextable wait;
      private CompletionState untilState;
      private SolutionCommand command;
      private int untilCount;
      public boolean emptySteps = false;
  
      /**
       * appendWon defaults to true if you omit it.
       */
      public SolutionInterpreter( Solution solution )
      {
          this( solution, true );
      }
  
      /**
       * @param solution
       * @param appendWon add a "WON" assertion at the end, if the last command
       *        is not already a single state assertion.
       */
      public SolutionInterpreter( Solution solution, boolean appendWon )
      {
          this.commandIt = Arrays.asList( solution.commands ).iterator();
          this.commandIndex = 0;
  
          this.wonAssert = new WonAssertCreator( appendWon );
          this.wait = null;
          this.untilState = null;
          this.command = null;
          this.untilCount = -1;
      }
  
      public static SolutionInterpreter getNothingPlaying()
      {
          SolutionInterpreter nothingPlaying =
              new SolutionInterpreter( SolutionParser.parse( "1" ) );
          nothingPlaying.emptySteps = true;
          return nothingPlaying;
      }
  
      public SolutionTimeStep next( CompletionState worldState )
      {
          if ( emptySteps )
          {
              return new SolutionTimeStep(
                  ++commandIndex, new TimeStepAction[] {} );
          }
          if ( wait != null )
          {
              SolutionTimeStep ret = wait.next();
              if ( ret  != null )
              {
                  return ret;
              }
          }
  
          if ( untilState != null )
          {
              if ( worldState != CompletionState.RUNNING )
              {
                  SolutionTimeStep ret = new SolutionTimeStep(
                      commandIndex, new AssertStateAction( untilState ) );
                  untilState = null;
                  untilCount = -1;
                  wonAssert.done = true;
                  return ret;
              }
              else if ( untilCount > maxUntils )
              {
                  throw new SolutionExceptions.UntilActionNeverEnded(
                      untilState );
              }
              else
              {
                  ++untilCount;
                  return new SolutionTimeStep( commandIndex );
              }
          }
  
          return nextTimeStep( worldState );
      }
  
      private SolutionTimeStep nextTimeStep( final CompletionState worldState )
      {
          wait = null;
  
          if ( ! commandIt.hasNext() )
          {
              ++commandIndex;
              return wonAssert.create( commandIndex, command );
          }
  
          command = commandIt.next();
          ++commandIndex;
  
          // Work around need for final variable
          class StepsToWait { int value = 0; };
          final StepsToWait stepsToWait = new StepsToWait();
  
          final List<TimeStepAction> tsActions = new ArrayList<TimeStepAction>();
  
          for ( CommandAction action : command.actions )
          {
              action.typeSwitch( new CommandActionTypeSwitch()
              {
                  @Override
                  public void caseWaitAction( WaitAction waitAction )
                  {
                      stepsToWait.value  += waitAction.steps;
                  }
  
                  @Override
                  public void caseUntilAction( UntilAction untilAction )
                  {
                      untilState = untilAction.targetState;
                      untilCount = 0;
  
                      if ( worldState != CompletionState.RUNNING )
                      {
                          tsActions.add( new AssertStateAction( untilState ) );
                          wonAssert.done = true;
                          untilState = null;
                      }
                  }
  
                  @Override
                  public void caseSelectAction( SelectAction action )
                  {
                      tsActions.add( action );
                  }
  
                  @Override
                  public void casePlaceTokenAction( PlaceTokenAction action )
                  {
                      tsActions.add( action );
                  }
  
                  @Override
                  public void caseAssertStateAction( AssertStateAction action )
                      throws UnexpectedState
                  {
                      tsActions.add( action );
                  }
                  // Curse you, Java, for making me this way
              } );
          }
  
          if ( stepsToWait.value > 1 )
          {
              wait = new WaitNextable( commandIndex, stepsToWait.value - 1 );
          }
  
          return new SolutionTimeStep(
              commandIndex,
              tsActions.toArray( new TimeStepAction[ tsActions.size() ] )
          );
      }
  
      private static class WaitNextable
      {
          private final int commandIndex;
          private int stepsLeft;
  
          public WaitNextable( int commandIndex, int steps )
          {
              this.commandIndex = commandIndex;
              this.stepsLeft = steps;
          }
  
          public SolutionTimeStep next()
          {
              if ( stepsLeft > 0 )
              {
                  --stepsLeft;
                  return new SolutionTimeStep( commandIndex );
              }
              return null;
          }
      }
  
      private static class WonAssertCreator
      {
          private final boolean appendWon;
          public boolean done;
  
          public WonAssertCreator( boolean appendWon )
          {
              this.appendWon = appendWon;
              this.done = false;
          }
  
          public SolutionTimeStep create(
              int commandIndex, SolutionCommand lastCommand )
          {
              if ( done || !appendWon )
              {
                  return null;
              }
  
              if (
                     lastCommand != null
                  && lastCommand.actions.length == 1
                  && lastCommand.actions[0] instanceof AssertStateAction
              )
              {
                  return null;
              }
  
              done = true;
  
              return new SolutionTimeStep(
                  commandIndex, new AssertStateAction( CompletionState.WON ) );
          }
      }
  }
  