/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

// Based on AppleWin's code for NTSC emulation and its RGB Monitor palette
// Copyright (C) 2010-2011, William S Simms
// Copyright (C) 2014-2016, Michael Pohoreski, Tom Charlesworth
// Licensed under GPLv2+

#include "common/stream.h"
#include "common/rect.h"
#include "common/system.h"
#include "common/str.h"
#include "common/config-manager.h"
#include "common/math.h"
#include "common/memstream.h"

#include "graphics/surface.h"
#include "graphics/palette.h"
#include "graphics/thumbnail.h"

#include "engines/util.h"

#include "adl/display_a2.h"
#include "adl/adl.h"

namespace Adl {

#define NTSC_REMOVE_BLACK_GHOSTING
// #define NTSC_REMOVE_WHITE_RINGING

// Uppercase-only Apple II font (manually created).
const byte Display_A2::_font[64][8] = {
	{ 0x00, 0x1c, 0x22, 0x2a, 0x3a, 0x1a, 0x02, 0x3c }, { 0x00, 0x08, 0x14, 0x22, 0x22, 0x3e, 0x22, 0x22 }, // @A
	{ 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x22, 0x22, 0x1e }, { 0x00, 0x1c, 0x22, 0x02, 0x02, 0x02, 0x22, 0x1c }, // BC
	{ 0x00, 0x1e, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1e }, { 0x00, 0x3e, 0x02, 0x02, 0x1e, 0x02, 0x02, 0x3e }, // DE
	{ 0x00, 0x3e, 0x02, 0x02, 0x1e, 0x02, 0x02, 0x02 }, { 0x00, 0x3c, 0x02, 0x02, 0x02, 0x32, 0x22, 0x3c }, // FG
	{ 0x00, 0x22, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22 }, { 0x00, 0x1c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c }, // HI
	{ 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x1c }, { 0x00, 0x22, 0x12, 0x0a, 0x06, 0x0a, 0x12, 0x22 }, // JK
	{ 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x3e }, { 0x00, 0x22, 0x36, 0x2a, 0x2a, 0x22, 0x22, 0x22 }, // LM
	{ 0x00, 0x22, 0x22, 0x26, 0x2a, 0x32, 0x22, 0x22 }, { 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c }, // NO
	{ 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x02, 0x02, 0x02 }, { 0x00, 0x1c, 0x22, 0x22, 0x22, 0x2a, 0x12, 0x2c }, // PQ
	{ 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x0a, 0x12, 0x22 }, { 0x00, 0x1c, 0x22, 0x02, 0x1c, 0x20, 0x22, 0x1c }, // RS
	{ 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 }, { 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c }, // TU
	{ 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x14, 0x08 }, { 0x00, 0x22, 0x22, 0x22, 0x2a, 0x2a, 0x36, 0x22 }, // VW
	{ 0x00, 0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22 }, { 0x00, 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08 }, // XY
	{ 0x00, 0x3e, 0x20, 0x10, 0x08, 0x04, 0x02, 0x3e }, { 0x00, 0x3e, 0x06, 0x06, 0x06, 0x06, 0x06, 0x3e }, // Z[
	{ 0x00, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 }, { 0x00, 0x3e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3e }, // \]
	{ 0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e }, // ^_
	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08 }, //  !
	{ 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x14, 0x14, 0x3e, 0x14, 0x3e, 0x14, 0x14 }, // "#
	{ 0x00, 0x08, 0x3c, 0x0a, 0x1c, 0x28, 0x1e, 0x08 }, { 0x00, 0x06, 0x26, 0x10, 0x08, 0x04, 0x32, 0x30 }, // $%
	{ 0x00, 0x04, 0x0a, 0x0a, 0x04, 0x2a, 0x12, 0x2c }, { 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00 }, // &'
	{ 0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08 }, { 0x00, 0x08, 0x10, 0x20, 0x20, 0x20, 0x10, 0x08 }, // ()
	{ 0x00, 0x08, 0x2a, 0x1c, 0x08, 0x1c, 0x2a, 0x08 }, { 0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00 }, // *+
	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x04 }, { 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00 }, // ,-
	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }, { 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 }, // ./
	{ 0x00, 0x1c, 0x22, 0x32, 0x2a, 0x26, 0x22, 0x1c }, { 0x00, 0x08, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x1c }, // 01
	{ 0x00, 0x1c, 0x22, 0x20, 0x18, 0x04, 0x02, 0x3e }, { 0x00, 0x3e, 0x20, 0x10, 0x18, 0x20, 0x22, 0x1c }, // 23
	{ 0x00, 0x10, 0x18, 0x14, 0x12, 0x3e, 0x10, 0x10 }, { 0x00, 0x3e, 0x02, 0x1e, 0x20, 0x20, 0x22, 0x1c }, // 45
	{ 0x00, 0x38, 0x04, 0x02, 0x1e, 0x22, 0x22, 0x1c }, { 0x00, 0x3e, 0x20, 0x10, 0x08, 0x04, 0x04, 0x04 }, // 67
	{ 0x00, 0x1c, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x1c }, { 0x00, 0x1c, 0x22, 0x22, 0x3c, 0x20, 0x10, 0x0e }, // 89
	{ 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x08, 0x04 }, // :;
	{ 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10 }, { 0x00, 0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00 }, // <=
	{ 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04 }, { 0x00, 0x1c, 0x22, 0x10, 0x08, 0x08, 0x00, 0x08 }  // >?
};

