#include "Printer.hh"

#include "Paper.hh"
#include "MSXCharacterSets.hh"

#include "IntegerSetting.hh"
#include "MSXCliComm.hh"
#include "MSXException.hh"
#include "MSXMotherBoard.hh"
#include "serialize.hh"

#include "Math.hh"
#include "xrange.hh"

#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <memory>
#include <ranges>

namespace openmsx {

bool PrinterCore::getStatus(EmuTime /*time*/)
{
	return false; // false = low = ready
}

void PrinterCore::setStrobe(bool strobe, EmuTime /*time*/)
{
	if (!strobe && prevStrobe) {
		// falling edge
		write(toPrint);
	}
	prevStrobe = strobe;
}

void PrinterCore::writeData(uint8_t data, EmuTime /*time*/)
{
	toPrint = data;
}

void PrinterCore::plugHelper(Connector& /*connector*/, EmuTime /*time*/)
{
	// nothing
}

void PrinterCore::unplugHelper(EmuTime /*time*/)
{
	forceFormFeed();
}


// class ImagePrinter

ImagePrinter::ImagePrinter(MSXMotherBoard& motherBoard_, bool graphicsHiLo_)
	: motherBoard(motherBoard_)
	, dpiSetting(motherBoard.getSharedStuff<IntegerSetting>(
		"print-resolution",
		motherBoard.getCommandController(), "print-resolution",
		"resolution of the output image of emulated dot matrix printer in DPI",
		300, 72, 1200))
	, graphicsHiLo(graphicsHiLo_)
{
}

ImagePrinter::~ImagePrinter()
{
	flushEmulatedPrinter();
}

void ImagePrinter::write(uint8_t data)
{
	if (ramLoadOffset < ramLoadEnd) {
		fontInfo.ram[ramLoadOffset++] = data;
	} else if (sizeRemainingDataBytes) {
		if (eightBit == 0) {
			data &= 0x80;
		} else if (eightBit == 1) {
			data |= 0x80;
		}
		printGraphicByte(data);
		sizeRemainingDataBytes--;
	} else if (escSequence) {
		escSequence = false;

		std::ranges::fill(abEscSeq, 0);
		abEscSeq[0] = data;
		sizeEscPos = 1;

		remainingCommandBytes = calcEscSequenceLength(data);

		if (!remainingCommandBytes) {
			processEscSequence();
		}
	} else if (remainingCommandBytes) {
		abEscSeq[sizeEscPos++] = data;

		if (!--remainingCommandBytes) {
			processEscSequence();
		}
	} else {
		processCharacter(data);
	}
}

void ImagePrinter::forceFormFeed()
{
	flushEmulatedPrinter();
}

void ImagePrinter::resetEmulatedPrinter()
{
	resetSettings();

	pageHeight = pageTop + lines * lineFeed;
	hpos       = leftBorder;
	vpos       = pageTop;
}

void ImagePrinter::plot9Dots(double x, double y, unsigned pattern)
{
	for (auto i : xrange(9)) {
		if (pattern & (1 << i)) {
			paper->plot(x, y + (8 - i) * pixelSizeY);
		}
	}
}

void ImagePrinter::printGraphicByte(uint8_t data)
{
	ensurePrintPage();

	double destY      = vpos * pixelSizeY;
	double destHeight = pixelSizeY * 9.0;

	// Print Data to high 8 bits
	unsigned charBits = (graphicsHiLo ? Math::reverseByte(data) : data) << 1;

	printAreaTop    = std::min(printAreaTop, destY);
	printAreaBottom = std::max(printAreaBottom, destY + destHeight);

	// Print bit-mask
	plot9Dots(hpos * pixelSizeX, destY, charBits);

	// Move print-position...
	seekPrinterHeadRelative(1.0 / graphDensity);
}

void ImagePrinter::seekPrinterHeadRelative(double offset)
{
	hpos += offset;
	if (unsigned(hpos) > rightBorder) {
		hpos = leftBorder;
		vpos += lineFeed;
		if (vpos >= pageHeight) {
			doubleWidth ^= normalAfterLine;
			flushEmulatedPrinter();
		}
	}
}

void ImagePrinter::ensurePrintPage()
{
	if (!paper) {
		// A4 paper format (210mm x 297mm) at 300dpi
		// TODO make this configurable
		int dpi = dpiSetting->getInt();
		auto paperSizeX = unsigned((210 / 25.4) * dpi);
		auto paperSizeY = unsigned((297 / 25.4) * dpi);

		auto [dotsX, dotsY] = getNumberOfDots();
		pixelSizeX = double(paperSizeX) / dotsX;
		pixelSizeY = double(paperSizeY) / dotsY;

		paper = std::make_unique<Paper>(paperSizeX, paperSizeY,
		                                pixelSizeX, pixelSizeY);
	}
}

void ImagePrinter::flushEmulatedPrinter()
{
	if (paper) {
		if (printAreaBottom > printAreaTop) {
			try {
				auto filename = paper->save();
				motherBoard.getMSXCliComm().printInfo(
					"Printed to ", filename);
			} catch (MSXException& e) {
				motherBoard.getMSXCliComm().printWarning(
					"Failed to print: ", e.getMessage());
			}
			printAreaTop = -1.0;
			printAreaBottom = 0.0;
		}
		paper.reset();
	}
	hpos = leftBorder;
	vpos = pageTop;
}

static constexpr unsigned compress9(unsigned a)
{
	unsigned result = 0;
	for (auto i : xrange(9)) {
		if (a & (1 << i)) {
			result |= 1 << (i / 2);
		}
	}
	return result;
}
void ImagePrinter::printVisibleCharacter(uint8_t data)
{
	ensurePrintPage();

	double iYPos = 0;
	std::span mem = fontInfo.useRam ? fontInfo.ram : fontInfo.rom;
	std::span charBitmap = subspan(mem, fontInfo.charWidth * data, fontInfo.charWidth);
	uint8_t attribute = charBitmap[0];
	unsigned start = (attribute >> 4) & 0x07;
	unsigned end = attribute & 0x0f;
	unsigned topBits = attribute >> 7;
	bool script = superscript || subscript;

	if (!proportional) {
		start = 0;  // Fixed width font
		end   = fontInfo.charWidth - 1;
	}
	if (subscript) {
		iYPos /= 2.0;
		iYPos += pixelSizeY * 4.5;
	}
	if (script) {
		iYPos -= pixelSizeY * 4.5;
	}

	double hPos = hpos;
	double headRelative = (doubleWidth ? 2 : 1) * fontInfo.pixelDelta / fontDensity;

	double destY = vpos * pixelSizeY + iYPos;
	double destHeight = pixelSizeY * 9.0;
	double dblStrikeOffset = doubleStrike ? (pixelSizeY * (1.0 / 2.5)) : 0.0;
	printAreaTop    = std::min(printAreaTop, destY);
	printAreaBottom = std::max(printAreaBottom, destY + destHeight + dblStrikeOffset);

	for (auto i : xrange(start, end)) {
		unsigned charBits = unsigned(charBitmap[i + 1]) << topBits;

		if (underline) {
			charBits |= 2;
		}
		if (script) {
			charBits = compress9(charBits);
		}

		for (auto d : xrange(doubleWidth ? 2 : 1)) {
			for (auto b : xrange(bold ? 2 : 1)) {
				for (auto y : xrange(doubleStrike ? 2: 1)) {
					double destX = (hPos + (d + b * 0.5) / fontDensity) * pixelSizeX;
					plot9Dots(destX, destY + y * dblStrikeOffset, charBits);
				}
			}
		}
		hPos += headRelative;
	}
	seekPrinterHeadRelative((1 + end - start) * headRelative);
}


// class ImagePrinterMSX

ImagePrinterMSX::ImagePrinterMSX(MSXMotherBoard& motherBoard_)
	: ImagePrinter(motherBoard_, true)
{
	resetEmulatedPrinter();
}

zstring_view ImagePrinterMSX::getName() const
{
	return "msx-printer";
}

zstring_view ImagePrinterMSX::getDescription() const
{
	// TODO which printer type
	return "Emulate MSX printer, prints to image.";
}

std::pair<unsigned, unsigned> ImagePrinterMSX::getNumberOfDots()
{
	return {825, 825};
}

void ImagePrinterMSX::resetSettings()
{
	lineFeed     = 12.0;
	leftBorder   = 48;
	rightBorder  = 800;
	graphDensity = 1.0;
	fontDensity  = 1.0;
	pageTop      = 48;
	lines        = 72;
	fontWidth    = 8;
	eightBit     = -1;

	// note: this only overwrites 9/12 of the fontInfo.rom array.
	copy_to_range(getMSXFontRotated(), fontInfo.rom);
	fontInfo.charWidth = 9;
	fontInfo.pixelDelta = 1.0;
	fontInfo.useRam = false;
}


unsigned ImagePrinterMSX::calcEscSequenceLength(uint8_t character)
{
	switch (character) {
		case 'C':
			return 1;
		case 'T': case 'Z':
			return 2;
		case 'O': case '\\': case 'L': case '/':
			return 3;
		case 'S':
			return 4;
		case 'G':
			return 7;
		default:
			return 0;
	}
}

unsigned ImagePrinterMSX::parseNumber(unsigned sizeStart, unsigned sizeChars)
{
	unsigned Value = 0;
	while (sizeChars--) {
		Value = Value * 10;
		uint8_t data = abEscSeq[sizeStart++];
		if (data >= '0' && data <= '9') {
			Value += unsigned(data - '0');
		}
	}
	return Value;
}

void ImagePrinterMSX::processEscSequence()
{
	switch (abEscSeq[0]) {
		case 'N':
			proportional = false;
			fontDensity = 1.0;
			break;
		case 'E':
			proportional = false;
			fontDensity = 1.40;
			break;
		case 'Q':
			proportional = false;
			fontDensity = 1.72;
			break;
		case 'P':
			proportional = true;
			fontDensity = 0.90;
			break;
		case '!':
			letterQuality = true;
			break;
		case '\"':
			letterQuality = false;
			break;
		case 'C':
			switch (abEscSeq[1]) {
				case 'D':
					doubleStrike = true;
					break;
				case 'd':
					doubleStrike = false;
					break;
				case 'I':
					italic = true;
					break;
				case 'i':
					italic = false;
					break;
				case 'B':
					bold = true;
					break;
				case 'b':
					bold = false;
					break;
				case 'S':
					superscript = true;
					break;
				case 's':
					superscript = false;
					break;
				case 'U':
					subscript = true;
					break;
				case 'u':
					subscript = false;
					break;
			}
			break;
		case '(': // ???: Set a horizontal tab position
			break;
		case ')': // ???: Partially delete a horizontal tab position
			break;
		case '2': // ???: Clear horizontal tabs
			break;
		case 'O':
			switch (abEscSeq[1]) {
				case 'S':
					perforationSkip = parseNumber(2, 2);
					break;
				case 'I': // ???: Set page-height(inches)
					break;
				default: // ???: Set page-height (lines)
					break;
			}
			break;
		case '/': // Right margin
			break;
		case 'L':
			leftBorder = parseNumber(1, 3);
			break;
		case 'A': // ???: Line-feed 1/6"
			lineFeed = 12.0;
			break;
		case 'B': // ???: Line-feed 1/9"
			lineFeed = 8.0;
			break;
		case 'T': // ???: Line-feed nn/144"
			lineFeed = parseNumber(1, 2) * (1.0 / 2.0);
			break;
		case 'Z': // ???: Line-feed nn/216"
			lineFeed = parseNumber(1, 2) * (1.0 / 3.0);
			break;
		case '[': // ???: Uni-directional printing
			break;
		case ']': // ???: Bi-directional printing
			break;
		case 'p':
			detectPaperOut = true;
			break;
		case 'q':
			detectPaperOut = false;
			break;
		case 13: // (ESC, CR) Move printer-head to end-position
			break;
		case '@':
			resetEmulatedPrinter();
			break;
		case '\\':
			rightBorder = parseNumber(1, 3);
			break;
		case 'G':
			graphDensity = std::max(0.1, parseNumber(1, 3) * (1.0 / 100.0));
			sizeRemainingDataBytes = parseNumber(4, 4);
			break;
		case 'S': // Print graphics, density depending on font
			sizeRemainingDataBytes = parseNumber(1, 4);
			break;
		case 'X':
			underline = true;
			break;
		case 'Y':
			underline = false;
			break;
		case '&':
		case '$':
			japanese = !japanese;
			break;
		case 'f': // ???: Scroll paper forward
			break;
		case 'r': // ???: Scroll paper back
			break;
	}
}

void ImagePrinterMSX::processCharacter(uint8_t data)
{
	if (alternateChar) {
		// Print SOH-preceded character
		printVisibleCharacter(data & 0x1F);
		alternateChar = false;
	} else {
		switch (data) {
			case 1: // SOH: A symbol code preceding code
				alternateChar = true;
				break;
			case 7: // BEL: Audible beep (buzzer, 0.3s)
				break;
			case 8: // BS: Backstep (1 Character)
				// TODO: fix for other font-sizes
				hpos = std::max(hpos - 8, double(leftBorder));
				break;
			case 9: // HAT: Horizontal tabulator
				// TODO: fix for other font-sizes
				hpos = ((unsigned(hpos) + 64 - leftBorder) & ~63)
				     + leftBorder;
				if (hpos < rightBorder) {
					break;
				}
				hpos = leftBorder;
				[[fallthrough]];
			case 10: // LF: Carriage return + Line feed
			case 11: // VT: Vertical tabulator (like LF)
				//hpos = leftBorder;
				vpos += lineFeed;
				if (vpos >= pageHeight) {
					flushEmulatedPrinter();
				}
				break;
			case 12: // FF: Form feed
				ensurePrintPage();
				flushEmulatedPrinter();
				break;
			case 13: // CR: Carriage return
				hpos = leftBorder;
				break;
			case 14: // SO: Double character-width on
				doubleWidth = true;
				break;
			case 15: // SI: Double character-width off
				doubleWidth = false;
				break;
			case 27:
				escSequence = true;
				break;
			default:
				if (data >= 32) {
					// Yes, we can print it!
					printVisibleCharacter(data);
				}
				break;
		}
	}
}

template<typename Archive>
void ImagePrinterMSX::serialize(Archive& /*ar*/, unsigned /*version*/)
{
	// TODO is this worth it?
}
INSTANTIATE_SERIALIZE_METHODS(ImagePrinterMSX);
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, ImagePrinterMSX, "ImagePrinterMSX");


