content: package rabbitescape.engine.util;
  
  import static org.hamcrest.CoreMatchers.equalTo;
  import static org.hamcrest.MatcherAssert.assertThat;
  import static rabbitescape.engine.CellularDirection.DOWN;
  import static rabbitescape.engine.CellularDirection.HERE;
  import static rabbitescape.engine.CellularDirection.LEFT;
  import static rabbitescape.engine.CellularDirection.RIGHT;
  import static rabbitescape.engine.CellularDirection.UP;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import org.junit.Test;
  
  import rabbitescape.engine.CellularDirection;
  import rabbitescape.engine.WaterRegion;
  
  public class TestWaterUtil
  {
      @Test
      public void empty_neighbourhood()
      {
          // Create a water table containing just one region.
          WaterRegion region = new WaterRegion( 0, 0, Util.newSet( UP, LEFT, RIGHT, DOWN ), 100 );
          LookupTable2D<WaterRegion> waterTable = new LookupTable2D<>( Arrays.asList( region ), new Dimension( 1, 1 ) );
  
          Map<CellularDirection, WaterRegion> neighbourhood = WaterUtil.findNeighbourhood( region , waterTable );
  
          assertThat( neighbourhood.keySet(), equalTo( Util.newSet( HERE ) ) );
          assertThat( neighbourhood.get( HERE ), equalTo( region ) );
      }
  
      @Test
      public void neighbourhood_includes_perimeter()
      {
          // Create a 1x1 water table with a perimeter of size 1 all the way around.
          List<WaterRegion> allRegions = new ArrayList<>();
          for ( int x = -1; x <= 1; x++ )
          {
              for ( int y = -1; y <= 1; y++ )
              {
                  allRegions.add( new WaterRegion( x, y, Util.newSet( UP, LEFT, RIGHT, DOWN ), 100 ) );
              }
          }
          LookupTable2D<WaterRegion> waterTable = new LookupTable2D<>( allRegions, new Dimension( 1, 1 ) );
          WaterRegion region = waterTable.getItemAt( 0, 0 );
  
          Map<CellularDirection, WaterRegion> neighbourhood = WaterUtil.findNeighbourhood( region , waterTable );
  
          assertThat( neighbourhood.keySet(), equalTo( Util.newSet( UP, LEFT, RIGHT, DOWN, HERE ) ) );
      }
  
      /**
       * Calculate the neighbourhood of a ramp in a water table of the form:
       * <pre>
       *        [/RAMP]       
       * [SPACE][/RAMP][BLOCK]
       *        [SPACE]       
       * </pre>
       * Check that the only neighbour returned is the space to the left.
       */
      @Test
      public void connection_has_to_be_in_both_directions()
      {
          WaterRegion middle = new WaterRegion( 1, 1, Util.newSet( UP, LEFT ), 10 );
          WaterRegion top = new WaterRegion( 1, 0, Util.newSet( UP, LEFT ), 20 );
          WaterRegion left = new WaterRegion( 0, 1, Util.newSet( UP, LEFT, RIGHT, DOWN ), 30 );
          // Right has no WaterRegion, because it's a solid block.
          WaterRegion bottom = new WaterRegion( 1, 2, Util.newSet( UP, LEFT, RIGHT, DOWN ), 40 );
          LookupTable2D<WaterRegion> waterTable = new LookupTable2D<>(
              Arrays.asList( middle, top, left, bottom ), new Dimension( 3, 3 ) );
  
          Map<CellularDirection, WaterRegion> neighbourhood = WaterUtil.findNeighbourhood( middle , waterTable );
  
          assertThat( neighbourhood.keySet(), equalTo( Util.newSet( LEFT, HERE ) ) );
          assertThat( neighbourhood.get( LEFT ), equalTo( left ) );
          assertThat( neighbourhood.get( HERE ), equalTo( middle ) );
      }
  
      @Test( expected = IllegalStateException.class )
      public void exception_if_two_regions_connected_on_same_side()
      {
          WaterRegion region = new WaterRegion( 0, 0, Util.newSet( DOWN ), 100 );
          WaterRegion bottomA = new WaterRegion( 0, 1, Util.newSet( UP ), 100 );
          WaterRegion bottomB = new WaterRegion( 0, 1, Util.newSet( UP ), 100 );
          LookupTable2D<WaterRegion> waterTable = new LookupTable2D<>(
              Arrays.asList( region, bottomA, bottomB ), new Dimension( 2, 2 ) );
  
          WaterUtil.findNeighbourhood( region , waterTable );
      }
  
      @Test
      public void no_exception_if_two_regions_on_same_side_arent_both_connected()
      {
          WaterRegion region = new WaterRegion( 0, 0, Util.newSet( DOWN ), 100 );
          WaterRegion bottomA = new WaterRegion( 0, 1, Util.newSet( UP ), 100 );
          WaterRegion bottomB = new WaterRegion( 0, 1, Util.newSet( LEFT ), 100 );
          LookupTable2D<WaterRegion> waterTable = new LookupTable2D<>(
              Arrays.asList( region, bottomA, bottomB ), new Dimension( 2, 2 ) );
  
          Map<CellularDirection, WaterRegion> neighbourhood = WaterUtil.findNeighbourhood( region , waterTable );
          assertThat( neighbourhood.keySet(), equalTo( Util.newSet( DOWN, HERE ) ) );
          assertThat( neighbourhood.get( DOWN ), equalTo( bottomA ) );
          assertThat( neighbourhood.get( HERE ), equalTo( region ) );
      }
  
      @Test
      public void water_falls_down_if_space_below()
      {
          Map<CellularDirection, WaterRegion> neighbourhood = new HashMap<>();
          neighbourhood.put( HERE, new WaterRegion( 0, 0, null, 100, 100, false ) );
          neighbourhood.put( DOWN, new WaterRegion( 0, 0, null, 100, 0, false ) );
  
          Map<CellularDirection, Integer> flow = WaterUtil.calculateFlow( neighbourhood  );
  
          assertThat( flow.get( DOWN ), equalTo( 100 ) );
      }
  
      @Test
      public void water_stays_if_no_space_below()
      {
          Map<CellularDirection, WaterRegion> neighbourhood = new HashMap<>();
          neighbourhood.put( HERE, new WaterRegion( 0, 0, null, 100, 100, false ) );
          neighbourhood.put( DOWN, new WaterRegion( 0, 0, null, 100, 100, false ) );
  
          Map<CellularDirection, Integer> flow = WaterUtil.calculateFlow( neighbourhood  );
  
          assertThat( "A small amount of water should fall due to compression.",
              flow.get( DOWN ), equalTo( 20 ) );
      }
  
      @Test
      public void water_spreads_on_a_surface()
      {
          Map<CellularDirection, WaterRegion> neighbourhood = new HashMap<>();
          neighbourhood.put( HERE, new WaterRegion( 0, 0, null, 100, 100, false ) );
          neighbourhood.put( LEFT, new WaterRegion( 0, 0, null, 100, 0, false ) );
          neighbourhood.put( RIGHT, new WaterRegion( 0, 0, null, 100, 0, false ) );
  
          Map<CellularDirection, Integer> flow = WaterUtil.calculateFlow( neighbourhood  );
  
          assertThat( "No water should fall as there's no region there.",
              flow.get( DOWN ), equalTo( 0 ) );
          assertThat( flow.get( LEFT ), equalTo( 33 ) );
          assertThat( flow.get( RIGHT ), equalTo( 33 ) );
      }
  
      @Test
      public void water_spreads_according_to_existing_contents()
      {
          Map<CellularDirection, WaterRegion> neighbourhood = new HashMap<>();
          neighbourhood.put( HERE, new WaterRegion( 0, 0, null, 100, 6, false ) );
          neighbourhood.put( LEFT, new WaterRegion( 0, 0, null, 100, 2, false ) );
          neighbourhood.put( RIGHT, new WaterRegion( 0, 0, null, 100, 1, false ) );
  
          Map<CellularDirection, Integer> flow = WaterUtil.calculateFlow( neighbourhood  );
  
          // To balance the three cells we need to end up with 3 units in each cell.
          assertThat( flow.get( LEFT ), equalTo( 1 ) );
          assertThat( flow.get( RIGHT ), equalTo( 2 ) );
      }
  
      @Test
      public void water_doesnt_spread_if_neighbours_are_fuller()
      {
          Map<CellularDirection, WaterRegion> neighbourhood = new HashMap<>();
          neighbourhood.put( HERE, new WaterRegion( 0, 0, null, 100, 99, false ) );
          neighbourhood.put( LEFT, new WaterRegion( 0, 0, null, 100, 101, false ) );
          neighbourhood.put( RIGHT, new WaterRegion( 0, 0, null, 100, 100, false ) );
  
          Map<CellularDirection, Integer> flow = WaterUtil.calculateFlow( neighbourhood  );
  
          // To balance the three cells we need to end up with 3 units in each cell.
          assertThat( flow.get( LEFT ), equalTo( 0 ) );
          assertThat( flow.get( RIGHT ), equalTo( 0 ) );
      }
  
      @Test
      public void pressurised_water_flows_upwards()
      {
          Map<CellularDirection, WaterRegion> neighbourhood = new HashMap<>();
          neighbourhood.put( HERE, new WaterRegion( 0, 0, null, 100, 200, false ) );
          neighbourhood.put( UP, new WaterRegion( 0, 0, null, 100, 0, false ) );
  
          Map<CellularDirection, Integer> flow = WaterUtil.calculateFlow( neighbourhood  );
  
          // 68 seems a fairly arbitrary number. It's calculated using the MAGIC_UP_NUMERATOR and MAGIC_UP_DENOMINATOR.
          assertThat( flow.get( UP ), equalTo( 68 ) );
      }
  
      @Test
      public void pressurised_water_can_flow_all_ways_at_once()
      {
          Map<CellularDirection, WaterRegion> neighbourhood = new HashMap<>();
          neighbourhood.put( HERE, new WaterRegion( 0, 0, null, 10, 200, false ) );
          neighbourhood.put( UP, new WaterRegion( 0, 0, null, 10, 0, false ) );
          neighbourhood.put( LEFT, new WaterRegion( 0, 0, null, 10, 0, false ) );
          neighbourhood.put( RIGHT, new WaterRegion( 0, 0, null, 10, 0, false ) );
          neighbourhood.put( DOWN, new WaterRegion( 0, 0, null, 10, 0, false ) );
  
          Map<CellularDirection, Integer> flow = WaterUtil.calculateFlow( neighbourhood  );
  
          assertThat( flow.get( UP ), equalTo( 62 ) );
          assertThat( flow.get( LEFT ), equalTo( 27 ) );
          assertThat( flow.get( RIGHT ), equalTo( 27 ) );
          assertThat( flow.get( DOWN ), equalTo( 47 ) );
          // This will leaving 37 units unmoved.
      }
  }
  