struct LineDoubleBright {
	static uint8 blend(uint8 c1, uint8 c2) {
		return c1;
	}
};

struct LineDoubleDim {
	static uint8 blend(uint8 c1, uint8 c2) {
		return (c1 >> 1) + (c1 >> 2);
	}
};

struct BlendBright {
	static uint8 blend(uint8 c1, uint8 c2) {
		return (c1 + c2) >> 1;
	}
};

struct BlendDim {
	static uint8 blend(uint8 c1, uint8 c2) {
		// AppleWin does c1 >>= 2; return (c1 < c2 ? c2 - c1 : 0);
		// I think the following looks a lot better:
		return ((c1 + c2) >> 2) + ((c1 + c2) >> 3);
	}
};

static const uint kColorPhases = 4;
// All PixelWriters have been adjusted to have 3 pixels of "pre-render" that
// will be cut off when blitting to the screen
static const uint kPreRender = 3;

template<typename ColorType, typename T>
class PixelWriter {
public:
	PixelWriter() : _ptr(nullptr), _format(g_system->getScreenFormat()), _phase(0), _window(0) { }

	void setupWrite(ColorType *dest) {
		_ptr = dest;
		_phase = 3;
		_window = 0;
	}

	void writePixels(uint bits) {
		for (uint b = 0; b < 14; ++b) {
			_window <<= 1;
			_window |= bits & 1;
			bits >>= 1;
			*_ptr++ = static_cast<T *>(this)->getColor();
			_phase = (_phase + 1) & 3;
		}
	}

protected:
	ColorType *_ptr;
	Graphics::PixelFormat _format;
	uint _phase;
	uint _window;
};

template<typename ColorType>
class PixelWriterColor : public PixelWriter<ColorType, PixelWriterColor<ColorType> > {
public:
	static const uint kColors = 16;
	typedef LineDoubleBright BlendRegular;
	typedef LineDoubleDim BlendScanlines;

	PixelWriterColor() {
		const byte palette[kColors][3] = {
			{ 0x00, 0x00, 0x00 }, { 0x9d, 0x09, 0x66 }, { 0x2a, 0x2a, 0xe5 }, { 0xc7, 0x34, 0xff },
			{ 0x00, 0x80, 0x00 }, { 0x80, 0x80, 0x80 }, { 0x0d, 0xa1, 0xff }, { 0xaa, 0xaa, 0xff },
			{ 0x55, 0x55, 0x00 }, { 0xf2, 0x5e, 0x00 }, { 0xc0, 0xc0, 0xc0 }, { 0xff, 0x89, 0xe5 },
			{ 0x38, 0xcb, 0x00 }, { 0xd5, 0xd5, 0x1a }, { 0x62, 0xf6, 0x99 }, { 0xff, 0xff, 0xff }
		};

		for (uint pattern = 0; pattern < kColors; ++pattern) {
			uint color = ((pattern & 1) << 3) | ((pattern & 2) << 1) | ((pattern & 4) >> 1) | ((pattern & 8) >> 3);

			for (uint phase = 0; phase < kColorPhases; ++phase) {
				_colors[phase][pattern] = this->_format.RGBToColor(palette[color][0], palette[color][1], palette[color][2]);
				color = ((color & 8) >> 3) | ((color << 1) & 0x0f);
			}
		}
	}

