public final class RawImage
{
	private static int[] TMP;
	
	public static enum MaskMode
	{
		REP, ADD, SUB, XOR, AND;
		
		public boolean transform(boolean bool)
		{
			switch(this)
			{
				case REP:
				case ADD:
					return true;
				case SUB:
					return false;
				case XOR:
					return !bool;
				case AND:
					return bool;
			}
			throw new IllegalStateException("Unreachable");
		}
	}
	
	private int[] buffer;
	public final int width, height;
	
	private boolean[] mask;
	
	public RawImage(int width, int height)
	{
		this.width = width;
		this.height = height;
		this.buffer = new int[width * height];
	}
	
	public RawImage(int width, int height, int[] buffer)
	{
		this.width = width;
		this.height = height;
		if(buffer.length != width * height)
			throw new IllegalArgumentException("Buffer length must be `width*height`");
		this.buffer = buffer;
	}
	
	public RawImage(int width, int height, int[] buffer, boolean[] mask)
	{
		this.width = width;
		this.height = height;
		if(buffer.length != width * height || buffer.length != mask.length)
			throw new IllegalArgumentException("Buffer length must be `width*height`");
		this.buffer = buffer;
		this.mask = mask;
	}
	
	// Returns or allocates a temporary buffer with a size of at least width*height.
	private static int[] get_tmp(int width, int height)
	{
		if(TMP == null || TMP.length < width * height)
		{
			TMP = new int[width * height];
		}
		return TMP;
	}
	
	public void move(int dx, int dy)
	{
		final int offset = dx + dy * width;
		if(Math.abs(dx) >= width || Math.abs(dy) >= height)
		{
			this.fill(0); // Fill, not clear, so the mask is respected.
			return;
		}
		else if(offset == 0)
			return;
		
		int[] tmp = get_tmp(width, height);
		
		int src = 0;
		int dst = offset;
		if(offset < 0)
		{
			src = -offset;
			dst = 0;
		}
		final int len = buffer.length - (src + dst);
		
		// Copy whole buffer to tmp.
		System.arraycopy(buffer, 0, tmp, 0, buffer.length);
		
		// Copy back from tmp
		if(mask == null)
		{
			// Do x-axis bounds clipping.
			if(dx < 0)
			{
				for(int y = 0; y < height; y++)
				{
					Arrays.fill(tmp, y * width, y * width - dx, 0);
				}
			}
			else if(dx > 0)
			{
				for(int y = 1; y <= height; y++)
				{
					Arrays.fill(tmp, y * width - dx, y * width, 0);
				}
			}
			
			// Do the actual copying.
			System.arraycopy(tmp, src, buffer, dst, len);
			
			// Do y-axis bounds clipping.
			Arrays.fill(buffer, 0, dst, 0); // Cut out top.
			Arrays.fill(buffer, buffer.length - src, buffer.length, 0); // Cut out bottom
		}
		else
		{
			this.fill(0); // Clear the old pixels. Only necessary when mask is enabled.
			
			// First translate the mask.
			System.arraycopy(mask, src, mask, dst, len);
			// Then do bounds clipping.
			Arrays.fill(mask, 0, dst, false); // Cut out top.
			Arrays.fill(mask, buffer.length - src, buffer.length, false); // Cut out bottom
			if(dx < 0) // Cut out whatever side.
			{
				for(int y = 1; y <= height; y++)
				{
					Arrays.fill(mask, y * width + dx, y * width, false);
				}
			}
			else if(dx > 0)
			{
				for(int y = 0; y < height; y++)
				{
					Arrays.fill(mask, y * width, y * width + dx, false);
				}
			}
			
			// Now we can copy the data back, if it's not masked out.
			for(int i = 0; i < len; i++)
			{
				if(mask[dst + i])
					buffer[dst + i] = tmp[src + i];
			}
		}
	}
	
	// Drawing functions
	