// class ImagePrinterEpson

static constexpr std::array<uint8_t, 12 * 256> EpsonFontRom = {
	0x8b, 0x04, 0x0a, 0x20, 0x8a, 0x60, 0x0a, 0x20, 0x1c, 0x02, 0x00, 0x00, //   0
	0x8b, 0x1c, 0x22, 0x08, 0xa2, 0x48, 0x22, 0x08, 0x22, 0x18, 0x00, 0x00, //   1
	0x9b, 0x00, 0x3c, 0x00, 0x82, 0x40, 0x02, 0x00, 0x3c, 0x02, 0x00, 0x00, //   2
	0x9a, 0x00, 0x1c, 0x22, 0x80, 0x62, 0x00, 0x22, 0x1c, 0x00, 0x00, 0x00, //   3
	0x96, 0x00, 0x12, 0x80, 0x5e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, //   4
	0xa7, 0x00, 0x00, 0x40, 0xa0, 0x00, 0xa0, 0x40, 0x00, 0x00, 0x00, 0x00, //   5
	0x8b, 0x12, 0x00, 0x7e, 0x80, 0x12, 0x80, 0x02, 0x80, 0x42, 0x00, 0x00, //   6
	0xc8, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //   7
	0x8b, 0x06, 0x00, 0x09, 0x00, 0x51, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, //   8
	0x8b, 0x5e, 0x80, 0x10, 0x80, 0x08, 0x40, 0x04, 0x40, 0x9e, 0x00, 0x00, //   9
	0x8a, 0x40, 0x9e, 0x00, 0x90, 0x40, 0x10, 0x4e, 0x80, 0x00, 0x00, 0x00, //  10
	0x8b, 0x92, 0x28, 0x44, 0x00, 0x44, 0x00, 0x44, 0x28, 0x92, 0x00, 0x00, //  11
	0x8b, 0xfe, 0x00, 0xa0, 0x00, 0x48, 0x00, 0x1e, 0x00, 0x0a, 0x00, 0x00, //  12
	0x8b, 0x06, 0x08, 0x54, 0xa0, 0x04, 0xa0, 0x54, 0x08, 0x06, 0x00, 0x00, //  13
	0x8b, 0x04, 0x0a, 0x20, 0x0a, 0xa0, 0x0a, 0x20, 0x1c, 0x02, 0x00, 0x00, //  14
	0x0a, 0x38, 0x44, 0x01, 0x44, 0x01, 0x46, 0x00, 0x44, 0x00, 0x00, 0x00, //  15
	0x9a, 0x00, 0x50, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x14, 0x00, 0x00, 0x00, //  16
	0x8a, 0x7e, 0x80, 0x00, 0x80, 0x12, 0x80, 0x12, 0x6c, 0x00, 0x00, 0x00, //  17
	0x8b, 0x3e, 0x40, 0x90, 0x00, 0xfe, 0x00, 0x92, 0x00, 0x92, 0x00, 0x00, //  18
	0x8b, 0x2c, 0x02, 0x28, 0x02, 0x1c, 0x20, 0x0a, 0x20, 0x1a, 0x00, 0x00, //  19
	0x8b, 0x3a, 0x44, 0x00, 0x8a, 0x10, 0xa2, 0x00, 0x44, 0xb8, 0x00, 0x00, //  20
	0x8b, 0x02, 0x08, 0x14, 0x22, 0x08, 0x22, 0x14, 0x08, 0x20, 0x00, 0x00, //  21
	0xa9, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, //  22
	0x8b, 0x06, 0x88, 0x14, 0x20, 0x44, 0x20, 0x14, 0x88, 0x06, 0x00, 0x00, //  23
	0x8b, 0x1c, 0xa2, 0x00, 0x22, 0x00, 0x22, 0x00, 0xa2, 0x1c, 0x00, 0x00, //  24
	0x8b, 0x3c, 0x82, 0x00, 0x02, 0x00, 0x02, 0x00, 0x82, 0x3c, 0x00, 0x00, //  25
	0x8b, 0x04, 0x0a, 0xa0, 0x0a, 0x20, 0x0a, 0xa0, 0x1c, 0x02, 0x00, 0x00, //  26
	0x9a, 0x00, 0x1c, 0xa2, 0x00, 0x22, 0x00, 0xa2, 0x1c, 0x00, 0x00, 0x00, //  27
	0x8a, 0x3c, 0x80, 0x02, 0x00, 0x02, 0x80, 0x3c, 0x02, 0x00, 0x00, 0x00, //  28
	0x8b, 0x3e, 0x00, 0x2a, 0x00, 0x6a, 0x80, 0x2a, 0x00, 0x22, 0x00, 0x00, //  29
	0x8b, 0x1c, 0x22, 0x08, 0x22, 0x48, 0xa2, 0x08, 0x22, 0x18, 0x00, 0x00, //  30
	0x8b, 0xa8, 0x00, 0x68, 0x00, 0x3e, 0x00, 0x68, 0x00, 0xa8, 0x00, 0x00, //  31
	0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  32
	0xc8, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  33
	0xa9, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, //  34
	0x8b, 0x28, 0x00, 0xfe, 0x00, 0x28, 0x00, 0xfe, 0x00, 0x28, 0x00, 0x00, //  35
	0x8b, 0x24, 0x00, 0x54, 0x00, 0xfe, 0x00, 0x54, 0x00, 0x48, 0x00, 0x00, //  36
	0x8b, 0xc0, 0x02, 0xc4, 0x08, 0x10, 0x20, 0x46, 0x80, 0x06, 0x00, 0x00, //  37
	0x8b, 0x4c, 0xa0, 0x12, 0xa0, 0x4a, 0x00, 0x04, 0x08, 0x12, 0x00, 0x00, //  38
	0xc8, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, //  39
	0xc9, 0x00, 0x00, 0x00, 0x00, 0x38, 0x44, 0x82, 0x00, 0x00, 0x00, 0x00, //  40
	0xa7, 0x00, 0x00, 0x82, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  41
	0x8b, 0x10, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x10, 0x00, 0x00, //  42
	0x8b, 0x10, 0x00, 0x10, 0x00, 0x7c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, //  43
	0x39, 0x00, 0x00, 0x00, 0x0c, 0x01, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, //  44
	0x8b, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, //  45
	0xa8, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  46
	0x9a, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, //  47
	0x8b, 0x38, 0x44, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x38, 0x00, 0x00, //  48
	0xa9, 0x00, 0x00, 0x42, 0x00, 0xfe, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, //  49
	0x8b, 0x42, 0x80, 0x06, 0x80, 0x0a, 0x80, 0x12, 0x80, 0x62, 0x00, 0x00, //  50
	0x8b, 0x84, 0x00, 0x82, 0x00, 0xa2, 0x00, 0xd2, 0x00, 0x8c, 0x00, 0x00, //  51
	0x8b, 0x08, 0x10, 0x28, 0x40, 0x88, 0x00, 0xfe, 0x00, 0x08, 0x00, 0x00, //  52
	0x8b, 0xe4, 0x02, 0xa0, 0x02, 0xa0, 0x02, 0xa0, 0x02, 0x9c, 0x00, 0x00, //  53
	0x8b, 0x0c, 0x12, 0x20, 0x52, 0x80, 0x12, 0x00, 0x12, 0x0c, 0x00, 0x00, //  54
	0x8b, 0x80, 0x00, 0x82, 0x04, 0x88, 0x10, 0xa0, 0x40, 0x80, 0x00, 0x00, //  55
	0x8b, 0x6c, 0x92, 0x00, 0x92, 0x00, 0x92, 0x00, 0x92, 0x6c, 0x00, 0x00, //  56
	0x8b, 0x60, 0x90, 0x00, 0x90, 0x02, 0x94, 0x08, 0x90, 0x60, 0x00, 0x00, //  57
	0xa7, 0x00, 0x00, 0x36, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  58
	0x27, 0x00, 0x00, 0x6d, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  59
	0x89, 0x10, 0x00, 0x28, 0x00, 0x44, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, //  60
	0x8b, 0x28, 0x00, 0x28, 0x00, 0x28, 0x00, 0x28, 0x00, 0x28, 0x00, 0x00, //  61
	0xab, 0x00, 0x00, 0x82, 0x00, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x00, //  62
	0x8b, 0x40, 0x80, 0x00, 0x80, 0x0a, 0x80, 0x10, 0x80, 0x60, 0x00, 0x00, //  63
	0x8b, 0x38, 0x44, 0x82, 0x10, 0xaa, 0x00, 0xaa, 0x00, 0x7a, 0x00, 0x00, //  64
	0x8b, 0x1e, 0x20, 0x48, 0x80, 0x08, 0x80, 0x48, 0x20, 0x1e, 0x00, 0x00, //  65
	0x8b, 0x82, 0x7c, 0x82, 0x10, 0x82, 0x10, 0x82, 0x10, 0x6c, 0x00, 0x00, //  66
	0x8b, 0x7c, 0x82, 0x00, 0x82, 0x00, 0x82, 0x00, 0x82, 0x44, 0x00, 0x00, //  67
	0x8b, 0x82, 0x7c, 0x82, 0x00, 0x82, 0x00, 0x82, 0x44, 0x38, 0x00, 0x00, //  68
	0x8b, 0xfe, 0x00, 0x92, 0x00, 0x92, 0x00, 0x92, 0x00, 0x82, 0x00, 0x00, //  69
	0x8b, 0xfe, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x80, 0x00, 0x00, //  70
	0x8b, 0x7c, 0x82, 0x00, 0x82, 0x10, 0x82, 0x10, 0x82, 0x5c, 0x00, 0x00, //  71
	0x8b, 0xfe, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0xfe, 0x00, 0x00, //  72
	0xa9, 0x00, 0x00, 0x82, 0x00, 0xfe, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, //  73
	0x8a, 0x0c, 0x02, 0x00, 0x82, 0x00, 0x82, 0x7c, 0x80, 0x00, 0x00, 0x00, //  74
	0x8b, 0xfe, 0x00, 0x10, 0x00, 0x28, 0x00, 0x44, 0x00, 0x82, 0x00, 0x00, //  75
	0x8b, 0xfe, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, //  76
	0x8b, 0xfe, 0x00, 0x40, 0x20, 0x10, 0x20, 0x40, 0x00, 0xfe, 0x00, 0x00, //  77
	0x8b, 0xfe, 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0xfe, 0x00, 0x00, //  78
	0x8b, 0x7c, 0x82, 0x00, 0x82, 0x00, 0x82, 0x00, 0x82, 0x7c, 0x00, 0x00, //  79
	0x8b, 0xfe, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x60, 0x00, 0x00, //  80
	0x8b, 0x7c, 0x82, 0x00, 0x82, 0x08, 0x82, 0x04, 0x80, 0x7a, 0x00, 0x00, //  81
	0x8b, 0xfe, 0x00, 0x90, 0x00, 0x90, 0x00, 0x98, 0x04, 0x62, 0x00, 0x00, //  82
	0x8b, 0x64, 0x92, 0x00, 0x92, 0x00, 0x92, 0x00, 0x92, 0x4c, 0x00, 0x00, //  83
	0x8b, 0x80, 0x00, 0x80, 0x00, 0xfe, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, //  84
	0x8b, 0xfc, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0xfc, 0x00, 0x00, //  85
	0x8b, 0xe0, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0xe0, 0x00, 0x00, //  86
	0x8b, 0xfc, 0x02, 0x04, 0x08, 0x30, 0x08, 0x04, 0x02, 0xfc, 0x00, 0x00, //  87
	0x9a, 0x00, 0x82, 0x44, 0x28, 0x10, 0x28, 0x44, 0x82, 0x00, 0x00, 0x00, //  88
	0x8b, 0x80, 0x40, 0x20, 0x10, 0x0e, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, //  89
	0x9a, 0x00, 0x82, 0x04, 0x8a, 0x10, 0xa2, 0x40, 0x82, 0x00, 0x00, 0x00, //  90
	0xa9, 0x00, 0x00, 0xfe, 0x00, 0x82, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, //  91
	0x9a, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00, 0x00, //  92
	0xa9, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, //  93
	0x8b, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0x40, 0x00, 0x20, 0x00, 0x00, //  94
	0x0b, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, //  95
	0xb7, 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  96
	0x8b, 0x04, 0x0a, 0x20, 0x0a, 0x20, 0x0a, 0x20, 0x1c, 0x02, 0x00, 0x00, //  97
	0x8a, 0xfe, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x1c, 0x00, 0x00, 0x00, //  98
	0x8a, 0x1c, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x00, 0x00, //  99
	0x8a, 0x1c, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0xfe, 0x00, 0x00, 0x00, // 100
	0x8b, 0x1c, 0x22, 0x08, 0x22, 0x08, 0x22, 0x08, 0x22, 0x18, 0x00, 0x00, // 101
	0x89, 0x10, 0x00, 0x10, 0x7e, 0x90, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, // 102
	0x0a, 0x38, 0x44, 0x01, 0x44, 0x01, 0x44, 0x01, 0x7e, 0x00, 0x00, 0x00, // 103
	0x8a, 0xfe, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x1e, 0x00, 0x00, 0x00, // 104
	0x98, 0x00, 0x22, 0x00, 0xbe, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // 105
	0x99, 0x00, 0x01, 0x00, 0x01, 0x20, 0x01, 0xbe, 0x00, 0x00, 0x00, 0x00, // 106
	0x9a, 0x00, 0xfe, 0x00, 0x08, 0x00, 0x14, 0x00, 0x22, 0x00, 0x00, 0x00, // 107
	0x98, 0x00, 0x82, 0x00, 0xfe, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // 108
	0x8b, 0x1e, 0x20, 0x00, 0x20, 0x1e, 0x20, 0x00, 0x20, 0x1e, 0x00, 0x00, // 109
	0x8a, 0x3e, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x1e, 0x00, 0x00, 0x00, // 110
	0x8b, 0x1c, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x1c, 0x00, 0x00, // 111
	0x0a, 0x7f, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x38, 0x00, 0x00, 0x00, // 112
	0x1b, 0x00, 0x38, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x7e, 0x00, 0x00, // 113
	0x8a, 0x3e, 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, // 114
	0x8b, 0x10, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x04, 0x00, 0x00, // 115
	0x8a, 0x20, 0x00, 0x7c, 0x02, 0x20, 0x02, 0x20, 0x02, 0x00, 0x00, 0x00, // 116
	0x8b, 0x3c, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x3c, 0x02, 0x00, 0x00, // 117
	0x8b, 0x20, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, // 118
	0x8b, 0x3c, 0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02, 0x3c, 0x00, 0x00, // 119
	0x89, 0x22, 0x14, 0x00, 0x08, 0x00, 0x14, 0x22, 0x00, 0x00, 0x00, 0x00, // 120
	0x0b, 0x40, 0x20, 0x11, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00, // 121
	0x89, 0x22, 0x04, 0x22, 0x08, 0x22, 0x10, 0x22, 0x00, 0x00, 0x00, 0x00, // 122
	0xaa, 0x00, 0x00, 0x10, 0x00, 0x6c, 0x82, 0x00, 0x82, 0x00, 0x00, 0x00, // 123
	0xc7, 0x00, 0x00, 0x00, 0x00, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 124
	0xaa, 0x00, 0x82, 0x00, 0x82, 0x6c, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // 125
	0x8b, 0x40, 0x80, 0x00, 0x80, 0x40, 0x20, 0x00, 0x20, 0x40, 0x00, 0x00, // 126
	0x8b, 0x7c, 0x82, 0x04, 0x8a, 0x10, 0xa2, 0x40, 0x82, 0x7c, 0x00, 0x00, // 127
	0x8a, 0x04, 0x0a, 0x80, 0x2a, 0x60, 0x0a, 0x24, 0x1a, 0x00, 0x00, 0x00, // 128
	0x8a, 0x0c, 0x12, 0x28, 0x82, 0x68, 0x02, 0x28, 0x10, 0x00, 0x00, 0x00, // 129
	0x8a, 0x0c, 0x32, 0x00, 0x82, 0x40, 0x02, 0x0c, 0x32, 0x00, 0x00, 0x00, // 130
	0x8a, 0x0c, 0x12, 0x00, 0xa0, 0x42, 0x00, 0x24, 0x18, 0x00, 0x00, 0x00, // 131
	0x98, 0x00, 0x02, 0x00, 0x16, 0x88, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, // 132
	0xa9, 0x00, 0x00, 0x40, 0xa0, 0x00, 0xa0, 0x40, 0x00, 0x00, 0x00, 0x00, // 133
	0x8b, 0x12, 0x00, 0x1e, 0x60, 0x12, 0x80, 0x12, 0x80, 0x40, 0x00, 0x00, // 134
	0x9a, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x00, 0x80, 0x00, 0x00, 0x00, // 135
	0x8a, 0x06, 0x01, 0x08, 0x01, 0x10, 0x21, 0x80, 0x02, 0x00, 0x00, 0x00, // 136
	0x8b, 0x06, 0x58, 0x80, 0x08, 0x84, 0x40, 0x06, 0x58, 0x80, 0x00, 0x00, // 137
	0x8b, 0x12, 0x4c, 0x80, 0x10, 0x80, 0x50, 0x02, 0x4c, 0x80, 0x00, 0x00, // 138
	0x8b, 0x02, 0x18, 0x24, 0x80, 0x44, 0x02, 0x48, 0x30, 0x80, 0x00, 0x00, // 139
	0x8b, 0x06, 0x38, 0xc0, 0x20, 0x88, 0x26, 0xd8, 0x02, 0x08, 0x00, 0x00, // 140
	0x8b, 0x02, 0x04, 0x08, 0x14, 0x40, 0xa4, 0x00, 0xbe, 0x40, 0x00, 0x00, // 141
	0x8a, 0x04, 0x0a, 0x20, 0x0a, 0x20, 0x8a, 0x24, 0x1a, 0x00, 0x00, 0x00, // 142
	0x1b, 0x00, 0x18, 0x21, 0x04, 0x41, 0x06, 0x40, 0x04, 0x40, 0x00, 0x00, // 143
	0x8b, 0x02, 0x10, 0x6a, 0x00, 0xaa, 0x00, 0xac, 0x10, 0x80, 0x00, 0x00, // 144
	0x8a, 0x06, 0x18, 0x60, 0x00, 0x82, 0x10, 0x82, 0x6c, 0x00, 0x00, 0x00, // 145
	0x8b, 0x0e, 0x30, 0x40, 0x90, 0x0e, 0x70, 0x82, 0x10, 0x82, 0x00, 0x00, // 146
	0x8b, 0x04, 0x22, 0x08, 0x22, 0x1c, 0x22, 0x08, 0x22, 0x10, 0x00, 0x00, // 147
	0x8b, 0x1a, 0x24, 0x42, 0x08, 0x92, 0x20, 0x84, 0x48, 0xb0, 0x00, 0x00, // 148
	0x8a, 0x0c, 0x11, 0x02, 0x2c, 0x12, 0x20, 0x44, 0x18, 0x00, 0x00, 0x00, // 149
	0xa9, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // 150
	0x8b, 0x02, 0x04, 0x08, 0x14, 0x80, 0x24, 0x00, 0x3e, 0x80, 0x00, 0x00, // 151
	0x8b, 0x0c, 0x12, 0x00, 0xa2, 0x00, 0x22, 0x00, 0xa4, 0x18, 0x00, 0x00, // 152
	0x8b, 0x0c, 0x32, 0x00, 0x82, 0x00, 0x02, 0x00, 0x8c, 0x30, 0x00, 0x00, // 153
	0x8a, 0x04, 0x0a, 0x20, 0x8a, 0x20, 0x0a, 0x24, 0x9a, 0x00, 0x00, 0x00, // 154
	0x8a, 0x0c, 0x12, 0x00, 0xa0, 0x02, 0x00, 0x24, 0x98, 0x00, 0x00, 0x00, // 155
	0x8b, 0x0c, 0x32, 0x80, 0x02, 0x00, 0x02, 0x0c, 0xb2, 0x00, 0x00, 0x00, // 156
	0x8b, 0x06, 0x18, 0x22, 0x08, 0x22, 0x48, 0x22, 0x80, 0x20, 0x00, 0x00, // 157
	0x8a, 0x0c, 0x12, 0x28, 0x02, 0x68, 0x02, 0xa8, 0x10, 0x00, 0x00, 0x00, // 158
	0x8b, 0x08, 0x20, 0x88, 0x66, 0x18, 0x20, 0x48, 0x20, 0x80, 0x00, 0x00, // 159
	0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 160
	0x9a, 0x00, 0x02, 0x00, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, // 161
	0x9a, 0x00, 0x20, 0x40, 0x80, 0x00, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, // 162
	0x8b, 0x28, 0x06, 0x38, 0xc0, 0x28, 0x06, 0x38, 0xc0, 0x28, 0x00, 0x00, // 163
	0x8a, 0x00, 0x24, 0x10, 0x46, 0x38, 0xc4, 0x10, 0x48, 0x00, 0x00, 0x00, // 164
	0x8b, 0x40, 0x82, 0x44, 0x88, 0x10, 0x22, 0x44, 0x82, 0x04, 0x00, 0x00, // 165
	0x8b, 0x0c, 0x10, 0x42, 0xa0, 0x12, 0xa8, 0x44, 0x0a, 0x10, 0x00, 0x00, // 166
	0xc8, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, // 167
	0xba, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, // 168
	0x98, 0x00, 0x02, 0x00, 0x04, 0x88, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, // 169
	0x8b, 0x10, 0x04, 0x50, 0x28, 0x10, 0x28, 0x14, 0x40, 0x10, 0x00, 0x00, // 170
	0x8b, 0x10, 0x00, 0x14, 0x08, 0x10, 0x20, 0x50, 0x00, 0x10, 0x00, 0x00, // 171
	0x29, 0x00, 0x00, 0x01, 0x04, 0x0a, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, // 172
	0x8b, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, // 173
	0xa8, 0x00, 0x00, 0x02, 0x04, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // 174
	0x9a, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, // 175
	0x8b, 0x1c, 0x20, 0x42, 0x00, 0x82, 0x00, 0x84, 0x08, 0x70, 0x00, 0x00, // 176
	0x99, 0x00, 0x02, 0x00, 0x46, 0x18, 0x62, 0x80, 0x00, 0x00, 0x00, 0x00, // 177
	0x8b, 0x02, 0x40, 0x06, 0x80, 0x0a, 0x80, 0x12, 0x80, 0x60, 0x00, 0x00, // 178
	0x8b, 0x04, 0x00, 0x82, 0x00, 0x92, 0x00, 0xb2, 0x4c, 0x80, 0x00, 0x00, // 179
	0x8b, 0x08, 0x10, 0x08, 0x20, 0x08, 0x46, 0x38, 0xc0, 0x08, 0x00, 0x00, // 180
	0x8b, 0x04, 0x60, 0x82, 0x20, 0x82, 0x20, 0x84, 0x18, 0x80, 0x00, 0x00, // 181
	0x8a, 0x0c, 0x10, 0x22, 0x10, 0x42, 0x10, 0x82, 0x0c, 0x00, 0x00, 0x00, // 182
	0x8b, 0x80, 0x02, 0x84, 0x08, 0x90, 0x20, 0x80, 0x40, 0x80, 0x00, 0x00, // 183
	0x8b, 0x0c, 0x62, 0x10, 0x82, 0x10, 0x82, 0x10, 0x8c, 0x60, 0x00, 0x00, // 184
	0x8a, 0x60, 0x02, 0x90, 0x04, 0x90, 0x08, 0x90, 0x60, 0x00, 0x00, 0x00, // 185
	0xa9, 0x00, 0x00, 0x02, 0x14, 0x22, 0x14, 0x20, 0x00, 0x00, 0x00, 0x00, // 186
	0x2a, 0x00, 0x00, 0x01, 0x04, 0x2a, 0x44, 0x28, 0x40, 0x00, 0x00, 0x00, // 187
	0x9a, 0x00, 0x10, 0x08, 0x24, 0x02, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, // 188
	0x8a, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 0x00, 0x00, 0x00, // 189
	0x9a, 0x00, 0x02, 0x00, 0x04, 0x80, 0x48, 0x20, 0x10, 0x00, 0x00, 0x00, // 190
	0x8a, 0x48, 0x02, 0x80, 0x08, 0x80, 0x10, 0x80, 0x60, 0x00, 0x00, 0x00, // 191
	0x8b, 0x1c, 0x20, 0x42, 0x80, 0x12, 0x88, 0x22, 0x88, 0x70, 0x00, 0x00, // 192
	0x8b, 0x02, 0x04, 0x08, 0x10, 0x28, 0x40, 0x88, 0x00, 0xfe, 0x00, 0x00, // 193
	0x8b, 0x06, 0x98, 0x62, 0x80, 0x12, 0x80, 0x12, 0x8c, 0x60, 0x00, 0x00, // 194
	0x8b, 0x1c, 0x22, 0x40, 0x82, 0x00, 0x82, 0x00, 0x84, 0x40, 0x00, 0x00, // 195
	0x8b, 0x06, 0x98, 0x62, 0x80, 0x02, 0x80, 0x04, 0x88, 0x70, 0x00, 0x00, // 196
	0x8b, 0x06, 0x38, 0xc2, 0x10, 0x82, 0x10, 0x82, 0x00, 0x80, 0x00, 0x00, // 197
	0x8b, 0x06, 0x38, 0xc0, 0x10, 0x80, 0x10, 0x80, 0x00, 0x80, 0x00, 0x00, // 198
	0x8b, 0x1c, 0x22, 0x40, 0x82, 0x00, 0x92, 0x04, 0x98, 0x40, 0x00, 0x00, // 199
	0x8b, 0x06, 0x38, 0xc0, 0x10, 0x00, 0x10, 0x06, 0x38, 0xc0, 0x00, 0x00, // 200
	0x92, 0x00, 0x02, 0x00, 0x86, 0x18, 0xe2, 0x00, 0x80, 0x00, 0x00, 0x00, // 201
	0x8b, 0x0c, 0x02, 0x00, 0x02, 0x80, 0x04, 0x98, 0x60, 0x80, 0x00, 0x00, // 202
	0x8b, 0x06, 0x38, 0xc0, 0x10, 0x20, 0x08, 0x44, 0x02, 0x80, 0x00, 0x00, // 203
	0x9a, 0x00, 0x06, 0x18, 0x62, 0x80, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, // 204
	0x8b, 0x06, 0x38, 0xc0, 0x00, 0x30, 0x00, 0x46, 0x38, 0xc0, 0x00, 0x00, // 205
	0x8b, 0x06, 0x38, 0xc0, 0x20, 0x10, 0x08, 0x06, 0x38, 0xc0, 0x00, 0x00, // 206
	0x8b, 0x0c, 0x32, 0x40, 0x82, 0x00, 0x82, 0x04, 0x98, 0x60, 0x00, 0x00, // 207
	0x8b, 0x06, 0x18, 0x60, 0x90, 0x00, 0x90, 0x00, 0x90, 0x60, 0x00, 0x00, // 208
	0x8b, 0x1c, 0x20, 0x42, 0x00, 0x8a, 0x00, 0x84, 0x0a, 0x70, 0x00, 0x00, // 209
	0x8b, 0x06, 0x18, 0x60, 0x80, 0x10, 0x88, 0x14, 0x82, 0x60, 0x00, 0x00, // 210
	0x8b, 0x04, 0x62, 0x00, 0x92, 0x00, 0x92, 0x00, 0x8c, 0x40, 0x00, 0x00, // 211
	0x8b, 0x80, 0x00, 0x86, 0x18, 0xe0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, // 212
	0x8b, 0x0c, 0x32, 0xc0, 0x02, 0x00, 0x02, 0x0c, 0x30, 0xc0, 0x00, 0x00, // 213
	0x9b, 0x00, 0xfe, 0x00, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, // 214
	0x8b, 0x06, 0x38, 0xc4, 0x08, 0x10, 0x08, 0x06, 0x38, 0xc0, 0x00, 0x00, // 215
	0x8b, 0x02, 0x84, 0x48, 0x20, 0x18, 0x24, 0x02, 0x40, 0x80, 0x00, 0x00, // 216
	0x8b, 0x80, 0x40, 0x26, 0x18, 0x00, 0x20, 0x00, 0x40, 0x80, 0x00, 0x00, // 217
	0x8b, 0x02, 0x04, 0x8a, 0x00, 0x92, 0x00, 0xa2, 0x40, 0x80, 0x00, 0x00, // 218
	0x9b, 0x00, 0x06, 0x18, 0x62, 0x80, 0x02, 0x80, 0x00, 0x80, 0x00, 0x00, // 219
	0xa8, 0x00, 0x00, 0xc0, 0x30, 0x0c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // 220
	0x8a, 0x02, 0x00, 0x02, 0x80, 0x06, 0x98, 0x60, 0x80, 0x00, 0x00, 0x00, // 221
	0x9a, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x40, 0x20, 0x00, 0x00, 0x00, // 222
	0x0b, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, // 223
	0xb7, 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 224
	0x8a, 0x04, 0x0a, 0x20, 0x0a, 0x20, 0x0a, 0x24, 0x1a, 0x00, 0x00, 0x00, // 225
	0x8a, 0x06, 0x18, 0xe2, 0x00, 0x22, 0x00, 0x24, 0x18, 0x00, 0x00, 0x00, // 226
	0x8a, 0x0c, 0x10, 0x02, 0x20, 0x02, 0x20, 0x02, 0x20, 0x00, 0x00, 0x00, // 227
	0x8b, 0x0c, 0x10, 0x02, 0x20, 0x02, 0x20, 0x06, 0x38, 0xc0, 0x00, 0x00, // 228
	0x8a, 0x0c, 0x12, 0x28, 0x02, 0x28, 0x02, 0x28, 0x10, 0x00, 0x00, 0x00, // 229
	0x8b, 0x20, 0x00, 0x26, 0x18, 0x60, 0x00, 0xa0, 0x00, 0x80, 0x00, 0x00, // 230
	0x1b, 0x00, 0x18, 0x25, 0x00, 0x45, 0x00, 0x46, 0x18, 0x60, 0x00, 0x00, // 231
	0x8a, 0x06, 0x18, 0xe0, 0x00, 0x20, 0x00, 0x26, 0x18, 0x00, 0x00, 0x00, // 232
	0x99, 0x00, 0x02, 0x00, 0x26, 0x18, 0x22, 0x80, 0x00, 0x00, 0x00, 0x00, // 233
	0x89, 0x01, 0x00, 0x01, 0x00, 0x26, 0x18, 0xa0, 0x00, 0x00, 0x00, 0x00, // 234
	0x8a, 0x06, 0x18, 0x60, 0x88, 0x04, 0x12, 0x00, 0x20, 0x00, 0x00, 0x00, // 235
	0x99, 0x00, 0x02, 0x00, 0x06, 0x98, 0x62, 0x80, 0x00, 0x00, 0x00, 0x00, // 236
	0x8a, 0x26, 0x18, 0x20, 0x06, 0x38, 0x00, 0x26, 0x18, 0x00, 0x00, 0x00, // 237
	0x89, 0x26, 0x18, 0x20, 0x00, 0x20, 0x06, 0x18, 0x00, 0x00, 0x00, 0x00, // 238
	0x8a, 0x0c, 0x12, 0x00, 0x20, 0x02, 0x00, 0x24, 0x18, 0x00, 0x00, 0x00, // 239
	0x0a, 0x03, 0x1c, 0x60, 0x04, 0x40, 0x04, 0x48, 0x30, 0x00, 0x00, 0x00, // 240
	0x1b, 0x00, 0x18, 0x24, 0x00, 0x44, 0x00, 0x47, 0x18, 0x60, 0x00, 0x00, // 241
	0x89, 0x06, 0x38, 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, // 242
	0x8a, 0x02, 0x10, 0x02, 0x28, 0x02, 0x28, 0x04, 0x20, 0x00, 0x00, 0x00, // 243
	0x9a, 0x00, 0x20, 0x0c, 0x32, 0xc0, 0x22, 0x00, 0x20, 0x00, 0x00, 0x00, // 244
	0x8a, 0x0c, 0x32, 0x00, 0x02, 0x00, 0x02, 0x0c, 0x32, 0x00, 0x00, 0x00, // 245
	0x9a, 0x00, 0x3e, 0x00, 0x04, 0x00, 0x08, 0x10, 0x20, 0x00, 0x00, 0x00, // 246
	0x8b, 0x0e, 0x30, 0x04, 0x00, 0x18, 0x04, 0x00, 0x06, 0x38, 0x00, 0x00, // 247
	0x8b, 0x02, 0x00, 0x24, 0x10, 0x08, 0x04, 0x12, 0x00, 0x20, 0x00, 0x00, // 248
	0x1b, 0x00, 0x40, 0x21, 0x12, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00, // 249
	0x8b, 0x02, 0x00, 0x26, 0x00, 0x2a, 0x00, 0x32, 0x00, 0x20, 0x00, 0x00, // 250
	0x9a, 0x00, 0x10, 0x04, 0x1a, 0x60, 0x82, 0x00, 0x80, 0x00, 0x00, 0x00, // 251
	0x99, 0x00, 0x02, 0x04, 0x08, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, 0x00, // 252
	0x9a, 0x00, 0x02, 0x00, 0x82, 0x0c, 0xb0, 0x40, 0x10, 0x00, 0x00, 0x00, // 253
	0x8b, 0x40, 0x80, 0x00, 0x80, 0x40, 0x20, 0x00, 0x20, 0x40, 0x00, 0x00, // 254
	0x8b, 0x1a, 0x24, 0x42, 0x08, 0x92, 0x20, 0x84, 0x48, 0xb0, 0x00, 0x00  // 255
};