	// >> 2 to synchronize rendering output with NTSC
	ColorType getColor() { return _colors[this->_phase][(this->_window >> 2) & (kColors - 1)]; }

private:
	ColorType _colors[kColorPhases][kColors];
};

template<typename ColorType, uint8 R, uint8 G, uint8 B>
class PixelWriterMono : public PixelWriter<ColorType, PixelWriterMono<ColorType, R, G, B> > {
public:
	static const uint kColors = 2;

	typedef LineDoubleBright BlendRegular;
	typedef LineDoubleDim BlendScanlines;

	PixelWriterMono() {
		_colors[0] = this->_format.RGBToColor(0, 0, 0);
		_colors[1] = this->_format.RGBToColor(R, G, B);
	}

	ColorType getColor() { return _colors[(this->_window >> 3) & (kColors - 1)]; }

private:
	ColorType _colors[kColors];
};

static double filterChroma(double z) {
	static double x[3] = {0, 0, 0};
	static double y[3] = {0, 0, 0};

	x[0] = x[1];
	x[1] = x[2];
	x[2] = z / 7.438011255;

	y[0] = y[1];
	y[1] = y[2];
	y[2] = -x[0] + x[2] + (-0.7318893645 * y[0]) + (1.2336442711 * y[1]);

	return y[2];
}

static double filterLuma(double z) {
	static double x[3] = {0, 0, 0};
	static double y[3] = {0, 0, 0};

	x[0] = x[1];
	x[1] = x[2];
	x[2] = z / 13.71331570;

	y[0] = y[1];
	y[1] = y[2];
	y[2] = x[0] + x[2] + (2.f * x[1]) + (-0.3961075449 * y[0]) + (1.1044202472 * y[1]);

	return y[2];
}

static double filterSignal(double z) {
	static double x[3] = {0, 0, 0};
	static double y[3] = {0, 0, 0};

	x[0] = x[1];
	x[1] = x[2];
	x[2] = z / 7.614490548;

	y[0] = y[1];
	y[1] = y[2];
	y[2] = x[0] + x[2] + (2.0 * x[1]) + (-0.2718798058 * y[0]) + (0.7465656072 * y[1]);

	return y[2];
}

template<typename ColorType>
class PixelWriterColorNTSC : public PixelWriter<ColorType, PixelWriterColorNTSC<ColorType> > {
public:
	static const uint kColors = 4096;

	typedef BlendBright BlendRegular;
	typedef BlendDim BlendScanlines;

	PixelWriterColorNTSC() {
		for (uint phase = 0; phase < kColorPhases; ++phase) {
			double phi = Common::deg2rad(phase * 90.0 + 45.0);
			for (uint s = 0; s < kColors; ++s) {
				uint t = s;
				double y;
				double i = 0.0;
				double q = 0.0;

				for (uint n = 0; n < 12; ++n) {
					double z = (double)(0 != (t & 0x800));
					t = t << 1;

					for (uint k = 0; k < 2; k++ ) {
						const double zz = filterSignal(z);
						double c = filterChroma(zz);
						y = filterLuma(zz - c);

						c = c * 2.0;
						i = i + (c * cos(phi) - i) / 8.0;
						q = q + (c * sin(phi) - q) / 8.0;

						phi += Common::deg2rad(45.0);
					}
				}

				// YIQ to RGB
				const double r64 = y + (0.956 * i) + (0.621 * q);
				const double g64 = y + (-0.272 * i) + (-0.647 * q);
				const double b64 = y + (-1.105 * i) + (1.702 * q);

				uint8 r = CLIP<double>(r64, 0.0, 1.0) * 255;
				uint8 g = CLIP<double>(g64, 0.0, 1.0) * 255;
				uint8 b = CLIP<double>(b64, 0.0, 1.0) * 255;

	#ifdef NTSC_REMOVE_WHITE_RINGING
				if ((s & 0xf) == 15) {
					// white
					r = 255;
					g = 255;
					b = 255;
				}
	#endif

	#ifdef NTSC_REMOVE_BLACK_GHOSTING
				if ((s & 0xf) == 0) {
					// Black
					r = 0;
					g = 0;
					b = 0;
				}
	#endif

				_colors[phase][s] = this->_format.RGBToColor(r, g, b);
			}
		}
	}

