content: package rabbitescape.render;
  
  import static org.hamcrest.MatcherAssert.*;
  import static org.hamcrest.CoreMatchers.*;
  
  import java.util.ArrayList;
  import java.util.List;
  
  import rabbitescape.render.androidlike.Bitmap;
  import rabbitescape.render.androidlike.Canvas;
  import rabbitescape.render.androidlike.Paint;
  import rabbitescape.render.androidlike.Path;
  import rabbitescape.render.androidlike.Rect;
  
  import org.junit.*;
  
  public class TestRendering
  {
      @Test
      public void Bitmaps_are_not_rescaled_every_render()
      {
          TrackingBitmapScaler scaler = new TrackingBitmapScaler();
          FakeBitmapLoader loader = new FakeBitmapLoader( 12, 12 );
          BitmapCache<FakeBitmap> cache = new BitmapCache<FakeBitmap>(
              loader, scaler, Runtime.getRuntime().maxMemory() / 8 );
  
          List<Sprite> sprites1 = sprites( "x", 1, 1, 6, 4 );
          List<Sprite> sprites2 = sprites( "x", 1, 1, 6, 4 );
  
          TrackingCanvas output = new TrackingCanvas( 200, 200, loader, scaler );
  
          Renderer<FakeBitmap, FakePaint> renderer =
              new Renderer<FakeBitmap, FakePaint>( 0, 0, 16, cache );
  
          // Sanity: no calls to scale yet
          assertThat( scaler.scaleCalls.size(), equalTo( 0 ) );
  
          renderer.render( output, sprites1, null );
  
          // Sanity: we had to scale the bitmap
          assertThat( scaler.scaleCalls.size(), equalTo( 1 ) );
          assertThat( scaler.scaleCalls.get( 0 ), equalTo( 0.5 ) );
  
          // This is what we are testing: render new sprite, same bitmap
          renderer.render( output, sprites2, null );
  
          // No new scale call was made
          assertThat( scaler.scaleCalls.size(), equalTo( 1 ) );
      }
  
      @Test
      public void Bitmaps_are_drawn_if_they_overlap_the_canvas()
      {
          // Exact top left
          assertDrawnAt( 0f, 0f, 0, 0,   0,   0,   0,   0, 64, 64, 32, 32, 32 );
          assertDrawnAt( 0f, 0f, 1, 1,   0,   0, -32, -32, 64, 64, 32, 32, 32 );
          assertDrawnAt( 0f, 0f, 3, 3,   0,   0, -48, -48, 64, 64, 32, 32, 16 );
          assertDrawnAt( 0f, 0f, 1, 1, -32, -32,   0,   0, 64, 64, 32, 32, 32 );
          assertDrawnAt( 0f, 0f, 3, 2, -96, -64,   0,   0, 64, 64, 32, 32, 48 );
          assertDrawnAt( 0f, 0f, 4, 2, -96, -64, -48,   0, 64, 64, 32, 32, 48 );
  
          // Overlap from top left
          assertDrawnAt( -6f, -6f, 0, 0, -6, -6, 0, 0, 64, 64, 32, 32, 32 );
          assertDrawnAt( -6f, -6f, 0, 0, -4, -4, 0, 0, 64, 64, 32, 32, 48 );
  
          // Overlap from left
          assertDrawnAt( -6f, 0f, 0, 0,   0, 0, -6, 0, 64, 64, 32, 32, 32 );
          assertDrawnAt( -6f, 0f, 0, 0, -12, 0,  0, 0, 64, 64, 32, 32, 16 );
  
          // Overlap from top
          assertDrawnAt( 0f, -6f, 0, 0,   0, 0,  0, -6, 64, 64, 32, 32, 32 );
          assertDrawnAt( 0f, -6f, 0, 0, -0, -12, 0,  0, 64, 64, 32, 32, 16 );
  
          // Overlap to bottom right
          assertDrawnAt( 0f,   0f, 0, 0, 0, 0,  0,  0, 64, 64, 96, 96, 32 );
          assertDrawnAt( 32f, 32f, 1, 1, 0, 0,  0,  0, 64, 64, 64, 64, 32 );
          assertDrawnAt( 62f, 62f, 0, 0, 0, 0, 62, 62, 64, 64, 64, 64, 64 );
      }
  
      @Test
      public void Bitmaps_are_not_drawn_if_they_do_not_overlap_the_canvas()
      {
          // Just on top left, then off top left
          assertDrawnAt( -31f, -31f, 0, 0, 0, 0, -31, -31, 64, 64, 32, 32, 32 );
          assertNotDrawn(            0, 0, 0, 0, -32, -32, 64, 64, 32, 32, 32 );
  
          // Again, larger image
          assertDrawnAt( -63f, -63f, 0, 0, -32, -32, -31, -31, 64, 64, 64, 64, 32 );
          assertNotDrawn(            0, 0, -32, -32, -32, -32, 64, 64, 64, 64, 32 );
  
          // Just on left, then off left
          assertDrawnAt( -31f, 0f, 0, 0, 0, 0, -31, 0, 64, 64, 32, 32, 32 );
          assertNotDrawn(          0, 0, 0, 0, -32, 0, 64, 64, 32, 32, 32 );
  
          // Just on top, then off top
          assertDrawnAt(  0f, -31f, 0, 0, 0, -31, 0, 0, 64, 64, 32, 32, 32 );
          assertNotDrawn(           0, 0, 0, -32, 0, 0, 64, 64, 32, 32, 32 );
  
          // Just on bottom right, then off botton right
          assertDrawnAt( 63f, 63f, 0, 0, 0, 0, 63, 63, 64, 64, 32, 32, 32 );
          assertNotDrawn(          0, 0, 0, 0, 64, 64, 64, 64, 32, 32, 32 );
  
          // Just on bottom right, then off botton right (again)
          assertDrawnAt( 63f, 63f, 0, 0, 15, 15, 48, 48, 64, 64, 32, 32, 32 );
          assertNotDrawn(          0, 0, 16, 16, 48, 48, 64, 64, 32, 32, 32 );
  
          // Just on bottom right, then off botton right (again)
          assertDrawnAt( 63f, 63f, 3, 3, 16, 16, 7, 7, 64, 64, 32, 32, 16 );
          assertNotDrawn(          3, 3, 16, 16, 8, 8, 64, 64, 32, 32, 16 );
      }
  
      // ---
  
      private void assertNotDrawn(
          int tileX,
          int tileY,
          int spriteOffset32X,
          int spriteOffset32Y,
          int rendererOffsetX,
          int rendererOffsetY,
          int canvasSizeX,
          int canvasSizeY,
          int bitmapWidth,
          int bitmapHeight,
          int tileSize
      )
      {
          TrackingCanvas output = draw( tileX, tileY, spriteOffset32X,
              spriteOffset32Y, rendererOffsetX, rendererOffsetY, canvasSizeX,
              canvasSizeY, bitmapWidth, bitmapHeight, tileSize );
  
          assertThat( output.drawCalls.size(), equalTo( 0 ) );
  
          // TODO: they are still loaded at the moment, so that
          // we can measure their size.  We could at least avoid
          // loading them when they are off the right or bottom.
          // assertThat( output.loadCalls.size(), equalTo( 0 ) );
      }
  
      private void assertDrawnAt(
          float expectedDrawX,
          float expectedDrawY,
          int tileX,
          int tileY,
          int spriteOffset32X,
          int spriteOffset32Y,
          int rendererOffsetX,
          int rendererOffsetY,
          int canvasSizeX,
          int canvasSizeY,
          int bitmapWidth,
          int bitmapHeight,
          int tileSize
      )
      {
          TrackingCanvas output = draw( tileX, tileY, spriteOffset32X,
              spriteOffset32Y, rendererOffsetX, rendererOffsetY, canvasSizeX,
              canvasSizeY, bitmapWidth, bitmapHeight, tileSize );
  
          assertThat( output.drawCalls.get( 0 ).left, equalTo( expectedDrawX ) );
          assertThat( output.drawCalls.get( 0 ).top,  equalTo( expectedDrawY ) );
          assertThat( output.drawCalls.size(), equalTo( 1 ) );
  
          int imgSize = new FakeBitmapLoader( 0, 0 ).sizeFor( tileSize );
  
          assertThat( output.loadCalls.get( 0 ).fileName, equalTo( "x" ) );
          assertThat( output.loadCalls.get( 0 ).tileSize, equalTo( imgSize ) );
          assertThat( output.loadCalls.size(), equalTo( 1 ) );
  
          if ( tileSize == imgSize )
          {
              assertThat( output.scaleCalls.size(), equalTo( 0 ) );
          }
          else
          {
              assertThat(
                  output.scaleCalls.get( 0 ),
                  equalTo( ( double )tileSize / imgSize )
              );
              assertThat( output.scaleCalls.size(), equalTo( 1 ) );
          }
      }
  
      private TrackingCanvas draw(
          int tileX,
          int tileY,
          int spriteOffset32X,
          int spriteOffset32Y,
          int rendererOffsetX,
          int rendererOffsetY,
          int canvasSizeX,
          int canvasSizeY,
          int bitmapWidth,
          int bitmapHeight,
          int tileSize )
      {
          TrackingBitmapScaler scaler = new TrackingBitmapScaler();
          FakeBitmapLoader loader = new FakeBitmapLoader( bitmapWidth, bitmapHeight );
          BitmapCache<FakeBitmap> cache = new BitmapCache<FakeBitmap>(
              loader, scaler, Runtime.getRuntime().maxMemory() / 8 );
  
          TrackingCanvas output = new TrackingCanvas(
              canvasSizeX, canvasSizeY, loader, scaler );
  
          new Renderer<FakeBitmap, FakePaint>(
              rendererOffsetX, rendererOffsetY, tileSize, cache
          ).render(
              output,
              sprites(
                  "x",
                  tileX,
                  tileY,
                  spriteOffset32X,
                  spriteOffset32Y
              ),
              null
          );
  
          return output;
      }
  
      private List<Sprite> sprites(
          String bitmapName,
          int tileX,
          int tileY,
          int offset32X,
          int offset32Y
      )
      {
          List<Sprite> ret = new ArrayList<Sprite>();
  
          ret.add( new Sprite(
              bitmapName, null, tileX, tileY, offset32X, offset32Y ) );
  
          return ret;
      }
  
      private static class FakeBitmap implements Bitmap
      {
          private final int width;
          private final int height;
  
          public FakeBitmap( int width, int height )
          {
              this.width = width;
              this.height = height;
          }
  
          @Override
          public String name()
          {
              return null;
          }
  
          @Override
          public int width()
          {
              return width;
          }
  
          @Override
          public int height()
          {
              return height;
          }
  
          @Override
          public void recycle()
          {
          }
  
          @Override
          public long getByteCount()
          {
              return 1;
          }
      }
  
      private static class FakePaint implements Paint
      {
      }
  
      private static class TrackingBitmapScaler
      implements BitmapScaler<FakeBitmap>
      {
          public List<Double> scaleCalls = new ArrayList<Double>();
  
          @Override
          public FakeBitmap scale( FakeBitmap originalBitmap, double scale )
          {
              scaleCalls.add( scale );
              return originalBitmap;
          }
      }
  
      public static class TrackingCanvas implements Canvas<FakeBitmap, FakePaint>
      {
          private static class DrawCall
          {
              public final float left;
              public final float top;
  
              public DrawCall( float left, float top )
              {
                  this.left = left;
                  this.top = top;
              }
          }
  
          public final List<DrawCall> drawCalls = new ArrayList<DrawCall>();
          public final List<FakeBitmapLoader.LoadCall> loadCalls;
          public final List<Double> scaleCalls;
  
          private final int width;
          private final int height;
  
          public TrackingCanvas(
              int width,
              int height,
              FakeBitmapLoader loader,
              TrackingBitmapScaler scaler
          )
          {
              this.width = width;
              this.height = height;
              this.loadCalls = loader.loadCalls;
              this.scaleCalls = scaler.scaleCalls;
          }
  
          @Override
          public void drawBitmap(
              FakeBitmap bitmap,
              float left,
              float top,
              FakePaint paint
          )
          {
              drawCalls.add( new DrawCall( left, top ) );
          }
  
          @Override
          public int width()
          {
              return width;
          }
  
          @Override
          public int height()
          {
              return height;
          }
  
          @Override
          public void drawColor( FakePaint paint )
          {
          }
  
          @Override
          public void drawLine(
              float startX,
              float startY,
              float stopX,
              float stopY,
              FakePaint paint
          )
          {
          }
  
          @Override
          public void drawPath( Path path, FakePaint paint )
          {
          }
  
          @Override
          public void drawRect( Rect rect, FakePaint paint )
          {
          }
  
          @Override
          public void drawText( String text, float x, float y, FakePaint paint )
          {
          }
      }
  
      private static class FakeBitmapLoader implements BitmapLoader<FakeBitmap>
      {
          private static class LoadCall
          {
              private final String fileName;
              private final int tileSize;
  
              public LoadCall( String fileName, int tileSize )
              {
                  this.fileName = fileName;
                  this.tileSize = tileSize;
              }
          }
  
          public final List<LoadCall> loadCalls;
  
          private final int width;
          private final int height;
  
          public FakeBitmapLoader( int width, int height )
          {
              this.loadCalls = new ArrayList<LoadCall>();
              this.width = width;
              this.height = height;
          }
  
          @Override
          public FakeBitmap load( String fileName, int tileSize )
          {
              loadCalls.add( new LoadCall( fileName, tileSize ) );
              return new FakeBitmap( width, height );
          }
  
          @Override
          public int sizeFor( int tileSize )
          {
              return 32;
          }
      }
  }
  