ImagePrinterEpson::ImagePrinterEpson(MSXMotherBoard& motherBoard_)
	: ImagePrinter(motherBoard_, false)
{
	resetEmulatedPrinter();
}

zstring_view ImagePrinterEpson::getName() const
{
	return "epson-printer";
}

zstring_view ImagePrinterEpson::getDescription() const
{
	return "Emulate Epson FX80 printer, prints to image.";
}

std::pair<unsigned, unsigned> ImagePrinterEpson::getNumberOfDots()
{
	return {610, 825};
}

void ImagePrinterEpson::resetSettings()
{
	lineFeed     = 12.0;
	leftBorder   = 48;
	rightBorder  = leftBorder + 480;
	graphDensity = 1.0;
	fontDensity  = 1.0;
	pageTop      = 48;
	lines        = 72;
	fontWidth    = 6;
	eightBit     = -1;

	copy_to_range(EpsonFontRom, fontInfo.rom);
	fontInfo.charWidth = 12;
	fontInfo.pixelDelta = 0.5;
	fontInfo.useRam = false;
}

unsigned ImagePrinterEpson::calcEscSequenceLength(uint8_t character)
{
	switch (character & 127) {
		case '!': case '-': case '/': case '3': case 'A': case 'J':
		case 'N': case 'Q': case 'R': case 'S': case 'U': case 'W':
		case 'b': case 'i': case 'j': case 'l': case 'p': case 's':
			return 1;
		case '%': case '?': case 'K': case 'L': case 'Z': case '^':
			return 2;
		case '*': case ':': case '&':
			return 3;
		case 'B': // Set tabs, variable length (up to 16 tabs)
			return 0;
		case 'C': // Set form length, variable length (2 or 3)
			return 0;
		case 'D': // Set tabs, variable length (up to 32 tabs)
			return 0;
		default:
			return 0;
	}
}