	ColorType getColor() { return _colors[this->_phase][(this->_window >> 1) & (kColors - 1)]; }

private:
	ColorType _colors[kColorPhases][kColors];
};

template<typename ColorType>
class PixelWriterMonoNTSC : public PixelWriter<ColorType, PixelWriterMonoNTSC<ColorType> > {
public:
	static const uint kColors = 4096;

	typedef BlendBright BlendRegular;
	typedef BlendDim BlendScanlines;

	PixelWriterMonoNTSC() {
		for (uint s = 0; s < kColors; ++s) {
			uint t = s;
			double y;

			for (uint n = 0; n < 12; ++n) {
				double z = (double)(0 != (t & 0x800));
				t = t << 1;

				for (uint k = 0; k < 2; k++ ) {
					const double zz = filterSignal(z);
					double c = filterChroma(zz);
					y = filterLuma(zz - c);
				}
			}

			const uint8 brightness = CLIP<double>(y, 0.0, 1.0) * 255;
			_colors[s] = this->_format.RGBToColor(brightness, brightness, brightness);
		}
	}

	ColorType getColor() { return _colors[(this->_window >> 1) & (kColors - 1)]; }

private:
	ColorType _colors[kColors];
};

template<typename ColorType, typename GfxWriter, typename TextWriter>
class DisplayImpl_A2 : public Display_A2 {
public:
	DisplayImpl_A2();
	~DisplayImpl_A2() override;

	void renderText() override;
	void renderGraphics() override;

private:
	enum {
		kRenderBufWidth = (kGfxPitch + 1) * 14, // one extra chunk to account for pre-render
		kRenderBufHeight = (kGfxHeight * 2) + 1 // one extra line to simplify scanline mixing
	};

	template<typename BlendFunc>
	void blendScanlines(uint yStart, uint yEnd);

	template<typename Reader, typename Writer>
	void render(Writer &writer);

	ColorType *_renderBuf;
	uint16 _doublePixelMasks[128];

	GfxWriter _writerColor;
	TextWriter _writerMono;
};

template<typename ColorType, typename GfxWriter, typename TextWriter>
DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::DisplayImpl_A2() : _doublePixelMasks() {
	_renderBuf = new ColorType[kRenderBufHeight * kRenderBufWidth]();

	for (uint8 val = 0; val < ARRAYSIZE(_doublePixelMasks); ++val)
		for (uint8 mask = 0; mask < 7; mask++)
			if (val & (1 << mask))
				_doublePixelMasks[val] |= 3 << (mask * 2);
}

template<typename ColorType, typename GfxWriter, typename TextWriter>
DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::~DisplayImpl_A2() {
	delete[] _renderBuf;
}

template<typename ColorType, typename GfxWriter, typename TextWriter>
template<typename Reader, typename Writer>
void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::render(Writer &writer) {
	uint startY = Reader::getStartY(this);
	const uint endY = Reader::getEndY(this);

	ColorType *ptr = _renderBuf + startY * kRenderBufWidth * 2;

	for (uint y = startY; y < endY; ++y) {
		uint16 lastBit = 0;

		writer.setupWrite(ptr);

		for (uint x = 0; x < kGfxPitch; ++x) {
			const uint8 m = Reader::getBits(this, y, x);

			uint16 bits = _doublePixelMasks[m & 0x7F];

			if (m & 0x80)
				bits = (bits << 1) | lastBit;

			lastBit = (bits >> 13) & 1;

			writer.writePixels(bits);
		}

		// Because of the pre-render, we need to feed
		// in some more bits to get the full picture
		writer.writePixels(0);

		// The odd lines will be filled in later, so skip a line
		ptr += 2 * kRenderBufWidth;
	}

	if (_enableScanlines)
		blendScanlines<typename Writer::BlendScanlines>(startY, endY);
	else
		blendScanlines<typename Writer::BlendRegular>(startY, endY);

	// For the NTSC modes we need to redo the scanline that blends with our first line
	if (GfxWriter::kColors == 4096 && startY > 0) {
		--startY;
		if (_enableScanlines)
			blendScanlines<typename GfxWriter::BlendScanlines>(startY, startY + 1);
		else
			blendScanlines<typename GfxWriter::BlendRegular>(startY, startY + 1);
	}

	g_system->copyRectToScreen(_renderBuf + startY * 2 * kRenderBufWidth + kPreRender, kRenderBufWidth * sizeof(ColorType), 0, startY * 2, kGfxWidth * 2, (endY - startY) * 2);
	g_system->updateScreen();
}