	public void drawLine(int x1, int y1, final int x2, final int y2, final int c)
	{
		final int dx = Math.abs(x2 - x1);
		final int dy = Math.abs(y2 - y1);
		final int sx = (x1 < x2) ? 1 : -1;
		final int sy = (y1 < y2) ? 1 : -1;
		int err = dx - dy;
		do
		{
			drawPixelChecked(x1, y1, c);
			final int e2 = 2 * err;
			if(e2 > -dy)
			{
				err = err - dy;
				x1 = x1 + sx;
			}
			if(e2 < dx)
			{
				err = err + dx;
				y1 = y1 + sy;
			}
		}
		while(!(x1 == x2 && y1 == y2));
		drawPixelChecked(x2, y2, c);
	}
	
	public void drawRect(int x1, int y1, int x2, int y2, final int c)
	{
		// Do the clamping once at the start so we don't have to perform checks when drawing the pixel.
		x1 = MathUtils.clamp(x1, 0, width - 1);
		x2 = MathUtils.clamp(x2, 0, width - 1);
		y1 = MathUtils.clamp(y1, 0, height - 1);
		y2 = MathUtils.clamp(y2, 0, height - 1);
		
		if(mask == null)
		{
			// top
			final int ix = y1 * width;
			Arrays.fill(buffer, ix + x1, ix + x2 + 1, c);
			
			// bottom
			final int jx = y2 * width;
			Arrays.fill(buffer, jx + x1, jx + x2 + 1, c);
			
			for(int i = y1 + 1; i < y2; i++)
			{
				setPixel(x1, i, c);
				setPixel(x2, i, c);
			}
		}
		else
		{
			// top
			final int ix = y1 * width;
			for(int k = ix + x1; k < ix + x2 + 1; k++)
			{
				if(mask[k])
					buffer[k] = c;
			}
			
			// bottom
			final int jx = y2 * width;
			for(int k = jx + x1; k < jx + x2 + 1; k++)
			{
				if(mask[k])
					buffer[k] = c;
			}
			
			for(int i = y1 + 1; i < y2; i++)
			{
				drawPixel(x1, i, c);
				drawPixel(x2, i, c);
			}
		}
	}
	
	public void fillRect(int x1, int y1, int x2, int y2, int c)
	{
		x1 = MathUtils.clamp(x1, 0, width - 1);
		x2 = MathUtils.clamp(x2, 0, width - 1);
		y1 = MathUtils.clamp(y1, 0, height - 1);
		y2 = MathUtils.clamp(y2, 0, height - 1);
		
		if(mask == null)
		{
			for(; y1 <= y2; y1++)
			{
				final int k = y1 * width;
				Arrays.fill(buffer, k + x1, k + x2 + 1, c);
			}
		}
		else
		{
			for(; y1 <= y2; y1++)
			{
				final int offset = y1 * width;
				for(int k = offset + x1; k < offset + x2 + 1; k++)
				{
					if(mask[k])
						buffer[k] = c;
				}
			}
		}
	}
	
	public void drawPixel(int x, int y, int c)
	{
		final int loc = index(x, y);
		if(mask == null || mask[loc])
		{
			buffer[loc] = c;
		}
	}
	
	public void drawPixelChecked(int x, int y, int c)
	{
		if(x < 0 || y < 0 || x >= width || y >= height)
			return;
		final int loc = index(x, y);
		if(mask == null || mask[loc])
		{
			buffer[loc] = c;
		}
	}
	
	public void fill(int c)
	{
		if(mask == null)
		{
			this.clear(c);
		}
		else
		{
			for(int i = 0; i < buffer.length; i++)
			{
				if(mask[i])
					buffer[i] = c;
			}
		}
	}
	
	// Mask Manipulation
	
	public void fillMask(MaskMode mode)
	{
		if(this.mask == null)
			return;
		switch(mode)
		{
			case REP:
			case ADD:
				Arrays.fill(mask, true);
				break;
			case SUB:
				Arrays.fill(mask, false);
				break;
			case XOR:
				for(int i = 0; i < mask.length; i++)
					mask[i] = !mask[i];
				break;
			case AND:
				break;
		}
	}
	
