package de.gurkenlabs.litiengine.graphics;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

import de.gurkenlabs.litiengine.Game;
import de.gurkenlabs.litiengine.environment.IEnvironment;
import de.gurkenlabs.litiengine.environment.tilemap.IMap;
import de.gurkenlabs.litiengine.environment.tilemap.MapUtilities;
import de.gurkenlabs.litiengine.util.ImageProcessing;
import de.gurkenlabs.litiengine.util.MathUtilities;

public abstract class ColorLayer implements IRenderable {
  private final IEnvironment environment;
  private final Image[][] tiles;

  private int alpha;
  private Color color;

  protected ColorLayer(IEnvironment env, final Color color, final int alpha) {
    this.environment = env;
    this.color = color;
    this.alpha = alpha;
    this.tiles = new Image[env.getMap().getSizeInTiles().width][env.getMap().getSizeInTiles().height];
    this.updateSection(this.environment.getMap().getBounds());
  }

  @Override
  public void render(Graphics2D g) {
    final Rectangle2D viewport = Game.getCamera().getViewPort();

    final IMap map = this.getEnvironment().getMap();
    final Point startTile = MapUtilities.getTile(map, new Point2D.Double(viewport.getX(), viewport.getY()));
    final Point endTile = MapUtilities.getTile(map, new Point2D.Double(viewport.getMaxX(), viewport.getMaxY()));
    final int startX = MathUtilities.clamp(startTile.x, 0, tiles.length - 1);
    final int endX = MathUtilities.clamp(endTile.x, 0, tiles.length - 1);
    final int startY = MathUtilities.clamp(startTile.y, 0, tiles[0].length - 1);
    final int endY = MathUtilities.clamp(endTile.y, 0, tiles[0].length - 1);

    final Point2D origin = Game.getCamera().getViewPortLocation(0, 0);

    // draw the tile on the layer image
    for (int x = startX; x <= endX; x++) {
      for (int y = startY; y <= endY; y++) {
        RenderEngine.renderImage(g, tiles[x][y], origin.getX() + x * map.getTileSize().width, origin.getY() + y * map.getTileSize().height);
      }
    }
  }

  public int getAlpha() {
    return this.alpha;
  }

  public Color getColor() {
    return this.color;
  }

  public Color getColorWithAlpha() {
    return new Color(this.getColor().getRed(), this.getColor().getGreen(), this.getColor().getBlue(), this.getAlpha());
  }

  public void setAlpha(int ambientAlpha) {
    this.alpha = MathUtilities.clamp(ambientAlpha, 0, 255);
    this.updateSection(this.environment.getMap().getBounds());
  }

  public void setColor(final Color color) {
    this.color = color;
    this.updateSection(this.environment.getMap().getBounds());
  }

  public void updateSection(Rectangle2D section) {
    if (this.getColor() == null) {
      return;
    }

    final IMap map = this.getEnvironment().getMap();

    final Rectangle2D tileSection = MapUtilities.getTileBoundingBox(map, section);
    final BufferedImage img = ImageProcessing.getCompatibleImage((int) tileSection.getWidth(), (int) tileSection.getHeight());
    final Graphics2D g = img.createGraphics();

    this.renderSection(g, tileSection);

    g.dispose();

    this.setTiles(img, tileSection);
  }

  private void setTiles(BufferedImage img, Rectangle2D section) {
    final IMap map = this.getEnvironment().getMap();
    final Point startTile = MapUtilities.getTile(map, new Point2D.Double(section.getX(), section.getY()));
    final Point endTile = MapUtilities.getTile(map, new Point2D.Double(section.getMaxX(), section.getMaxY()));
    final int startX = MathUtilities.clamp(startTile.x, 0, tiles.length - 1);
    final int startY = MathUtilities.clamp(startTile.y, 0, tiles[0].length - 1);

    final int endX = MathUtilities.clamp(endTile.x, 0, tiles.length - 1);
    final int endY = MathUtilities.clamp(endTile.y, 0, tiles[0].length - 1);

    for (int x = startX; x < endX; x++) {
      for (int y = startY; y < endY; y++) {
        final BufferedImage smallImage = img.getSubimage((x - startX) * map.getTileSize().width, (y - startY) * map.getTileSize().height, map.getTileSize().width, map.getTileSize().height);
        this.tiles[x][y] = smallImage;
      }
    }
  }

  protected abstract void renderSection(Graphics2D g, Rectangle2D section);

  protected IEnvironment getEnvironment() {
    return this.environment;
  }
}

@@ -3615,32 +3615,66 @@
 (startTile.x, 0,
+ Math.min(endTile.x - startTile.x,
  tiles.length - 
@@ -3662,32 +3662,33 @@
 .x, tiles.length
+)
  - 1);%0A    final
@@ -3728,32 +3728,66 @@
 (startTile.y, 0,
+ Math.min(endTile.y - startTile.y,
  tiles%5B0%5D.length
@@ -3778,32 +3778,33 @@
  tiles%5B0%5D.length
+)
  - 1);%0A%0A    fina
@@ -3841,32 +3841,67 @@
 mp(endTile.x, 0,
+  Math.min(endTile.x - startTile.x,
  tiles.length - 
@@ -3889,32 +3889,33 @@
 .x, tiles.length
+)
  - 1);%0A    final
@@ -3951,32 +3951,66 @@
 mp(endTile.y, 0,
+ Math.min(endTile.y - startTile.y,
  tiles%5B0%5D.length
@@ -4001,32 +4001,33 @@
  tiles%5B0%5D.length
+)
  - 1);%0A%0A    for 
@@ -4046,16 +4046,17 @@
 rtX; x %3C
+=
  endX; x
@@ -4091,16 +4091,17 @@
 rtY; y %3C
+=
  endY; y
@@ -4124,51 +4124,19 @@
 nal 
-BufferedImage smallImage = img.getSubimage(
+int subX = 
 (x -
@@ -4169,17 +4169,42 @@
 ().width
-,
+;%0A        final int subY =
  (y - st
@@ -4235,16 +4235,86 @@
 ).height
+;%0A%0A        final BufferedImage smallImage = img.getSubimage(subX, subY
 , map.ge

Fix indexing issue with the ColorLayer.