unsigned ImagePrinterEpson::parseNumber(unsigned sizeStart, unsigned sizeChars)
{
	unsigned Value = 0;
	sizeStart += sizeChars;
	while (sizeChars--) {
		Value = Value * 256 + abEscSeq[--sizeStart];
	}
	return Value;
}

void ImagePrinterEpson::processEscSequence()
{
	uint8_t character = abEscSeq[0] & 127;

	switch (character) {
		case '!': { // Master Print Mode Select
			unsigned masterSelect  = parseNumber(1, 1);
			elite        = (masterSelect & 1) != 0;
			compressed   = (masterSelect & 4) != 0;
			bold         = (masterSelect & 8) != 0;
			doubleStrike = (masterSelect & 16) != 0;
			doubleWidth  = (masterSelect & 32) != 0;

			if (elite) {
				fontDensity = 1.20;
			} else if (compressed) {
				fontDensity = 1.72;
			} else {
				fontDensity = 1.00;
			}
			break;
		}
		case '#': // Accept Eight Bit as-is
			break;
		case '%': // Activates Character Set
			fontInfo.useRam = parseNumber(1, 1) & 1;
			break;
		case '&': // Custom character set, variable length
			ramLoadOffset =       12 * parseNumber(2, 1);
			ramLoadEnd = std::max(12 * parseNumber(3, 1) + 12, ramLoadOffset);
			break;
		case '*': // Turn Graphics Mode ON
			ninePinGraphics = false;
			switch (parseNumber(1, 1)) {
				default:
				case 0:
					graphDensity = 1.0;
					break;
				case 1: case 2:
					graphDensity = 2.0;
					break;
				case 3:
					graphDensity = 4.0;
					break;
				case 4:
					graphDensity = 1.33;
					break;
				case 5:
					graphDensity = 1.2;
					break;
				case 6:
					graphDensity = 1.5;
					break;
			}
			sizeRemainingDataBytes = parseNumber(2, 2);
			break;
		case '-': // Turn Underline Mode ON/OFF
			underline = parseNumber(1, 1) != 0;
			break;
		case '/': // Selects Vertical Tab Channel
			break;
		case '0': // Sets Line Spacing to 1/8 inch
			lineFeed = 9.0;
			break;
		case '1': // Sets Line Spacing to 7/72 inch
			lineFeed = 7.0;
			break;
		case '2': // Sets Line Spacing to 1/6 inch
			lineFeed = 12.0;
			break;
		case '3': // Sets Line Spacing to n/216 inch
			lineFeed = (parseNumber(1, 1) & 127) * (1.0 / 3.0);
			break;
		case '4': // Turn Italic Mode ON
			italic = true;
			break;
		case '5': // Turn Italic Mode OFF
			italic = false;
			break;
		case '6': // Turn Printing of International Italic characters ON
			noHighEscapeCodes = true;
			break;
		case '7': // Turn Printing of International Italic characters OFF
			noHighEscapeCodes = false;
			break;
		case '8': // Turn Paper Out Sensor ON
			detectPaperOut = true;
			break;
		case '9': // Turn Paper Out Sensor OFF
			detectPaperOut = false;
			break;
		case ':': // Copies Rom Character set to RAM
			copy_to_range(fontInfo.rom, fontInfo.ram);
			break;
		case '<': // Turn Uni-directional printing ON (left to right)
			leftToRight = true;
			break;
		case '=': // Sets eight bit to 0
			eightBit = 0;
			break;
		case '>': // Sets eight bit to 1
			eightBit = 1;
			break;
		case '?': // Redefines Graphics Codes
			break;
		case '@': // Reset
			eightBit = -1;
			ninePinGraphics = false;
			graphDensity = 1.0;
			fontDensity = 1.0;
			underline = false;
			lineFeed = 12.0;
			italic = false;
			detectPaperOut = false;
			leftToRight = false;
			doubleStrike = false;
			elite = false;
			compressed = false;
			rightBorder = 6 * 78;
			subscript = false;
			superscript = false;
			doubleWidth = false;
			bold = false;
			proportional = false;
			fontInfo.useRam = false;
			noHighEscapeCodes = false;
			alternateChar = false;
			countryCode = CountryCode::USA;
			break;
		case 'A': // Sets Line Spacing to n/72 inch
			lineFeed = parseNumber(1, 1) & 127;
			break;
		case 'B': // Set tabs, variable length (up to 16 tabs)
			break;
		case 'C': // Set form length, variable length (2 or 3)
			break;
		case 'D': // Set tabs, variable length (up to 32 tabs)
			break;
		case 'E': // Turn Emphasized Mode ON
			bold = true;
			break;
		case 'F': // Turn Emphasized Mode OFF
			bold = false;
			break;
		case 'G': // Turn Double Strike Mode ON
			doubleStrike = true;
			break;
		case 'H': // Turn Double Strike Mode OFF
			doubleStrike = false;
			break;
		case 'I': // Enables printing of chars 1-31
			alternateChar = parseNumber(1, 1) & 1;
			break;
		case 'J': // Forces Line Feed with n/216 inch
			vpos += (parseNumber(1, 1) & 127) * (1.0 / 3.0);
			if (vpos >= pageHeight) {
				flushEmulatedPrinter();
			}
			break;
		case 'K': // Turn Single Density Graphics on (480 dot mode)
			graphDensity = 1.0;
			ninePinGraphics = false;
			sizeRemainingDataBytes = parseNumber(1, 2);
			break;
		case 'L': // Turn Double Density Graphics on (960 dot mode)
			graphDensity = 2.0;
			ninePinGraphics = false;
			sizeRemainingDataBytes = parseNumber(1, 2);
			break;
		case 'M': // Turn Elite mode ON
			elite = true;
			fontDensity = 1.20;
			break;
		case 'N': // Turn Skip Over Perforation ON
			break;
		case 'O': // Turn Skip Over Perforation OFF
			break;
		case 'P': // Turn Elite mode OFF
			elite = false;
			fontDensity = compressed ? 1.72 : 1.00;
			break;
		case 'Q': { // Set Right Margin
			auto width = std::min(parseNumber(1, 2), 78u); // TODO Font dependent !!
			rightBorder = 6 * width;
			break;
		}
		case 'R': // Select International Character Set
			countryCode = static_cast<CountryCode>(parseNumber(1, 1));
			if (countryCode > CountryCode::JAPAN) {
				countryCode = CountryCode::USA;
			}
			break;
		case 'S': { // Turn Script Mode ON
			auto script = parseNumber(1, 1) & 1;
			superscript = script == 0;
			subscript   = script == 1;
			break;
		}
		case 'T': // Turn Script Mode OFF
			subscript = false;
			superscript = false;
			break;
		case 'U': // Turn Uni-directional mode ON/OFF
			leftToRight = parseNumber(1, 1) != 0;
			break;
		case 'W': // Turn Expanded Mode ON/OFF
			normalAfterLine = false;
			doubleWidth = parseNumber(1, 1) != 0;
			break;
		case 'Y': // Turn High Speed Double Density Graphics ON
			break;
		case 'Z': // Turns Quadruple Density Graphics ON
			graphDensity = 4.0;
			ninePinGraphics = false;
			sizeRemainingDataBytes = parseNumber(1, 2);
			break;
		case '^': // Turn Nine Pin Graphics Mode ON
			graphDensity = parseNumber(1, 1) ? 2.0 : 1.0;
			ninePinGraphics = true;
			sizeRemainingDataBytes = 2 * parseNumber(2, 2);
			break;
		case 'b': // Set Vertical Tab
			break;
		case 'i': // Turn Immediate Mode ON/OFF
			break;
		case 'j': // Immediate Reverse Line Feed
			vpos -= (parseNumber(1, 1) & 127) * (1.0 / 3.0);
			vpos = std::max(vpos, pageTop);
			break;
		case 'l': // Set Left Margin
			break;
		case 'p': // Turn proportional mode ON/OFF
			proportional = parseNumber(1, 1) != 0;
			break;
		case 's': // Set Print Speed
			break;
		case 127: // Deletes Last Character in Buffer
			break;
	}
}

