content: package rabbitescape.engine.solution;
  
  import static org.junit.Assert.fail;
  import static org.hamcrest.MatcherAssert.*;
  import static org.hamcrest.CoreMatchers.*;
  
  import java.io.ByteArrayOutputStream;
  import java.io.PrintStream;
  import java.io.UnsupportedEncodingException;
  
  import org.junit.Test;
  
  import rabbitescape.engine.Token;
  import rabbitescape.engine.World;
  import rabbitescape.engine.World.CompletionState;
  import rabbitescape.engine.textworld.TextWorldManip;
  
  public class TestSolutionRunner
  {
      @Test( expected = SolutionExceptions.UnexpectedState.class )
      public void Unexpected_state_is_an_error()
      {
          SolutionRunner.runSolution(
              expectingSolution( CompletionState.LOST ), neverEndingWorld() );
      }
  
      @Test
      public void Unexpected_state_is_serialised_to_helpful_message()
      {
          try
          {
              SolutionRunner.runSolution(
                  expectingSolution( CompletionState.LOST ), neverEndingWorld() );
              fail( "Expected exception!" );
          }
          catch( SolutionExceptions.UnexpectedState e )
          {
              e.solutionId = 3;
              e.level = "baz";
              e.world = "z";
  
              assertThat(
                  e.getMessage(),
                  equalTo(
                      "Solution failed: state was RUNNING but we expected LOST"
                      + " at command 1 of solution 3 in baz:\nz."
                      + "\nTo see: ./runrabbit swing -l baz -s3"
                  )
              );
          }
      }
  
      @Test( expected = SolutionExceptions.DidNotWin.class )
      public void Failing_unexpectedly_is_an_error()
      {
          SolutionRunner.runSolution(
              expectingSolution( CompletionState.WON ), neverEndingWorld() );
      }
  
      @Test
      public void Failing_unexpectedly_is_serialised_to_helpful_message()
      {
          try
          {
              SolutionRunner.runSolution(
                  expectingSolution( CompletionState.WON ), neverEndingWorld() );
              fail( "Expected exception!" );
          }
          catch( SolutionExceptions.UnexpectedState e )
          {
              e.solutionId = 4;
              e.level = "X";
              e.world = "";
  
              assertThat(
                  e.getMessage(),
                  equalTo(
                      "Solution failed: We expected to win, but the state was"
                      + " RUNNING at command 1 of solution 4 in X:\n."
                      + "\nTo see: ./runrabbit swing -l X -s4"
                  )
              );
          }
      }
  
      @Test( expected = SolutionExceptions.RanPastEnd.class )
      public void Going_on_beyond_the_end_is_an_error()
      {
          SolutionRunner.runSolution( waitFourThenBashSolution(), threeStepWorld() );
      }
  
      @Test
      public void Going_on_beyond_the_end_is_serialised_to_helpful_message()
      {
          try
          {
              SolutionRunner.runSolution( waitFourThenBashSolution(), threeStepWorld() );
              fail( "Expected exception!" );
          }
          catch( SolutionExceptions.RanPastEnd e )
          {
              e.solutionId = 5;
              e.world = "";
  
              assertThat(
                  e.getMessage(),
                  equalTo(
                      "Solution failed: world has stopped (state: WON) but"
                      + " there are more solution commands"
                      + " at command 3 of solution 5 in <>:\n."
                      + "\nTo see: ./runrabbit swing -l <> -s5"
                  )
              );
          }
      }
  
      @Test( expected = SolutionExceptions.UsedRunOutAbility.class )
      public void Using_missing_ability_is_an_error()
      {
          SolutionRunner.runSolution(
              useBash30Solution(), neverEndingWorldWithBash() );
      }
  
      @Test
      public void Using_missing_ability_is_serialised_to_helpful_message()
      {
          try
          {
              SolutionRunner.runSolution(
                  useBash30Solution(), neverEndingWorldWithBash() );
  
              fail( "Expected exception!" );
          }
          catch( SolutionExceptions.UsedRunOutAbility e )
          {
              e.solutionId = 6;
              e.level = "foo";
              e.world = "";
  
              assertThat(
                  e.getMessage(),
                  equalTo(
                      "Solution failed: ability 'bash' was used when there"
                      + " were none left at command 4 of solution 6 in foo:\n."
                      + "\nTo see: ./runrabbit swing -l foo -s6"
                  )
              );
          }
      }
  
      @Test( expected = SolutionExceptions.UsedMissingAbility.class )
      public void Using_run_out_ability_is_an_error()
      {
          SolutionRunner.runSolution( useBash30Solution(), neverEndingWorld() );
      }
  
      @Test
      public void Using_run_out_ability_is_serialised_to_helpful_message()
      {
          try
          {
              SolutionRunner.runSolution(
                  useBash30Solution(), neverEndingWorld() );
  
              fail( "Expected exception!" );
          }
          catch( SolutionExceptions.UsedMissingAbility e )
          {
              e.solutionId = 7;
              e.level = "foo";
              e.world = "";
  
              assertThat(
                  e.getMessage(),
                  equalTo(
                      "Solution failed: ability 'bash' was used but this level"
                      + " does not provide it at command 2 of solution 7 in"
                      + " foo:\n."
                      + "\nTo see: ./runrabbit swing -l foo -s7"
                  )
              );
          }
      }
  
      @Test( expected = SolutionExceptions.PlacedTokenOutsideWorld.class )
      public void Placing_a_token_outside_the_world_is_an_error()
      {
          SolutionRunner.runSolution(
              useBash100Solution(), neverEndingWorldWithBash() );
      }
  
      @Test
      public void Placing_a_token_outside_the_world_is_serialised_nicely()
      {
          try
          {
              SolutionRunner.runSolution(
                  useBash100Solution(), neverEndingWorldWithBash() );
  
              fail( "Expected exception!" );
          }
          catch( SolutionExceptions.PlacedTokenOutsideWorld e )
          {
              e.solutionId = 8;
              e.level = "bar";
              e.world = "";
  
              assertThat(
                  e.getMessage(),
                  equalTo(
                      "Solution failed: placed a token at (10, 0) but the"
                      + " world is only 5x2 in size"
                      + " at command 2 of solution 8 in bar:\n."
                      + "\nTo see: ./runrabbit swing -l bar -s8"
                  )
              );
          }
      }
  
      @Test
      public void Rabbit_dying_by_walking_out_of_level_is_not_an_error()
      {
          SolutionRunner.runSolution(
              waitFiveThenLostSolution(),
              TextWorldManip.createWorld(
                  "#r   ",
                  "#####",
                  ":num_rabbits=0",
                  ":num_to_save=1"
              )
          );
      }
  
      @Test( expected = SolutionExceptions.FailedToPlaceToken.class )
      public void Placing_a_token_on_a_block_is_an_error()
      {
          SolutionRunner.runSolution( useBash30Solution(), blockAt30World() );
      }
  
      @Test
      public void Placing_a_token_on_a_block_is_serialised_nicely()
      {
          try
          {
              SolutionRunner.runSolution( useBash30Solution(), blockAt30World() );
  
              fail( "Expected exception!" );
          }
          catch( SolutionExceptions.FailedToPlaceToken e )
          {
              e.solutionId = 9;
              e.level = "bar";
              e.world = "y\nx";
  
              assertThat(
                  e.getMessage(),
                  equalTo(
                      "Solution failed: tried to place a bash token at (3, 0) but"
                      + " a block was already there so it did not place"
                      + " at command 4 of solution 9 in bar:\ny\nx."
                      + "\nTo see: ./runrabbit swing -l bar -s9"
                  )
              );
          }
      }
  
      @Test
      public void Until_never_ending_is_serialised_nicely()
      {
          try
          {
              SolutionRunner.runSolution(
                  new Solution(
                      new SolutionCommand(
                          new UntilAction( CompletionState.WON )
                      )
                  ),
                  neverEndingWorld()
              );
  
              fail( "Expected exception!" );
          }
          catch( SolutionExceptions.UntilActionNeverEnded e )
          {
              e.solutionId = 10;
              e.level = "qux";
              e.world = "x\ny";
  
              assertThat(
                  e.getMessage(),
                  equalTo(
                      "Solution failed: the level never finished, but"
                      + " there was an until:WON"
                      + " action at command 1 of solution 10 in qux:\nx\ny."
                      + "\nTo see: ./runrabbit swing -l qux -s10"
                  )
              );
          }
      }
  
      @Test
      public void Real_level_with_WON_at_end_works()
      {
          World world = TextWorldManip.createWorld(
              ":num_rabbits=1",
              ":num_to_save=1",
              "Q    ",
              "    O",
              "#####"
          );
  
          boolean solved = SolutionRunner.runSolution(
              SolutionParser.parse( "6;WON" ), world );
  
          assertThat( solved, is( true ) );
      }
  
      @Test
      public void Real_level_with_no_assert_but_we_won_works()
      {
          World world = TextWorldManip.createWorld(
              ":num_rabbits=1",
              ":num_to_save=1",
              "Q    ",
              "    O",
              "#####"
          );
  
          boolean solved = SolutionRunner.runSolution(
              SolutionParser.parse( "6" ), world );
  
          assertThat( solved, is( true ) );
      }
  
      @Test
      public void Real_level_with_LOST_at_end_works()
      {
          World world = TextWorldManip.createWorld(
              ":num_rabbits=1",
              ":num_to_save=1",
              "Q    ",
              "     ",
              "#####"
          );
  
          boolean solved = SolutionRunner.runSolution(
              SolutionParser.parse( "7;LOST" ), world );
  
          assertThat( solved, is( false ) );
      }
  
      @Test
      public void Real_level_with_no_assert_and_still_running_did_not_solve()
      {
          World world = TextWorldManip.createWorld(
              ":num_rabbits=1",
              ":num_to_save=1",
              "Q    ",
              "     ",
              "#####"
          );
  
          boolean solved = SolutionRunner.runSolution(
              SolutionParser.parse( "2;RUNNING" ), world );
  
          assertThat( solved, is( false ) );
      }
  
      @Test
      public void Real_level_with_until_WON_works()
      {
          World world = TextWorldManip.createWorld(
              ":num_rabbits=1",
              ":num_to_save=1",
              "Q    ",
              "    O",
              "#####"
          );
  
          boolean solved = SolutionRunner.runSolution(
              SolutionParser.parse( "until:WON" ), world );
  
          assertThat( solved, is( true ) );
      }
  
      @Test
      public void Real_level_with_until_LOST_works()
      {
          World world = TextWorldManip.createWorld(
              ":num_rabbits=1",
              ":num_to_save=1",
              "Q    ",
              "     ",
              "#####"
          );
  
          boolean solved = SolutionRunner.runSolution(
              SolutionParser.parse( "until:LOST" ), world );
  
          assertThat( solved, is( false ) );
      }
  
      @Test
      public void Print_step() throws UnsupportedEncodingException // for the UTF8
      {
          World world = TextWorldManip.createWorld(
              ":num_rabbits=0",
              ":num_to_save=1",
              ":solution.1=until:WON",
              " d j ",
              "#####",
              "#O###",
              "#####"
          );
  
          Solution solution = SolutionParser.parse( world.solutions[0] );
  
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          PrintStream ps = new PrintStream( baos );
  
          SolutionRunner.runSolution( solution, world, ps, false );
  
          String out = baos.toString("UTF8");
  
          String exp =
              "Waiting:0" + "\n" +
              "  Saved:0" + "\n" +
              "00  d j " + "\n" +
              "01 #####" + "\n" +
              "02 #O###" + "\n" +
              "03 #####" + "\n" +
              "   00000" + "\n" +
              "   01234" + "\n" +
              "Waiting:0" + "\n" +
              "  Saved:0" + "\n" +
              "00  dj  " + "\n" +
              "01 #####" + "\n" +
              "02 #O###" + "\n" +
              "03 #####" + "\n" +
              "   00000" + "\n" +
              "   01234" + "\n" +
              "Waiting:0" + "\n" +
              "  Saved:0" + "\n" +
              "00  j   " + "\n" +
              "01 #####" + "\n" +
              "02 #O###" + "\n" +
              "03 #####" + "\n" +
              "   00000" + "\n" +
              "   01234" + "\n" +
              "Waiting:0" + "\n" +
              "  Saved:0" + "\n" +
              "00      " + "\n" +
              "01 #j###" + "\n" +
              "02 #O###" + "\n" +
              "03 #####" + "\n" +
              "   00000" + "\n" +
              "   01234" + "\n" +
              "Waiting:0" + "\n" +
              "  Saved:0" + "\n" +
              "00      " + "\n" +
              "01 #j###" + "\n" +
              "02 #O###" + "\n" +
              "03 #####" + "\n" +
              "   00000" + "\n" +
              "   01234" + "\n" +
              "Waiting:0" + "\n" +
              "  Saved:0" + "\n" +
              "00      " + "\n" +
              "01 # ###" + "\n" +
              "02 #O###" + "\n" +
              "03 #####" + "\n" +
              "   00000" + "\n" +
              "   01234" + "\n" +
              "Waiting:0" + "\n" +
              "  Saved:1" + "\n" +
              "00      " + "\n" +
              "01 # ###" + "\n" +
              "02 #O###" + "\n" +
              "03 #####" + "\n" +
              "   00000" + "\n" +
              "   01234" + "\n" +
              "Waiting:0" + "\n" +
              "  Saved:1" + "\n" +
              "00      " + "\n" +
              "01 # ###" + "\n" +
              "02 #O###" + "\n" +
              "03 #####" + "\n" +
              "   00000" + "\n" +
              "   01234" + "\n";
  
          assertThat( out, equalTo( exp ) );
      }
  
      // --
  
      private World neverEndingWorld()
      {
          return TextWorldManip.createWorld(
              "#r  #",
              "#####"
          );
      }
  
      private World neverEndingWorldWithBash()
      {
          return TextWorldManip.createWorld(
              "#r  #",
              "#####",
              ":bash=1"
          );
      }
  
      private World threeStepWorld()
      {
          return TextWorldManip.createWorld(
              "#r O",
              "####",
              ":num_rabbits=0",
              ":num_to_save=1"
          );
      }
  
      private World blockAt30World()
      {
          return TextWorldManip.createWorld(
              "#r ##",
              "#####",
              ":bash=2"
          );
      }
  
      private Solution expectingSolution( CompletionState expected )
      {
          return new Solution( new SolutionCommand( new AssertStateAction( expected ) ) );
      }
  
      private Solution waitFourThenBashSolution()
      {
          return new Solution(
              new SolutionCommand( new WaitAction( 1 ) ),
              new SolutionCommand( new WaitAction( 2 ) ),
              new SolutionCommand( new WaitAction( 1 ) ),
              new SolutionCommand( new SelectAction( Token.Type.bash ) )
          );
      }
  
      private Solution waitFiveThenLostSolution()
      {
          return new Solution(
              new SolutionCommand( new WaitAction( 5 ) ),
              new SolutionCommand( new AssertStateAction( CompletionState.LOST ) )
          );
      }
  
      private Solution useBash30Solution()
      {
          return new Solution(
              new SolutionCommand( new SelectAction( Token.Type.bash ) ),
              new SolutionCommand( new PlaceTokenAction( 1, 0 ) ),
              new SolutionCommand( new WaitAction( 1 ) ),
              new SolutionCommand( new PlaceTokenAction( 3, 0 ) )
          );
      }
  
      private Solution useBash100Solution()
      {
          return new Solution(
              new SolutionCommand( new SelectAction( Token.Type.bash ) ),
              new SolutionCommand( new PlaceTokenAction( 10, 0 ) )
          );
      }
  }
  