template<typename ColorType, typename GfxWriter, typename TextWriter>
template<typename BlendType>
void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::blendScanlines(uint yStart, uint yEnd) {
	const Graphics::PixelFormat rgbFormat = g_system->getScreenFormat();

	// Note: this reads line yEnd * 2 of _renderBuf!
	for (uint y = yStart; y < yEnd; ++y) {
		ColorType *buf = &_renderBuf[y * 2 * kRenderBufWidth];
		for (uint x = 0; x < kRenderBufWidth; ++x) {
			const ColorType color1 = buf[x];
			const ColorType color2 = buf[2 * kRenderBufWidth + x];

			uint8 r1, g1, b1, r2, g2, b2;

			rgbFormat.colorToRGB(color1, r1, g1, b1);
			rgbFormat.colorToRGB(color2, r2, g2, b2);

			const uint8 r3 = BlendType::blend(r1, r2);
			const uint8 g3 = BlendType::blend(g1, g2);
			const uint8 b3 = BlendType::blend(b1, b2);

			buf[kRenderBufWidth + x] = rgbFormat.RGBToColor(r3, g3, b3);
		}
	}
}

template<typename ColorType, typename GfxWriter, typename TextWriter>
void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::renderText() {
	if (_mode == kModeGraphics)
		return;

	_blink = (g_system->getMillis() / 270) & 1;

	if (_mode == kModeMixed && _enableColor && !_enableMonoText)
		render<TextReader>(_writerColor);
	else
		render<TextReader>(_writerMono);
}

template<typename ColorType, typename GfxWriter, typename TextWriter>
void DisplayImpl_A2<ColorType, GfxWriter, TextWriter>::renderGraphics() {
	if (_mode == kModeText)
		return;

	render<GfxReader>(_writerColor);
}

Display_A2::Display_A2() :
		_frameBuf(nullptr),
		_showCursor(false),
		_enableColor(false),
		_enableScanlines(false),
		_enableMonoText(false),
		_blink(false) { }

Display_A2::~Display_A2() {
	delete[] _frameBuf;
}

void Display_A2::init() {
	createTextBuffer(Display_A2::kTextWidth, Display_A2::kTextHeight);

	_frameBuf = new byte[Display_A2::kGfxSize]();

	_enableColor = ConfMan.getBool("color");
	_enableScanlines = ConfMan.getBool("scanlines");
	_enableMonoText = ConfMan.getBool("monotext");
}

void Display_A2::loadFrameBuffer(Common::ReadStream &stream, byte *dst) const {
	for (uint j = 0; j < 8; ++j) {
		for (uint i = 0; i < 8; ++i) {
			stream.read(dst, Display_A2::kGfxPitch);
			dst += Display_A2::kGfxPitch * 64;
			stream.read(dst, Display_A2::kGfxPitch);
			dst += Display_A2::kGfxPitch * 64;
			stream.read(dst, Display_A2::kGfxPitch);
			stream.readUint32LE();
			stream.readUint32LE();
			dst -= Display_A2::kGfxPitch * 120;
		}
		dst -= Display_A2::kGfxPitch * 63;
	}

	if (stream.eos() || stream.err())
		error("Failed to read frame buffer");
}

void Display_A2::loadFrameBuffer(Common::ReadStream &stream) {
	loadFrameBuffer(stream, _frameBuf);
}

void Display_A2::putPixel(const Common::Point &p, byte color) {
	const byte offset = p.x / 7;
	byte mask = 0x80 | (1 << (p.x % 7));

	// Since white and black are in both palettes, we leave
	// the palette bit alone
	if ((color & 0x7f) == 0x7f || (color & 0x7f) == 0)
		mask &= 0x7f;

	// Adjust colors starting with bits '01' or '10' for
	// odd offsets
	if (offset & 1) {
		byte c = color << 1;
		if (c >= 0x40 && c < 0xc0)
			color ^= 0x7f;
	}

	writeFrameBuffer(p, color, mask);
}