	public void maskRect(int x1, int y1, int x2, int y2, MaskMode mode)
	{
		x1 = MathUtils.clamp(x1, 0, width - 1);
		x2 = MathUtils.clamp(x2, 0, width - 1);
		y1 = MathUtils.clamp(y1, 0, height - 1);
		y2 = MathUtils.clamp(y2, 0, height - 1);
		
		switch(mode)
		{
			case REP:
				this.fillMask(MaskMode.SUB);
			case ADD:
				for(; y1 <= y2; y1++)
				{
					final int k = y1 * width;
					Arrays.fill(mask, k + x1, k + x2 + 1, true);
				}
				break;
			case SUB:
				for(; y1 <= y2; y1++)
				{
					final int k = y1 * width;
					Arrays.fill(mask, k + x1, k + x2 + 1, false);
				}
				break;
			case XOR:
				for(; y1 <= y2; y1++)
				{
					final int offset = y1 * width;
					for(int k = offset + x1; k < offset + x2 + 1; k++)
					{
						mask[k] = !mask[k];
					}
				}
				break;
			case AND:
				// Before Rectangle
				final int offset = y1 * width + x1;
				Arrays.fill(mask, 0, offset, false);
				
				// After Rectangle
				final int offset2 = y2 * width + x2 + 1;
				Arrays.fill(mask, offset2, mask.length, false);
				
				// Within Rectangle
				final int onstep = x2 - x1 + 1;
				final int offstep = width - onstep;
				
				for(int i = offset + onstep; i < offset2; i += width)
					Arrays.fill(mask, i, i + offstep, false);
				break;
		}
	}
	
	public void setMaskEnabled(boolean enabled)
	{
		if(enabled)
		{
			if(mask == null)
				mask = new boolean[buffer.length];
		}
		else if(mask != null)
		{
			mask = null;
		}
	}
	
	public void toggleMask()
	{
		if(mask == null)
		{
			mask = new boolean[buffer.length];
		}
		else
		{
			mask = null;
		}
	}
	
	public boolean[] copyMask()
	{
		return mask == null ? null : Arrays.copyOf(mask, mask.length);
	}
	
	public boolean[] borrowMask()
	{
		return mask;
	}
	
	public void setMask(boolean[] mask)
	{
		this.mask = mask;
	}
	
	// Buffer Manipulation
	
	public void clear(int c)
	{
		Arrays.fill(buffer, c);
	}
	
	public void setPixel(int x, int y, int c)
	{
		buffer[index(x, y)] = c;
	}
	
	public int getPixel(int x, int y)
	{
		return buffer[index(x, y)];
	}
	
	public int getIndex(int x, int y)
	{
		return index(x, y);
	}
	
	private int index(int x, int y)
	{
		return y * width + x;
	}
	
	public int[] copyBuffer()
	{
		return Arrays.copyOf(buffer, buffer.length);
	}
	
	public int[] borrowBuffer()
	{
		return buffer;
	}
	
	public void setBuffer(int[] buffer)
	{
		this.buffer = buffer;
	}
	
	// Create a RawImage which has direct access to the pixels of the BufferedImage.
	// This could be quite unreliable.
	public static RawImage unwrapBufferedImage(BufferedImage image)
	{
		return new RawImage(image.getWidth(), image.getHeight(), ((DataBufferInt) image.getRaster().getDataBuffer()).getData());
	}
	
