function GameBoyAdvanceVideo() {
	this.renderPath = new GameBoyAdvanceSoftwareRenderer();

	this.CYCLES_PER_PIXEL = 4;

	this.HORIZONTAL_PIXELS = 240;
	this.HBLANK_PIXELS = 68;
	this.HDRAW_LENGTH = 1006;
	this.HBLANK_LENGTH = 226;
	this.HORIZONTAL_LENGTH = 1232;

	this.VERTICAL_PIXELS = 160;
	this.VBLANK_PIXELS = 68;
	this.VERTICAL_TOTAL_PIXELS = 228;

	this.TOTAL_LENGTH = 280896;

	this.drawCallback = function() {};
	this.vblankCallback = function() {};
};

GameBoyAdvanceVideo.prototype.clear = function() {
	this.renderPath.clear(this.cpu.mmu);

	// DISPSTAT
	this.DISPSTAT_MASK = 0xFF38;
	this.inHblank = false;
	this.inVblank = false;
	this.vcounter = 0;
	this.vblankIRQ = 0;
	this.hblankIRQ = 0;
	this.vcounterIRQ = 0;
	this.vcountSetting = 0;

	// VCOUNT
	this.vcount = -1;

	this.lastHblank = 0;
	this.nextHblank = this.HDRAW_LENGTH;
	this.nextEvent = this.nextHblank;

	this.nextHblankIRQ = 0;
	this.nextVblankIRQ = 0;
	this.nextVcounterIRQ = 0;
};

GameBoyAdvanceVideo.prototype.freeze = function() {
	return {
		'inHblank': this.inHblank,
		'inVblank': this.inVblank,
		'vcounter': this.vcounter,
		'vblankIRQ': this.vblankIRQ,
		'hblankIRQ': this.hblankIRQ,
		'vcounterIRQ': this.vcounterIRQ,
		'vcountSetting': this.vcountSetting,
		'vcount': this.vcount,
		'lastHblank': this.lastHblank,
		'nextHblank': this.nextHblank,
		'nextEvent': this.nextEvent,
		'nextHblankIRQ': this.nextHblankIRQ,
		'nextVblankIRQ': this.nextVblankIRQ,
		'nextVcounterIRQ': this.nextVcounterIRQ,
		'renderPath': this.renderPath.freeze(this.core.encodeBase64)
	};
};

GameBoyAdvanceVideo.prototype.defrost = function(frost) {
	this.inHblank = frost.inHblank;
	this.inVblank = frost.inVblank;
	this.vcounter = frost.vcounter;
	this.vblankIRQ = frost.vblankIRQ;
	this.hblankIRQ = frost.hblankIRQ;
	this.vcounterIRQ = frost.vcounterIRQ;
	this.vcountSetting = frost.vcountSetting;
	this.vcount = frost.vcount;
	this.lastHblank = frost.lastHblank;
	this.nextHblank = frost.nextHblank;
	this.nextEvent = frost.nextEvent;
	this.nextHblankIRQ = frost.nextHblankIRQ;
	this.nextVblankIRQ = frost.nextVblankIRQ;
	this.nextVcounterIRQ = frost.nextVcounterIRQ;
	this.renderPath.defrost(frost.renderPath, this.core.decodeBase64);
};

GameBoyAdvanceVideo.prototype.setBacking = function(backing) {
	var pixelData = backing.createImageData(this.HORIZONTAL_PIXELS, this.VERTICAL_PIXELS);
	this.context = backing;

	// Clear backing first
	for (var offset = 0; offset < this.HORIZONTAL_PIXELS * this.VERTICAL_PIXELS * 4;) {
		pixelData.data[offset++] = 0xFF;
		pixelData.data[offset++] = 0xFF;
		pixelData.data[offset++] = 0xFF;
		pixelData.data[offset++] = 0xFF;
	}

	this.renderPath.setBacking(pixelData);
}

GameBoyAdvanceVideo.prototype.updateTimers = function(cpu) {
	var cycles = cpu.cycles;

	if (this.nextEvent <= cycles) {
		if (this.inHblank) {
			// End Hblank
			this.inHblank = false;
			this.nextEvent = this.nextHblank;

			++this.vcount;

			switch (this.vcount) {
			case this.VERTICAL_PIXELS:
				this.inVblank = true;
				this.renderPath.finishDraw(this);
				this.nextVblankIRQ = this.nextEvent + this.TOTAL_LENGTH;
				this.cpu.mmu.runVblankDmas();
				if (this.vblankIRQ) {
					this.cpu.irq.raiseIRQ(this.cpu.irq.IRQ_VBLANK);
				}
				this.vblankCallback();
				break;
			case this.VERTICAL_TOTAL_PIXELS - 1:
				this.inVblank = false;
				break;
			case this.VERTICAL_TOTAL_PIXELS:
				this.vcount = 0;
				this.renderPath.startDraw();
				break;
			}

			this.vcounter = this.vcount == this.vcountSetting;
			if (this.vcounter && this.vcounterIRQ) {
				this.cpu.irq.raiseIRQ(this.cpu.irq.IRQ_VCOUNTER);
				this.nextVcounterIRQ += this.TOTAL_LENGTH;
			}

			if (this.vcount < this.VERTICAL_PIXELS) {
				this.renderPath.drawScanline(this.vcount);
			}
		} else {
			// Begin Hblank
			this.inHblank = true;
			this.lastHblank = this.nextHblank;
			this.nextEvent = this.lastHblank + this.HBLANK_LENGTH;
			this.nextHblank = this.nextEvent + this.HDRAW_LENGTH;
			this.nextHblankIRQ = this.nextHblank;

			if (this.vcount < this.VERTICAL_PIXELS) {
				this.cpu.mmu.runHblankDmas();
			}
			if (this.hblankIRQ) {
				this.cpu.irq.raiseIRQ(this.cpu.irq.IRQ_HBLANK);
			}
		}
	}
};

GameBoyAdvanceVideo.prototype.writeDisplayStat = function(value) {
	this.vblankIRQ = value & 0x0008;
	this.hblankIRQ = value & 0x0010;
	this.vcounterIRQ = value & 0x0020;
	this.vcountSetting = (value & 0xFF00) >> 8;

	if (this.vcounterIRQ) {
		// FIXME: this can be too late if we're in the middle of an Hblank
		this.nextVcounterIRQ = this.nextHblank + this.HBLANK_LENGTH + (this.vcountSetting - this.vcount) * this.HORIZONTAL_LENGTH;
		if (this.nextVcounterIRQ < this.nextEvent) {
			this.nextVcounterIRQ += this.TOTAL_LENGTH;
		}
	}
};

GameBoyAdvanceVideo.prototype.readDisplayStat = function() {
	return (this.inVblank) | (this.inHblank << 1) | (this.vcounter << 2);
};

GameBoyAdvanceVideo.prototype.finishDraw = function(pixelData) {
	this.context.putImageData(pixelData, 0, 0);
	this.drawCallback();
};