void Display_A2::setPixelByte(const Common::Point &p, byte color) {
	assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);

	_frameBuf[p.y * Display_A2::kGfxPitch + p.x / 7] = color;
}

void Display_A2::setPixelBit(const Common::Point &p, byte color) {
	writeFrameBuffer(p, color, 1 << (p.x % 7));
}

void Display_A2::setPixelPalette(const Common::Point &p, byte color) {
	writeFrameBuffer(p, color, 0x80);
}

byte Display_A2::getPixelByte(const Common::Point &p) const {
	assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);

	return _frameBuf[p.y * Display_A2::kGfxPitch + p.x / 7];
}

bool Display_A2::getPixelBit(const Common::Point &p) const {
	assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);

	const byte *b = _frameBuf + p.y * Display_A2::kGfxPitch + p.x / 7;
	return *b & (1 << (p.x % 7));
}

void Display_A2::clear(byte color) {
	byte val = 0;

	const byte c = color << 1;
	if (c >= 0x40 && c < 0xc0)
		val = 0x7f;

	for (uint i = 0; i < Display_A2::kGfxSize; ++i) {
		_frameBuf[i] = color;
		color ^= val;
	}
}

// FIXME: This does not currently update the surfaces
void Display_A2::printChar(char c) {
	if (c == Display_A2::asciiToNative('\r'))
		_cursorPos = (_cursorPos / Display_A2::kTextWidth + 1) * Display_A2::kTextWidth;
	else if (c == Display_A2::asciiToNative('\a')) {
		renderText();
		static_cast<AdlEngine *>(g_engine)->bell();
	} else if ((byte)c < 0x80 || (byte)c >= 0xa0) {
		setCharAtCursor(c);
		++_cursorPos;
	}

	if (_cursorPos == Display_A2::kTextWidth * Display_A2::kTextHeight)
		scrollUp();
}

void Display_A2::showCursor(bool enable) {
	_showCursor = enable;
}

void Display_A2::writeFrameBuffer(const Common::Point &p, byte color, byte mask) {
	assert(p.x >= 0 && p.x < Display_A2::kGfxWidth && p.y >= 0 && p.y < Display_A2::kGfxHeight);

	byte *b = _frameBuf + p.y * Display_A2::kGfxPitch + p.x / 7;
	color ^= *b;
	color &= mask;
	*b ^= color;
}

template<typename ColorType>
static Display_A2 *Display_A2_create_helper() {
	const bool ntsc = ConfMan.getBool("ntsc");
	const bool color = ConfMan.getBool("color");
	const bool monotext = ConfMan.getBool("monotext");

	typedef PixelWriterMono<ColorType, 0xff, 0xff, 0xff> PixelWriterMonoWhite;
	typedef PixelWriterMono<ColorType, 0x00, 0xc0, 0x00> PixelWriterMonoGreen;

	if (ntsc) {
		if (color) {
			if (monotext)
				return new DisplayImpl_A2<ColorType, PixelWriterColorNTSC<ColorType>, PixelWriterMonoWhite>;
			else
				return new DisplayImpl_A2<ColorType, PixelWriterColorNTSC<ColorType>, PixelWriterMonoNTSC<ColorType> >;
		} else {
			if (monotext)
				return new DisplayImpl_A2<ColorType, PixelWriterMonoNTSC<ColorType>, PixelWriterMonoWhite>;
			else
				return new DisplayImpl_A2<ColorType, PixelWriterMonoNTSC<ColorType>, PixelWriterMonoNTSC<ColorType> >;
		}
	}

	if (color)
		return new DisplayImpl_A2<ColorType, PixelWriterColor<ColorType>, PixelWriterMonoWhite>;
	else
		return new DisplayImpl_A2<ColorType, PixelWriterMonoGreen, PixelWriterMonoGreen>;
}

Display_A2 *Display_A2_create() {
	initGraphics(Display_A2::kGfxWidth * 2, Display_A2::kGfxHeight * 2, nullptr);
	debugN(1, "Initialized graphics with format: %s", g_system->getScreenFormat().toString().c_str());

	const uint bpp = g_system->getScreenFormat().bytesPerPixel;

	if (bpp == 4)
		return Display_A2_create_helper<uint32>();
	else if (bpp == 2)
		return Display_A2_create_helper<uint16>();
	else
		return nullptr;
}

} // End of namespace Adl
