content: package rabbitescape.engine.solution;
  
  import org.hamcrest.BaseMatcher;
  import org.hamcrest.Description;
  import org.hamcrest.Matcher;
  import org.junit.*;
  
  import rabbitescape.engine.Token;
  import rabbitescape.engine.World;
  
  import static org.junit.Assert.fail;
  import static org.hamcrest.MatcherAssert.*;
  import static org.hamcrest.CoreMatchers.*;
  
  public class TestSolutionParser
  {
      @Test
      public void Empty_string_gives_single_empty_command()
      {
          assertThat(
              SolutionParser.parse( "" ),
              equalTo(
                  new Solution( new SolutionCommand() )
              )
          );
      }
  
      @Test
      public void Single_status_gives_single_validation_action()
      {
          assertThat(
              SolutionParser.parse( "WON" ),
              equalTo(
                  new Solution(
                      new SolutionCommand(
                          new AssertStateAction( World.CompletionState.WON ) )
                  )
              )
          );
      }
  
      @Test
      public void Trailing_semicolon_means_empty_command_at_end()
      {
          assertThat(
              SolutionParser.parse( "WON;" ),
              equalTo(
                  new Solution(
                      new SolutionCommand(
                          new AssertStateAction( World.CompletionState.WON ) ),
                      new SolutionCommand()
                  )
              )
          );
      }
  
      @Test
      public void Leading_semicolon_means_empty_command_at_start()
      {
          assertThat(
              SolutionParser.parse( ";WON" ),
              equalTo(
                  new Solution(
                      new SolutionCommand(),
                      new SolutionCommand(
                          new AssertStateAction( World.CompletionState.WON ) )
                  )
              )
          );
      }
  
      @Test
      public void Lots_of_empty_commands_preserved()
      {
          assertThat(
              SolutionParser.parse( ";;;WON;;;" ),
              equalTo(
                  new Solution(
                      new SolutionCommand(),
                      new SolutionCommand(),
                      new SolutionCommand(),
                      new SolutionCommand(
                          new AssertStateAction( World.CompletionState.WON ) ),
                      new SolutionCommand(),
                      new SolutionCommand(),
                      new SolutionCommand()
                  )
              )
          );
      }
  
      @Test
      public void Multiple_actions_are_found_and_listed()
      {
          assertThat(
              SolutionParser.parse( "1;2;WON" ),
              equalTo(
                  new Solution(
                      new SolutionCommand( new WaitAction( 1 ) ),
                      new SolutionCommand( new WaitAction( 2 ) ),
                      new SolutionCommand(
                          new AssertStateAction( World.CompletionState.WON ) )
                  )
              )
          );
      }
  
      @Test
      public void Simultaneous_actions_are_noted()
      {
          assertThat(
              SolutionParser.parse( "bash&(1,1);WON" ),
              equalTo(
                  new Solution(
                      new SolutionCommand(
                          new SelectAction( Token.Type.bash ),
                          new PlaceTokenAction( 1, 1 ) ),
                      new SolutionCommand(
                          new AssertStateAction( World.CompletionState.WON ) )
                  )
              )
          );
      }
  
      @Test
      public void Nonwait_actions_dont_get_a_wait_appended()
      {
          assertThat(
              SolutionParser.parse( "bridge;LOST" ),
              equalTo(
                  new Solution(
                      new SolutionCommand(
                          new SelectAction( Token.Type.bridge )
                      ),
                      new SolutionCommand(
                          new AssertStateAction( World.CompletionState.LOST ) )
                  )
              )
          );
      }
  
      @Test
      public void Empty_commands_dont_get_a_wait_appended()
      {
          assertThat(
              SolutionParser.parse( ";;" ),
              equalTo(
                  new Solution(
                      new SolutionCommand(),
                      new SolutionCommand(),
                      new SolutionCommand()
                  )
              )
          );
      }
  
      @Test
      public void No_assert_added_at_end()
      {
          assertThat(
              SolutionParser.parse( "bridge;(22,40)" ),
              equalTo(
                  new Solution(
                      new SolutionCommand(
                          new SelectAction( Token.Type.bridge )
                      ),
                      new SolutionCommand(
                          new PlaceTokenAction( 22, 40 )
                      )
                  )
              )
          );
      }
  
      @Test
      public void Can_parse_single_action()
      {
          assertThat(
              SolutionParser.parseCommand( "bash" ),
              equalTo(
                  new SolutionCommand(
                      new SelectAction( Token.Type.bash ) )
              )
          );
      }
  
      @Test
      public void Can_parse_multiple_single_actions()
      {
          assertThat(
              SolutionParser.parseCommand( "bash&(1,2)" ),
              equalTo(
                  new SolutionCommand(
                        new SelectAction( Token.Type.bash )
                      , new PlaceTokenAction( 1, 2 )
                  )
              )
          );
      }
  
      @Test
      public void Unrecognised_ability_is_an_error()
      {
          try
          {
              SolutionParser.parse( "unknown_ability" );
              fail( "Expected an InvalidAction!" );
          }
          catch ( InvalidAction e )
          {
              assertThat( e.action, equalTo( "unknown_ability" ) );
          }
      }
  
      @Test
      public void Unrecognised_state_is_an_error()
      {
          try
          {
              SolutionParser.parse( "1;UNKNOWN_STATE" );
              fail( "Expected an InvalidAction!" );
          }
          catch ( InvalidAction e )
          {
              assertThat( e.action, equalTo( "UNKNOWN_STATE" ) );
          }
      }
  
      @Test
      public void Nonnumeric_position_is_an_error()
      {
          try
          {
              SolutionParser.parse( "bash;(3,a)" );
              fail( "Expected an InvalidAction!" );
          }
          catch ( InvalidAction e )
          {
              assertThat( e.action, equalTo( "(3,a)" ) );
          }
      }
  
      @Test
      public void Massive_wait_is_an_error()
      {
          String bigNum = String.valueOf( Long.MAX_VALUE );
  
          try
          {
              SolutionParser.parse( bigNum );
              fail( "Expected an InvalidAction!" );
          }
          catch ( InvalidAction e )
          {
              assertThat( e.action, equalTo( bigNum ) );
          }
      }
  
      @Test
      public void Massive_position_is_an_error()
      {
          String bigNum = String.valueOf( Long.MAX_VALUE );
  
          try
          {
              SolutionParser.parse( "bash;(3," + bigNum + ")" );
              fail( "Expected an InvalidAction!" );
          }
          catch ( InvalidAction e )
          {
              assertThat( e.action, equalTo( "(3," + bigNum + ")" ) );
          }
      }
  
      @Test
      public void Empty_string_round_trips()
      {
          assertThat( "", roundTrips() );
      }
  
      @Test
      public void Wait_then_empty_round_trips()
      {
          assertThat( "2;", roundTrips() );
      }
  
      @Test
      public void Two_waits_then_empty_round_trips()
      {
          assertThat( "5;6;", roundTrips() );
      }
  
      @Test
      public void Empty_at_start_round_trips()
      {
          assertThat( ";5;6;", roundTrips() );
      }
  
      @Test
      public void Single_choose_token_round_trips()
      {
          assertThat( "bash", roundTrips() );
      }
  
      @Test
      public void Single_assert_round_trips()
      {
          assertThat( "WON", roundTrips() );
      }
  
      @Test
      public void Two_normal_commands_round_trips()
      {
          assertThat( "bash;(3,2)", roundTrips() );
      }
  
      @Test
      public void Two_normal_actions_round_trips()
      {
          assertThat( "bash&(3,2)", roundTrips() );
      }
  
      @Test
      public void Multiple_actions_and_commands_round_trips()
      {
          assertThat( ";bash&(3,2);RUNNING;3;bash", roundTrips() );
      }
  
      @Test
      public void Leading_and_trailing_empty_actions()
      {
          assertThat( ";;;2;bash&(3,2);RUNNING;3;bash;;;", roundTrips() );
      }
  
      @Test
      public void Single_choose_token_plus_empty_round_trips()
      {
          assertThat( "bash;", roundTrips() );
      }
  
      @Test
      public void Single_wait_round_trips()
      {
          assertThat( "", roundTrips() );
      }
  
      @Test
      public void Wait_1_changes_to_blank()
      {
          assertThat(
              SolutionParser.serialise( SolutionParser.parse( "1" ) ),
              equalTo( "" )
          );
      }
  
      @Test
      public void Two_waits_round_trip()
      {
          assertThat( "5;6", roundTrips() );
      }
  
      @Test
      public void Until_WON_round_trips()
      {
          assertThat( "5;6;until:WON", roundTrips() );
      }
  
      @Test
      public void Until_LOST_round_trips()
      {
          assertThat( "dig&(7,4);until:LOST", roundTrips() );
      }
  
      @Test( expected=SolutionCommand.WaitActionInMultiActionCommand.class )
      public void Multi_action_commands_must_not_include_a_wait()
      {
          SolutionParser.parse( "2&bash");
      }
  
      // ---
  
      private Matcher<String> roundTrips()
      {
          return new BaseMatcher<String>()
          {
              private String after = null;
  
              @Override
              public void describeTo( Description desc )
              {
                  desc.appendText( "\"" + after + "\"" );
              }
  
              @Override
              public boolean matches( Object inputObj )
              {
                  if ( !( inputObj instanceof String ) )
                  {
                      return false;
                  }
  
                  String before = (String)inputObj;
  
                  after = SolutionParser.serialise(
                      SolutionParser.parse( before ) );
  
                  return after.equals( before );
              }
          };
      }
  }
  