// International character code translation for the Epson FX-80 printer
//                                                   US   FR   DE   GB   DK   SE   IT   SP   JP
static constexpr std::array<uint8_t, 9> intlChar35  = { 35,  35,  35,   6,  35,  35,  35,  12,  35};
static constexpr std::array<uint8_t, 9> intlChar36  = { 36,  36,  36,  36,  36,  11,  36,  36,  36};
static constexpr std::array<uint8_t, 9> intlChar64  = { 64,   0,  16,  64,  64,  29,  64,  64,  64};
static constexpr std::array<uint8_t, 9> intlChar91  = { 91,   5,  23,  91,  18,  23,   5,   7,  91};
static constexpr std::array<uint8_t, 9> intlChar92  = { 92,  15,  24,  92,  20,  24,  92,   9,  31};
static constexpr std::array<uint8_t, 9> intlChar93  = { 93,  16,  25,  93,  13,  13,  30,   8,  93};
static constexpr std::array<uint8_t, 9> intlChar94  = { 94,  94,  94,  94,  94,  25,  94,  94,  94};
static constexpr std::array<uint8_t, 9> intlChar96  = { 96,  96,  96,  96,  96,  30,   2,  96,  96};
static constexpr std::array<uint8_t, 9> intlChar123 = {123,  30,  26, 123,  19,  26,   0,  22, 123};
static constexpr std::array<uint8_t, 9> intlChar124 = {124,   2,  27, 124,  21,  27,   3,  10, 124};
static constexpr std::array<uint8_t, 9> intlChar125 = {125,   1,  28, 125,  14,  14,   1, 125, 125};
static constexpr std::array<uint8_t, 9> intlChar126 = {126,  22,  17, 126, 126,  28,   4, 126, 126};

