content: package rabbitescape.engine.solution;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import static rabbitescape.engine.util.Util.*;
  
  import rabbitescape.engine.Token.Type;
  import rabbitescape.engine.World.CompletionState;
  import rabbitescape.engine.util.Util.Function;
  
  public class SolutionParser
  {
      public static final String COMMAND_DELIMITER = ";";
      public static final String ACTION_DELIMITER = "&";
      private static final Pattern WAIT_REGEX = Pattern.compile( "\\d+" );
      private static final Pattern UNTIL_REGEX =
          Pattern.compile( "until:([A-Z]+)" );
  
      private static final Pattern PLACE_TOKEN_REGEX = Pattern.compile(
          "\\((\\d+),(\\d+)\\)" );
  
      private static final List<String> COMPLETION_STATES =
          toStringList( CompletionState.values() );
  
      private static final List<String> TOKEN_TYPES =
          toStringList( Type.values() );
  
      public static Solution parse( String solution )
      {
          String[] stringCommands = split( solution, COMMAND_DELIMITER );
  
          List<SolutionCommand> commands = new ArrayList<>();
  
          for ( int i = 0; i < stringCommands.length; i++ )
          {
              commands.add( parseCommand( stringCommands[i] ) );
          }
  
          return new Solution(
              commands.toArray( new SolutionCommand[ commands.size() ] ) );
      }
  
      public static SolutionCommand parseCommand( String commandString )
      {
          ArrayList<CommandAction> actions = new ArrayList<CommandAction>();
  
          String[] actionStrings = split( commandString, ACTION_DELIMITER );
  
          for ( int j = 0; j < actionStrings.length; j++ )
          {
              if ( !actionStrings[j].equals( "" ) )
              {
                  actions.add( makeAction( actionStrings[j] ) );
              }
          }
  
          return new SolutionCommand(
              actions.toArray( new CommandAction[ actions.size() ] ) );
      }
  
      public static String serialise( Solution solution )
      {
          return join( ";", map( serialiseCommand(), solution.commands ) );
      }
  
      // ---
  
      private static CommandAction makeAction( String actionString )
      {
          try
          {
              return doMakeAction( actionString );
          }
          catch ( NumberFormatException e )
          {
              throw new InvalidAction( e, actionString );
          }
      }
  
      private static CommandAction doMakeAction( String actionString )
      throws NumberFormatException, InvalidAction
      {
          Matcher untilMatcher = UNTIL_REGEX.matcher( actionString );
          if ( COMPLETION_STATES.contains( actionString ) )
          {
              return new AssertStateAction(
                  CompletionState.valueOf( actionString ) );
          }
          else if ( untilMatcher.matches() )
          {
              CompletionState state =
                  CompletionState.valueOf( untilMatcher.group( 1 ) );
              return new UntilAction( state );
          }
          else if ( WAIT_REGEX.matcher( actionString ).matches() )
          {
              return new WaitAction( Integer.valueOf( actionString ) );
          }
          else if ( TOKEN_TYPES.contains( actionString ) )
          {
              return new SelectAction( Type.valueOf( actionString ) );
          }
          else
          {
              Matcher m = PLACE_TOKEN_REGEX.matcher( actionString );
              if ( m.matches() )
              {
                  return new PlaceTokenAction(
                      Integer.valueOf( m.group( 1 ) ),
                      Integer.valueOf( m.group( 2 ) ) );
              }
          }
          throw new InvalidAction( actionString );
      }
  
      private static Function<SolutionCommand, String> serialiseCommand()
      {
          return new Function<SolutionCommand, String>()
          {
              @Override
              public String apply( SolutionCommand command )
              {
                  return join( "&", map( serialiseAction(), command.actions ) );
              }
          };
      }
  
      private static Function<CommandAction, String> serialiseAction()
      {
          return new Function<CommandAction, String>()
          {
              @Override
              public String apply( CommandAction action )
              {
                  ActionSerialiser s = new ActionSerialiser();
                  action.typeSwitch( s );
                  return s.ret;
              }
          };
      }
  
      private static class ActionSerialiser implements CommandActionTypeSwitch
      {
          public String ret = null;
  
          @Override
          public void caseWaitAction( WaitAction waitAction )
          {
              if ( waitAction.steps == 1 )
              {
                  ret = "";
              }
              else
              {
                  ret = String.valueOf( waitAction.steps );
              }
          }
  
          @Override
          public void caseSelectAction( SelectAction selectAction )
          {
              ret = selectAction.type.name();
          }
  
          @Override
          public void caseAssertStateAction( AssertStateAction targetStateAction )
          {
              ret = targetStateAction.targetState.name();
          }
  
          @Override
          public void casePlaceTokenAction( PlaceTokenAction placeTokenAction )
          {
              ret = "(" + placeTokenAction.x + "," + placeTokenAction.y + ")";
          }
  
          @Override
          public void caseUntilAction( UntilAction untilAction )
          {
              ret = "until:" + untilAction.targetState.name();
          }
      }
  }
  