package com.kreative.paint.document.tile;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import com.kreative.paint.document.undo.Atom;
import com.kreative.paint.document.undo.History;
import com.kreative.paint.document.undo.Recordable;
public class TileSurface implements Cloneable, Recordable, PaintSurface {
private int x;
private int y;
private int tws, twa, twm;
private int ths, tha, thm;
private int matte;
private Map<Long,Tile> tiles;
private History history;
private List<TileSurfaceListener> listeners;
private TileListener tileListener;
public TileSurface(int x, int y, int tileWidth, int tileHeight, int matte) {
if (tileWidth < 4 || tileWidth > 16) throw new IllegalArgumentException("tileWidth = " + tileWidth);
if (tileHeight < 4 || tileHeight > 16) throw new IllegalArgumentException("tileHeight = " + tileHeight);
this.x = x;
this.y = y;
this.tws = tileWidth;
this.twa = 1 << tileWidth;
this.twm = (1 << tileWidth) - 1;
this.ths = tileHeight;
this.tha = 1 << tileHeight;
this.thm = (1 << tileHeight) - 1;
this.matte = matte;
this.tiles = new HashMap<Long,Tile>();
this.history = null;
this.listeners = new ArrayList<TileSurfaceListener>();
this.tileListener = new TileSurfaceTileListener(this);
}
private TileSurface(TileSurface o) {
this.x = o.x;
this.y = o.y;
this.tws = o.tws;
this.twa = o.twa;
this.twm = o.twm;
this.ths = o.ths;
this.tha = o.tha;
this.thm = o.thm;
this.matte = o.matte;
this.tiles = new HashMap<Long,Tile>();
for (Map.Entry<Long,Tile> e : o.tiles.entrySet()) {
this.tiles.put(e.getKey(), e.getValue().clone());
}
this.history = null;
this.listeners = new ArrayList<TileSurfaceListener>();
this.tileListener = new TileSurfaceTileListener(this);
for (Tile t : this.tiles.values()) {
t.addTileListener(this.tileListener);
}
}
@Override
public TileSurface clone() {
return new TileSurface(this);
}
@Override
public History getHistory() {
return history;
}
@Override
public void setHistory(History history) {
this.history = history;
for (Tile t : tiles.values()) t.setHistory(history);
}
public void addTileSurfaceListener(TileSurfaceListener l) {
listeners.add(l);
}
public void removeTileSurfaceListener(TileSurfaceListener l) {
listeners.remove(l);
}
public TileSurfaceListener[] getTileSurfaceListeners() {
return listeners.toArray(new TileSurfaceListener[listeners.size()]);
}
protected void notifyTileSurfaceListeners(int id) {
if (listeners.isEmpty()) return;
TileSurfaceEvent e = new TileSurfaceEvent(id, this);
switch (id) {
case TileSurfaceEvent.TILE_SURFACE_LOCATION_CHANGED:
for (TileSurfaceListener l : listeners)
l.tileSurfaceLocationChanged(e);
break;
case TileSurfaceEvent.TILE_SURFACE_MATTE_CHANGED:
for (TileSurfaceListener l : listeners)
l.tileSurfaceMatteChanged(e);
break;
case TileSurfaceEvent.TILE_SURFACE_CONTENT_CHANGED:
for (TileSurfaceListener l : listeners)
l.tileSurfaceContentChanged(e);
break;
}
}
private static class TileSurfaceTileListener implements TileListener {
private final TileSurface ts;
public TileSurfaceTileListener(TileSurface ts) {
this.ts = ts;
}
@Override
public void tileLocationChanged(TileEvent e) {
ts.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_CONTENT_CHANGED);
}
@Override
public void tileMatteChanged(TileEvent e) {
ts.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_CONTENT_CHANGED);
}
@Override
public void tileContentChanged(TileEvent e) {
ts.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_CONTENT_CHANGED);
}
}
public int getX() { return x; }
public int getY() { return y; }
public Point getLocation() { return new Point(x, y); }
private static class LocationAtom implements Atom {
private TileSurface ts;
private int oldX, oldY;
private int newX, newY;
public LocationAtom(TileSurface ts, int newX, int newY) {
this.ts = ts;
this.oldX = ts.x; this.oldY = ts.y;
this.newX = newX; this.newY = newY;
}
@Override
public boolean canBuildUpon(Atom prev) {
return (prev instanceof LocationAtom)
&& (((LocationAtom)prev).ts == this.ts);
}
@Override
public Atom buildUpon(Atom prev) {
this.oldX = ((LocationAtom)prev).oldX;
this.oldY = ((LocationAtom)prev).oldY;
return this;
}
@Override
public void redo() {
ts.x = newX;
ts.y = newY;
ts.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_LOCATION_CHANGED);
}
@Override
public void undo() {
ts.x = oldX;
ts.y = oldY;
ts.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_LOCATION_CHANGED);
}
}
public void setX(int x) {
if (this.x == x) return;
if (history != null) history.add(new LocationAtom(this, x, y));
this.x = x;
this.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_LOCATION_CHANGED);
}
public void setY(int y) {
if (this.y == y) return;
if (history != null) history.add(new LocationAtom(this, x, y));
this.y = y;
this.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_LOCATION_CHANGED);
}
public void setLocation(Point p) {
if (this.x == p.x && this.y == p.y) return;
if (history != null) history.add(new LocationAtom(this, p.x, p.y));
this.x = p.x;
this.y = p.y;
this.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_LOCATION_CHANGED);
}
public int getTileWidth() { return tws; }
public int getTileHeight() { return ths; }
public int getMatte() { return matte; }
private static class MatteAtom implements Atom {
private TileSurface ts;
private int oldMatte;
private int newMatte;
public MatteAtom(TileSurface ts, int newMatte) {
this.ts = ts;
this.oldMatte = ts.matte;
this.newMatte = newMatte;
}
@Override
public boolean canBuildUpon(Atom prev) {
return (prev instanceof MatteAtom)
&& (((MatteAtom)prev).ts == this.ts);
}
@Override
public Atom buildUpon(Atom prev) {
this.oldMatte = ((MatteAtom)prev).oldMatte;
return this;
}
@Override
public void redo() {
ts.matte = newMatte;
ts.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_MATTE_CHANGED);
}
@Override
public void undo() {
ts.matte = oldMatte;
ts.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_MATTE_CHANGED);
}
}
public void setMatte(int matte) {
if (this.matte == matte) return;
if (history != null) history.add(new MatteAtom(this, matte));
this.matte = matte;
for (Tile t : tiles.values()) t.setMatte(matte);
this.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_MATTE_CHANGED);
}
private static class SetTileAtom implements Atom {
private TileSurface ts;
private long key;
private Tile oldTile;
private Tile newTile;
public SetTileAtom(TileSurface ts, long key, Tile newTile) {
this.ts = ts;
this.key = key;
this.oldTile = ts.tiles.get(key);
this.newTile = newTile;
}
@Override
public boolean canBuildUpon(Atom prev) {
return (prev instanceof SetTileAtom)
&& (((SetTileAtom)prev).ts == this.ts)
&& (((SetTileAtom)prev).key == this.key);
}
@Override
public Atom buildUpon(Atom prev) {
this.oldTile = ((SetTileAtom)prev).oldTile;
return this;
}
@Override
public void redo() {
if (oldTile != null) {
oldTile.setHistory(null);
oldTile.removeTileListener(ts.tileListener);
}
if (newTile != null) {
newTile.setHistory(ts.history);
newTile.addTileListener(ts.tileListener);
ts.tiles.put(key, newTile);
} else {
ts.tiles.remove(key);
}
ts.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_CONTENT_CHANGED);
}
@Override
public void undo() {
if (newTile != null) {
newTile.setHistory(null);
newTile.removeTileListener(ts.tileListener);
}
if (oldTile != null) {
oldTile.setHistory(ts.history);
oldTile.addTileListener(ts.tileListener);
ts.tiles.put(key, oldTile);
} else {
ts.tiles.remove(key);
}
ts.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_CONTENT_CHANGED);
}
}
private Tile createTile(long key, int x, int y) {
Tile t = new Tile(x << tws, y << ths, twa, tha, matte);
if (history != null) history.add(new SetTileAtom(this, key, t));
t.setHistory(history);
t.addTileListener(tileListener);
this.tiles.put(key, t);
this.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_CONTENT_CHANGED);
return t;
}
public void addTile(Tile t) {
int x = t.getX() >> tws;
int y = t.getY() >> ths;
long key = (x & 0xFFFFFFFFL) | ((y & 0xFFFFFFFFL) << 32L);
if (history != null) history.add(new SetTileAtom(this, key, t));
if (tiles.containsKey(key)) {
tiles.get(key).setHistory(null);
tiles.get(key).removeTileListener(tileListener);
}
t.setHistory(history);
t.addTileListener(tileListener);
this.tiles.put(key, t);
this.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_CONTENT_CHANGED);
}
public Tile getTile(int x, int y, boolean create) {
x = ((x - this.x) >> tws);
y = ((y - this.y) >> ths);
long key = (x & 0xFFFFFFFFL) | ((y & 0xFFFFFFFFL) << 32L);
if (tiles.containsKey(key)) return tiles.get(key);
else if (create) return createTile(key, x, y);
else return null;
}
public Collection<Tile> getTiles(int x, int y, int width, int height, boolean create) {
Collection<Tile> intersectingTiles = new HashSet<Tile>();
if (width < 0) { x += width; width = -width; }
if (height < 0) { y += height; height = -height; }
int left = ((x - this.x - 8) >> tws);
int top = ((y - this.y - 8) >> ths);
int right = ((x - this.x + width + 8) >> tws);
int bottom = ((y - this.y + height + 8) >> ths);
for (int ky = top; ky <= bottom; ky++) {
for (int kx = left; kx <= right; kx++) {
long key = (kx & 0xFFFFFFFFL) | ((ky & 0xFFFFFFFFL) << 32L);
if (tiles.containsKey(key)) intersectingTiles.add(tiles.get(key));
else if (create) intersectingTiles.add(createTile(key, kx, ky));
}
}
return intersectingTiles;
}
public Collection<Tile> getTiles() {
return tiles.values();
}
public void paint(Graphics2D g) {
for (Tile t : tiles.values()) t.paint(g, x, y);
}
public void paint(Graphics2D g, int x, int y) {
for (Tile t : tiles.values()) t.paint(g, this.x + x, this.y + y);
}
@Override public int getMinX() { return Integer.MIN_VALUE; }
@Override public int getMinY() { return Integer.MIN_VALUE; }
@Override public int getMaxX() { return Integer.MAX_VALUE; }
@Override public int getMaxY() { return Integer.MAX_VALUE; }
@Override public boolean contains(int x, int y) { return true; }
@Override public boolean contains(int x, int y, int width, int height) { return true; }
@Override
public int getRGB(int x, int y) {
Tile t = getTile(x, y, false);
if (t == null) return matte;
return t.getRGB(
t.getX() + ((x - this.x) & twm),
t.getY() + ((y - this.y) & thm)
);
}
@Override
public int[] getRGB(int x, int y, int width, int height, int[] rgb, int offset, int rowCount) {
if (rgb == null) rgb = new int[offset + height * rowCount];
for (int ay = offset, iy = 0; iy < height; ay += rowCount, iy++) {
for (int ax = ay, ix = 0; ix < width; ax++, ix++) {
rgb[ax] = matte;
}
}
Collection<Tile> tt = getTiles(x, y, width, height, false);
x -= this.x;
y -= this.y;
for (Tile t : tt) {
int tx = t.getX();
int ty = t.getY();
int x1 = x - tx; if (x1 < 0) x1 = 0; else if (x1 > twa) x1 = twa;
int y1 = y - ty; if (y1 < 0) y1 = 0; else if (y1 > tha) y1 = tha;
int x2 = x + width - tx; if (x2 < 0) x2 = 0; else if (x2 > twa) x2 = twa;
int y2 = y + height - ty; if (y2 < 0) y2 = 0; else if (y2 > tha) y2 = tha;
if (x1 == x2 || y1 == y2) continue;
int xo = tx - x; if (xo < 0) xo = 0;
int yo = ty - y; if (yo < 0) yo = 0;
t.getRGB(tx + x1, ty + y1, x2 - x1, y2 - y1, rgb, offset + (yo * rowCount) + xo, rowCount);
}
return rgb;
}
@Override
public void setRGB(int x, int y, int rgb) {
Tile t = getTile(x, y, true);
t.setRGB(
t.getX() + ((x - this.x) & twm),
t.getY() + ((y - this.y) & thm),
rgb
);
}
@Override
public void setRGB(int x, int y, int rgb, Shape clip) {
if (clip == null || clip.contains(x, y)) {
this.setRGB(x, y, rgb);
}
}
@Override
public void setRGB(int x, int y, int width, int height, int[] rgb, int offset, int rowCount) {
Collection<Tile> tt = getTiles(x, y, width, height, true);
x -= this.x;
y -= this.y;
for (Tile t : tt) {
int tx = t.getX();
int ty = t.getY();
int x1 = x - tx; if (x1 < 0) x1 = 0; else if (x1 > twa) x1 = twa;
int y1 = y - ty; if (y1 < 0) y1 = 0; else if (y1 > tha) y1 = tha;
int x2 = x + width - tx; if (x2 < 0) x2 = 0; else if (x2 > twa) x2 = twa;
int y2 = y + height - ty; if (y2 < 0) y2 = 0; else if (y2 > tha) y2 = tha;
if (x1 == x2 || y1 == y2) continue;
int xo = tx - x; if (xo < 0) xo = 0;
int yo = ty - y; if (yo < 0) yo = 0;
t.setRGB(tx + x1, ty + y1, x2 - x1, y2 - y1, rgb, offset + (yo * rowCount) + xo, rowCount);
}
}
@Override
public void setRGB(int x, int y, int width, int height, int[] rgb, int offset, int rowCount, Shape clip) {
if (clip == null || clip.contains(x, y, width, height)) {
this.setRGB(x, y, width, height, rgb, offset, rowCount);
} else {
int[] oldRGB = new int[width * height];
this.getRGB(x, y, width, height, oldRGB, 0, width);
for (int oy = 0, ny = offset, ay = y, iy = 0; iy < height; oy += width, ny += rowCount, ay++, iy++) {
for (int ox = oy, nx = ny, ax = x, ix = 0; ix < width; ox++, nx++, ax++, ix++) {
if (clip.contains(ax, ay)) oldRGB[ox] = rgb[nx];
}
}
this.setRGB(x, y, width, height, oldRGB, 0, width);
}
}
@Override
public void clear(int x, int y, int width, int height) {
int[] rgb = new int[width * height];
for (int i = 0; i < rgb.length; i++) rgb[i] = matte;
this.setRGB(x, y, width, height, rgb, 0, width);
}
@Override
public void clear(int x, int y, int width, int height, Shape clip) {
if (clip == null || clip.contains(x, y, width, height)) {
this.clear(x, y, width, height);
} else {
int[] oldRGB = new int[width * height];
this.getRGB(x, y, width, height, oldRGB, 0, width);
for (int oy = 0, ay = y, iy = 0; iy < height; oy += width, ay++, iy++) {
for (int ox = oy, ax = x, ix = 0; ix < width; ox++, ax++, ix++) {
if (clip.contains(ax, ay)) oldRGB[ox] = matte;
}
}
this.setRGB(x, y, width, height, oldRGB, 0, width);
}
}
@Override
public void clearAll() {
if (history != null) {
for (long key : tiles.keySet()) {
history.add(new SetTileAtom(this, key, null));
}
}
for (Tile t : tiles.values()) {
t.setHistory(null);
t.removeTileListener(tileListener);
}
this.tiles.clear();
this.notifyTileSurfaceListeners(TileSurfaceEvent.TILE_SURFACE_CONTENT_CHANGED);
}
@Override
public Graphics2D createPaintGraphics() {
return new TileSurfaceGraphics(this, this.x, this.y);
}
}