	public static RawImage fromBufferedImage(BufferedImage image)
	{
		return new RawImage(image.getWidth(), image.getHeight(), image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth()));
	}
	
	public BufferedImage toBufferedImage()
	{
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		image.setRGB(0, 0, width, height, buffer, 0, width);
		return image;
	}
	
	public static RawImage copyOf(RawImage image)
	{
		if(image.mask == null)
			return new RawImage(image.width, image.height, image.copyBuffer());
		return new RawImage(image.width, image.height, image.copyBuffer(), image.copyMask());
	}
	
	public void copyRegion(RawImage image)
	{
		if(image == this)
			return;
		if(this.buffer.length != image.buffer.length)
			throw new RuntimeException("Cannot copy from a different sized RawImage");
		
		if(this.mask == null)
		{
			System.arraycopy(image.buffer, 0, buffer, 0, buffer.length);
		}
		else
		{
			for(int i = 0; i < buffer.length; i++)
			{
				if(mask[i])
					this.buffer[i] = image.buffer[i];
			}
		}
	}
	
	public void copyFrom(RawImage image, boolean withMask)
	{
		if(image == this)
			return;
		if(this.buffer.length != image.buffer.length)
			throw new RuntimeException("Cannot copy from a different sized RawImage");
		System.arraycopy(image.buffer, 0, buffer, 0, buffer.length);
		if(withMask)
		{
			if(image.mask == null)
				this.mask = null;
			else if(this.mask == null)
				this.mask = Arrays.copyOf(image.mask, image.mask.length);
			else
				System.arraycopy(image.mask, 0, mask, 0, mask.length);
		}
	}
	
	public void copyMaskFrom(RawImage image)
	{
		if(image == this)
			return;
		
		if(image.mask == null)
			this.mask = null;
		else if(this.mask == null)
			this.mask = Arrays.copyOf(image.mask, image.mask.length);
		else if(this.mask.length != image.mask.length)
			throw new RuntimeException("Cannot copy from a different sized RawImage");
		else
			System.arraycopy(image.mask, 0, mask, 0, mask.length);
	}
	
	public void dispose()
	{
		buffer = null;
		mask = null;
	}
	
	public boolean isMaskEnabled()
	{
		return mask != null;
	}
} public abstract class SingleChange extends SerialisedChange
{
	public abstract SingleChange getInstance();
	
	@Override
	public final void write(DataOutputStream out) throws IOException
	{
	}
	
	@Override
	public final void read(DataInputStream in) throws IOException
	{
	}
} public class Marker implements IMarker
{
	public IChange decode()
	{
		throw new IllegalStateException("Cannot decode a Marker");
	}
		
	public void write(DataOutputStream out) throws IOException
	{
			
	}
		
	public void read(DataInputStream in) throws IOException
	{
			
	}
} public class ClearMaskChange implements IEditChange, IMaskChange, Serialised
{
	public ClearMaskChange()
	{
		
	}
	
	@Override
	public void apply(RawImage image)
	{
		image.setMaskEnabled(false);
	}
	
	@Override
	public ClearMaskChange encode()
	{
		return this;
	}
	
	@Override
	public ClearMaskChange decode()
	{
		return this;
	}
	
	@Override
	public void write(DataOutputStream out) throws IOException
	{
	}
	
	@Override
	public void read(DataInputStream in) throws IOException
	{
	}
} public class DrawPathChange extends IPathChange
{
	public static DrawPathChange instance = new DrawPathChange();
	
	public DrawPathChange()
	{
		
	}
	
	@Override
	public void point(RawImage image, int x, int y, int c)
	{
		image.drawPixelChecked(x, y, c);
	}
	
	@Override
	public void line(RawImage image, int x1, int y1, int x2, int y2, int c)
	{
		image.drawLine(x1, y1, x2, y2, c);
	}
	
	@Override
	public SingleChange getInstance()
	{
		return instance;
	}
} public class FillImageChange implements IEditChange, Serialised
{
	private int colour;
	
	public FillImageChange()
	{
		
	}
	
	public FillImageChange(int colour)
	{
		this.colour = colour;
	}
	
	@Override
	public void apply(RawImage image)
	{
		image.fill(colour);
	}
	
	@Override
	public FillImageChange encode()
	{
		return this;
	}
	
	@Override
	public FillImageChange decode()
	{
		return this;
	}
	
	@Override
	public void write(DataOutputStream out) throws IOException
	{
		out.writeInt(colour);
	}
	
	@Override
	public void read(DataInputStream in) throws IOException
	{
		colour = in.readInt();
	}
} public class FillMaskChange implements IEditChange, IMaskChange, Serialised
{
	private MaskMode mode;
	
	public FillMaskChange()
	{
		
	}
	
	public FillMaskChange(MaskMode mode)
	{
		this.mode = mode;
	}
	
	@Override
	public void apply(RawImage image)
	{
		image.setMaskEnabled(true);
		image.fillMask(mode);
	}
	
	@Override
	public FillMaskChange encode()
	{
		return this;
	}
	
	@Override
	public FillMaskChange decode()
	{
		return this;
	}
	
	@Override
	public void write(DataOutputStream out) throws IOException
	{
		out.writeInt(mode.ordinal());
	}
	
	@Override
	public void read(DataInputStream in) throws IOException
	{
		int mode = in.readInt();
		for(MaskMode m : MaskMode.values())
		{
			if(m.ordinal() == mode)
			{
				this.mode = m;
				break;
			}
		}
	}
} public class PathChange implements IEditChange
{
	public static abstract class IPathChange extends SingleChange
	{
		public abstract void point(RawImage image, int x, int y, int colour);
		
		public abstract void line(RawImage image, int x1, int y1, int x2, int y2, int colour);
		
		public static void lineToPoints(IPathChange path, RawImage image, int x1, int y1, int x2, int y2, int colour)
		{
			final int dx = Math.abs(x2 - x1);
			final int dy = Math.abs(y2 - y1);
			final int sx = (x1 < x2) ? 1 : -1;
			final int sy = (y1 < y2) ? 1 : -1;
			int err = dx - dy;
			do
			{
				path.point(image, x1, y1, colour);
				final int e2 = 2 * err;
				if(e2 > -dy)
				{
					err = err - dy;
					x1 = x1 + sx;
				}
				if(e2 < dx)
				{
					err = err + dx;
					y1 = y1 + sy;
				}
			}
			while(!(x1 == x2 && y1 == y2));
			path.point(image, x2, y2, colour);
		}
	}
	
	protected IPathChange change;
	protected ArrayList<Point> points = new ArrayList<Point>();
	protected int colour;
	
	public PathChange(short x, short y, int colour, IPathChange change)
	{
		this(new Point(x, y), colour, change);
	}
	
	public PathChange(Point p, int colour, IPathChange change)
	{
		this.colour = colour;
		points.add(p);
		this.change = change;
	}
	
	public boolean moveTo(short x, short y)
	{
		return this.moveTo(new Point(x, y));
	}
	
	public boolean moveTo(Point p)
	{
		if(points.get(points.size() - 1).equals(p))
			return false;
		points.add(p);
		return true;
	}
	
	@Override
	public Serial encode()
	{
		short[] data = new short[points.size() * 2];
		int i = 0;
		for(Point p : points)
		{
			data[i++] = (short) p.x;
			data[i++] = (short) p.y;
		}
		return new Serial(data, colour, change.encode());
	}
	
	@Override
	public void apply(RawImage image)
	{
		if(points.size() == 1)
		{
			Point p = points.get(0);
			change.point(image, p.x, p.y, colour);
		}
		else
		{
			// Fill in lines first
			for(int i = 1; i < points.size(); i++)
			{
				Point p1 = points.get(i - 1);
				Point p2 = points.get(i);
				
				change.line(image, p1.x, p1.y, p2.x, p2.y, colour);
			}
			
			// Points after
			for(Point p : points)
			{
				change.point(image, p.x, p.y, colour);
			}
		}
	}
	
	public static class Serial implements Serialised
	{
		private short[] points;
		private int colour;
		private Serialised change;
		
		public Serial()
		{
			
		}
		
		public Serial(short[] data, int colour, Serialised change)
		{
			this.colour = colour;
			this.points = data;
			this.change = change;
		}
		
		@Override
		public PathChange decode()
		{
			PathChange change = new PathChange(points[0], points[1], colour, (IPathChange) this.change.decode());
			for(int i = 2; i < points.length; i += 2)
				change.moveTo(points[i], points[i + 1]);
			return change;
		}
		
		@Override
		public void write(DataOutputStream out) throws IOException
		{
			out.writeByte(HistoryIO.getChangeID(change.getClass()));
			change.write(out);
			out.writeInt(colour);
			out.writeInt(points.length);
			for(short s : points)
				out.writeShort(s);
		}
		
		@Override
		public void read(DataInputStream in) throws IOException
		{
			change = HistoryIO.create(in.readByte());
			change.read(in);
			colour = in.readInt();
			points = new short[in.readInt()];
			for(int i = 0; i < points.length; i++)
				points[i] = in.readShort();
		}
	}
} public class ResizeCanvasChange extends SerialisedChange implements IResizingChange
{
	private short width, height;
	
	public ResizeCanvasChange(int width, int height)
	{
		this.width = (short) width;
		this.height = (short) height;
	}
	
	@Override
	public void write(DataOutputStream out) throws IOException
	{
		out.writeShort(width);
		out.writeShort(height);
	}
	
	@Override
	public void read(DataInputStream in) throws IOException
	{
		width = in.readShort();
		height = in.readShort();
	}
	
	@Override
	public RawImage apply(RawImage image)
	{
		RawImage newImage = new RawImage(width, height);
		int[] buffer = image.borrowBuffer();
		int[] newBuffer = newImage.borrowBuffer();
		int w = Math.min(width, image.width);
		int h = Math.min(height, image.height);
		for(int j = 0; j < h; j++)
		{
			System.arraycopy(buffer, j * image.width, newBuffer, j * width, w);
		}
		return newImage;
	}
	
	@Override
	public int getWidth()
	{
		return width;
	}
	
	@Override
	public int getHeight()
	{
		return height;
	}
} public class SetImageChange extends SerialisedChange implements IImageChange
{
	private int[] buffer;
	
	public SetImageChange()
	{
		
	}
	
	public SetImageChange(int[] buffer)
	{
		this.buffer = buffer;
	}
	
	@Override
	public RawImage apply(RawImage image)
	{
		image.setBuffer(buffer);
		return image;
	}
	
	@Override
	public void write(DataOutputStream out) throws IOException
	{
		out.writeInt(buffer.length);
		for(int i = 0; i < buffer.length; i++)
			out.writeInt(buffer[i]);
	}
	
	@Override
	public void read(DataInputStream in) throws IOException
	{
		buffer = new int[in.readInt()];
		for(int i = 0; i < buffer.length; i++)
			buffer[i] = in.readInt();
	}
} public class SetMaskChange extends SerialisedChange implements IImageChange
{
	private boolean[] mask;
	
	public SetMaskChange()
	{
		
	}
	
	public SetMaskChange(boolean[] mask)
	{
		this.mask = mask;
	}
	
	@Override
	public RawImage apply(RawImage image)
	{
		image.setMask(mask);
		return image;
	}
	
	@Override
	public void write(DataOutputStream out) throws IOException
	{
		out.writeInt(mask.length);
		for(int i = 0; i < mask.length; i++)
			out.writeBoolean(mask[i]);
	}
	
	@Override
	public void read(DataInputStream in) throws IOException
	{
		mask = new boolean[in.readInt()];
		for(int i = 0; i < mask.length; i++)
			mask[i] = in.readBoolean();
	}
} public class IOUtils
{
	private static Class<?> MAIN_CLASS;
	
	private IOUtils()
	{
		
	}
	
	public static void setMainClass(Class<?> c)
	{
		MAIN_CLASS = c;
	}
	
	public static String jarDir()
	{
		try
		{
			return new File(MAIN_CLASS.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent();
		}
		catch(URISyntaxException e)
		{
			e.printStackTrace();
			return null;
		}
	}
	
	public static String jarPath()
	{
		try
		{
			return MAIN_CLASS.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
		}
		catch(URISyntaxException e)
		{
			e.printStackTrace();
			return null;
		}
	}
	
	public static String assemblePath(String... path)
	{
		String fullPath = "";
		for(String p : path)
		{
			fullPath += File.separator + p;
		}
		return fullPath;
	}
	
	public static String fileToString(String path)
	{
		StringBuffer sBuffer = new StringBuffer();
		try
		{
			BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path)));
			char[] buffer = new char[1024];
			
			int c;
			
			while((c = reader.read(buffer, 0, buffer.length)) != -1)
			{
				sBuffer.append(buffer, 0, c);
			}
			reader.close();
		}
		catch(IOException e)
		{
			e.printStackTrace();
		}
		
		return sBuffer.toString();
	}
	
	public static String fileToStringOneLine(String path)
	{
		String string = new String();
		try
		{
			BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path)));
			
			String line;
			while((line = reader.readLine()) != null)
			{
				string += line;
			}
			reader.close();
		}
		catch(IOException e)
		{
			e.printStackTrace();
		}
		
		return string;
	}
	
	public static String relativeFrom(File from, File to)
	{
		return from.toURI().relativize(to.toURI()).getPath();
	}
} public class RandomUtils
{
	private static final Random r = new Random();
	
	public static int rInt()
	{
		return r.nextInt();
	}
	
	public static int rInt(int max)
	{
		return rInt(r, max);
	}
	
	public static int rInt(Random r, int max)
	{
		return r.nextInt(max);
	}
	
	public static int rInt(int min, int max)
	{
		return rInt(r, min, max);
	}
	
	public static int rInt(Random r, int min, int max)
	{
		return r.nextInt(max - min + 1) + min;
	}
	
	public static long rLong()
	{
		return r.nextLong();
	}
}
import heroesgrave.spade.image.RawImage;
import heroesgrave.spade.image.change.SingleChange;
import heroesgrave.spade.image.change.IMarker.Marker;
import heroesgrave.spade.image.change.edit.ClearMaskChange;
import heroesgrave.spade.image.change.edit.DrawPathChange;
import heroesgrave.spade.image.change.edit.FillImageChange;
import heroesgrave.spade.image.change.edit.FillMaskChange;
import heroesgrave.spade.image.change.edit.PathChange;
import heroesgrave.spade.image.change.edit.ResizeCanvasChange;
import heroesgrave.spade.image.change.edit.SetImageChange;
import heroesgrave.spade.image.change.edit.SetMaskChange;
import heroesgrave.utils.io.IOUtils;
import heroesgrave.utils.misc.RandomUtils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
			return null;
		}
	}
	
	public static RawImage readBuffer(DataInputStream in) throws IOException
	{
		int width = in.readInt();
		int height = in.readInt();
		int[] buffer = new int[width * height];
		for(int i = 0; i < buffer.length; i++)
			buffer[i] = in.readInt();
		if(in.readBoolean())
		{
			boolean[] mask = new boolean[width * height];
			for(int i = 0; i < mask.length; i++)
				mask[i] = in.readBoolean();
			return new RawImage(width, height, buffer, mask);
		}
		else
		{
			return new RawImage(width, height, buffer);
		}
	}
	
	public static void read(DataInputStream in, LinkedList<Serialised> changes) throws IOException
	{
		try
		{
			int ID;
			while((ID = in.read()) != -1)
			{
				Class<? extends Serialised> c = classes.get(ID);
				Serialised change = c.newInstance();
				if(change instanceof SingleChange)
				{
					change = ((SingleChange) change).getInstance();
				}
				change.read(in);
				changes.addLast(change);
			}
		}
		catch(IllegalAccessException | InstantiationException e)
		{
			e.printStackTrace();
			throw new RuntimeException("A reflection error occured while reading the old history from file. Perhaps it was corrupted?");
		}
	}
	
	public void write(LinkedList<Serialised> changes, RawImage a, RawImage b)
	{
		try
		{
			File f = randomFile("his");
			f.deleteOnExit();
			
			DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
			
			writeBuffer(out, a);
			writeBuffer(out, b);
			
			write(out, changes);
			
			out.flush();
			out.close();
			
			files.push(f);
		}
		catch(IOException e)
		{
			e.printStackTrace();
			System.out.println("An error occured while writing the old history to file");
		}
	}
	
	private static void writeBuffer(DataOutputStream out, RawImage image) throws IOException
	{
		out.writeInt(image.width);
		out.writeInt(image.height);
		int[] buffer = image.borrowBuffer();
		for(int i = 0; i < buffer.length; i++)
			out.writeInt(buffer[i]);
		if(image.isMaskEnabled())
		{
			out.writeBoolean(true);
			boolean[] mask = image.borrowMask();
			for(int i = 0; i < mask.length; i++)
				out.writeBoolean(mask[i]);
		}
		else
		{
			out.writeBoolean(false);
		}
	}
	
	public static void write(DataOutputStream out, LinkedList<Serialised> changes) throws IOException
	{
		while(!changes.isEmpty())
		{
			Serialised change = changes.pop();
			Class<? extends Serialised> c = change.getClass();
			if(!classes.contains(c))
			{
				System.err.println("Unregistered Serialised Type: " + c);
			}
			else
			{
				out.writeByte(getChangeID(change.getClass()));
				change.write(out);
			}
		}
	}
	
	static
	{
		classes.add(Marker.class);
		
		classes.add(DrawPathChange.class);
		classes.add(PathChange.Serial.class);
		
		classes.add(SetImageChange.class);
		classes.add(SetMaskChange.class);