
struct Color {
	uint data;

	// I forget, is this still implicitly defined when the other two ctors exist?
	this(uint data) {
		this.data = data;
	}
	this(ubyte r, ubyte g, ubyte b) {
		this.data = (r << 24) | (g << 16) | (b << 8);
	}
	this(ubyte r, ubyte g, ubyte b, ubyte a) {
		this.data = (r << 24) | (g << 16) | (b << 8) | a;
	}
	
	@property ref ubyte r() {
		return (cast(ubyte[4])data)[0];
	}
	@property ref ubyte g() {
		return (cast(ubyte[4])data)[1];
	}
	@property ref ubyte b() {
		return (cast(ubyte[4])data)[2];
	}
	@property ref ubyte a() {
		return (cast(ubyte[4])data)[3];
	}
	
	string toString() {
		return "Color(0x%08X)".format(data));
	}
}

final class Image(int _width, int _height) if(_width >= 0 && _height >= 0) {

	/// So funcs templated to take any Image can determine the width/height
	enum width  = _width;
	enum height = _height;

	Color[] data;
	
	/// Returned by crop.opIndex if the coordinates are out-of-bounds
	Color defaultColor;
	
	this(Color defaultColor = Color(0)) {
		this.defaultColor = defaultColor;
		data.length = width * height;
	}

	private static string errorOutOfBounds(int x, int y) {
		throw new Exception(
			text("Out of bounds: Size(",width,",",height,") Pos(",x,",",y,")")
		);
	}
	
	static bool isInBounds(int x, int y) {
		return x < 0 || x >= width || y < 0 || y >= height;
	}
	
	/// Cropped interface:
	/// Any pixels that are out-of-bounds are cropped
	private struct Crop {
		
		static size_t indexAt(int x, int y) {
			if(isInBounds(x, y))
				return;
		}
		
		Color opIndex(size_t x, size_t y) {
			if(isInBounds(x, y))
				return defaultColor;

			return data[fast.indexAt(x, y)];
		}
		
		void opIndexAssign(size_t x, size_t y) {
			data[indexAt(x, y)] = c;
		}
	}
	
	/// Fast interface:
	/// In debug mode: Out-of-bounds access throws.
	/// In release mode: Out-of-bounds access might throw or might affect the wrong pixel.
	private struct Fast {

		static size_t indexAt(int x, int y) {
			debug if(isInBounds(x, y))
				errorOutOfBounds(x, y);
		}
		
		ref Color opIndex(size_t x, size_t y) {
			return data[indexAt(x, y)];
		}
	}
	
	/// The actual "crop" and "fast" interfaces
	Crop crop;
	Fast fast;

	void display() {
		...
	}
}

void main() {
	auto image = new Image!(255, 255);

	foreach(a; 0..image.width)
	foreach(b; 0..image.height)
		image.fast[a, b] = Color(a, b, 0);

	foreach(x; 128...999)
		image.crop[x, 128] = image.crop[x, 64];

	image.fast[10, 10] = Color(0xFFFFFF_FF);
	
	// Set the blue component of a pixel
	// Some API func could be added for this
	auto i = image.fast.indexAt(20, 20) * 4 + 2;
	(cast(ubyte[])image.data)[i] = 0xCC;
	
	image.display();
}