void ImagePrinterEpson::processCharacter(uint8_t data)
{
	if (data >= 32) {
		if (italic) {
			data |= 128;
		} else {
			data &= 127;
		}
	}

	if (!noHighEscapeCodes && data >= 128 && data < 160) {
		data &= 31;
	}

	// Convert international characters
	auto cc = std::to_underlying(countryCode);
	switch (data & 0x7f) {
		case 35:  data = (data & 0x80) | intlChar35 [cc]; break;
		case 36:  data = (data & 0x80) | intlChar36 [cc]; break;
		case 64:  data = (data & 0x80) | intlChar64 [cc]; break;
		case 91:  data = (data & 0x80) | intlChar91 [cc]; break;
		case 92:  data = (data & 0x80) | intlChar92 [cc]; break;
		case 93:  data = (data & 0x80) | intlChar93 [cc]; break;
		case 94:  data = (data & 0x80) | intlChar94 [cc]; break;
		case 96:  data = (data & 0x80) | intlChar96 [cc]; break;
		case 123: data = (data & 0x80) | intlChar123[cc]; break;
		case 124: data = (data & 0x80) | intlChar124[cc]; break;
		case 125: data = (data & 0x80) | intlChar125[cc]; break;
		case 126: data = (data & 0x80) | intlChar126[cc]; break;
	}

	if (data >= 32) {
		printVisibleCharacter(data);
		return;
	}

	switch (data) {
		case 0: // Terminates horizontal and vertical TAB setting
			break;
		case 7: // Sound beeper
			break;
		case 8: // Backspace
			// TODO: fix for other font-sizes
			hpos = std::max(hpos - 8, double(leftBorder));
			break;
		case 9: // Horizontal TAB
			// TODO: fix for other font-sizes
			hpos = ((unsigned(hpos) + 64 - leftBorder) & ~63)
			     + leftBorder;
			if (hpos < rightBorder) {
				break;
			}
			hpos = leftBorder;
			[[fallthrough]];
		case 10: // Line Feed
		case 11: // Vertical TAB
			vpos += lineFeed;
			if (vpos >= pageHeight) {
				flushEmulatedPrinter();
			}
			break;
		case 12: // Form Feed
			ensurePrintPage();
			flushEmulatedPrinter();
			break;
		case 13: // Carrige return
			hpos = leftBorder;
			break;
		case 14: // Turns expanded mode ON
			doubleWidth = true;
			normalAfterLine = true;
			break;
		case 15: // Shift in. Empties buffer, turns compressed mode ON (17.16 cpi)
			compressed = true;
			if (!elite) {
				fontDensity = 1.72;
			}
			break;
		case 17: // Device Control 1:
			break;
		case 18: // Device Control 2: turns compressed mode OFF
			compressed = false;
			fontDensity = 1.00;
			break;
		case 19: // Device Control 3:
			break;
		case 20: // Device Control 4: Turns expanded mode OFF
			doubleWidth = false;
			break;
		case 24: // Cancels all text in the print buffer
			break;
		case 27: // Escape
			escSequence = true;
			break;
		default:
			if (alternateChar) {
				printVisibleCharacter(data);
			}
			break;
	}
}

template<typename Archive>
void ImagePrinterEpson::serialize(Archive& /*ar*/, unsigned /*version*/)
{
	// TODO is this worth it?
}
INSTANTIATE_SERIALIZE_METHODS(ImagePrinterEpson);
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, ImagePrinterEpson, "ImagePrinterEpson");


} // namespace openmsx
