/*
 * xep80.c - XEP80 emulation
 *
 * Copyright (C) 2007 Mark Grebe
 *
 * This file is part of the Atari800 emulator project which emulates
 * the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers.
 *
 * Atari800 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 2 of the License, or
 * (at your option) any later version.
 *
 * Atari800 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 Atari800; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"
#ifdef XEP80_EMULATION
#include "xep80.h"
#include "xep80_fonts.h"
#include "statesav.h"
#include "atari.h"
#include "antic.h"
#include <string.h>
#include "platform.h"
#include "util.h"
#include "log.h"
#if SUPPORTS_CHANGE_VIDEOMODE
#include <videomode.h>
#endif /* SUPPORTS_CHANGE_VIDEOMODE */

#define IN_QUEUE_SIZE		10

/* Definitions for command protocol between the XEP and the Atari */
#define CMD_XX_MASK      0xC0
#define CMD_00           0x00
#define CMD_X_CUR_LOW    0x00
#define CMD_01           0x40
#define CMD_01_MASK      0x70
#define CMD_X_CUR_UPPER  0x40
#define CMD_X_CUR_HIGH   0x50
#define CMD_LEFT_MAR_L   0x60
#define CMD_LEFT_MAR_H   0x70
#define CMD_10           0x80
#define CMD_10_MASK      0xF0
#define CMD_Y_CUR_LOW    0x80
#define CMD_1001         0x90
#define CMD_1001_MASK    0xF8
#define CMD_Y_CUR_HIGH   0x90
#define CMD_Y_CUR_STATUS 0x98
#define CMD_GRAPH_60HZ   0x99
#define CMD_GRAPH_50HZ   0x9A
#define CMD_RIGHT_MAR_L  0xA0
#define CMD_RIGHT_MAR_H  0xB0
#define CMD_11           0xC0
#define CMD_GET_CHAR     0xC0
#define CMD_REQ_X_CUR    0xC1
#define CMD_MRST         0xC2
#define CMD_PRT_STAT     0xC3
#define CMD_FILL_PREV    0xC4
#define CMD_FILL_SPACE   0xC5
#define CMD_FILL_EOL     0xC6
#define CMD_CLR_LIST     0xD0
#define CMD_SET_LIST     0xD1
#define CMD_SCR_NORMAL   0xD2
#define CMD_SCR_BURST    0xD3
#define CMD_CHAR_SET_A   0xD4
#define CMD_CHAR_SET_B   0xD5
#define CMD_CHAR_SET_INT 0xD6
#define CMD_TEXT_50HZ    0xD7
#define CMD_CUR_OFF      0xD8
#define CMD_CUR_ON       0xD9
#define CMD_CUR_BLINK    0xDA
#define CMD_CUR_ST_LINE  0xDB
#define CMD_SET_SCRL_WIN 0xDC
#define CMD_SET_PRINT    0xDD
#define CMD_WHT_ON_BLK   0xDE
#define CMD_BLK_ON_WHT   0xDF
#define CMD_VIDEO_CTRL   0xED
#define CMD_ATTRIB_A     0xF4
#define CMD_ATTRIB_B     0xF5

#define CHAR_SET_A          0
#define CHAR_SET_B          1
#define CHAR_SET_INTERNAL   2

/* These center the graphics screen inside of the XEP80 screen */
#define GRAPH_X_OFFSET ((XEP80_SCRN_WIDTH - XEP80_GRAPH_WIDTH) / 2)
#define GRAPH_Y_OFFSET ((XEP80_scrn_height - XEP80_GRAPH_HEIGHT) / 2)

/* Used to determine if a character is double width */
#define IS_DOUBLE(x,y) (((char_data(y, x) & 0x80) && font_b_double) || \
                        (((char_data(y, x) & 0x80) == 0) && font_a_double))

/* Global variables */
int XEP80_enabled = FALSE;
int XEP80_port = 0;

int XEP80_char_height = XEP80_CHAR_HEIGHT_NTSC;
int XEP80_scrn_height = XEP80_HEIGHT * XEP80_CHAR_HEIGHT_NTSC;

/* Local state variables */
static int output_word = 0;

static UWORD input_queue[IN_QUEUE_SIZE];
static int input_count = 0;

/* Indicates moment when receiving of a word started,
   or a moment when transmitting of the first word in the output queue
   started. Used to compare with ANTIC_CPU_CLOCK when determining
   a bit currently transmitted. */
static unsigned int start_trans_cpu_clock;
/* Indicates that a byte is currently being received. */
static int receiving = FALSE;


/* Values in internal RAM */
static int ypos = 0; /* location: $01 (R1) */
static int xpos = 0; /* location: $02 (R2) */
static UBYTE last_char = 0; /* location: $04 (R4) */
static int lmargin = 0; /* location: $05 (R5) */
static int rmargin = 0x4f; /* location: $06 (R6) */
static int xscroll = 0; /* location: $1f (RAM bank 1 R7) */
/* 25 pointers to start of data for each line. Originally at locations $20..$38. */
static UBYTE *line_pointers[XEP80_HEIGHT];
static int old_ypos = 0; /* location: $39 */
static int old_xpos = 0; /* location: $3a */
static int list_mode = FALSE; /* location: $3b */
static int escape_mode = FALSE; /* location: $3c */
/* location: $3f */
static int burst_mode = FALSE; /* bit 0 */
static int screen_output = TRUE; /* bit 7; indicates screen/printer */

/* Attribute Latch 0 */
static UBYTE attrib_a = 0xff;
static int font_a_index = 0;
static int font_a_double = FALSE;
static int font_a_blank = FALSE;
static int font_a_blink = FALSE;
/* Attribute Latch 1 */
static UBYTE attrib_b = 0xff;
static int font_b_index = 0;
static int font_b_double = FALSE;
static int font_b_blank = FALSE;
static int font_b_blink = FALSE;

/* TCP */
static int cursor_on = TRUE; /* byte 13 */
static int graphics_mode = FALSE;
static int pal_mode = FALSE;

/* VCR*/
static int blink_reverse = FALSE; /* bit 0 */
static int cursor_blink = FALSE; /* bit 1 */
static int cursor_overwrite = FALSE; /* bit 2 */
static int inverse_mode = FALSE; /* bit 3 */
static int char_set = CHAR_SET_A; /* bits 6-7 */

/* CURS */
static int cursor_x = 0;
static int cursor_y = 0;
static int curs = 0; /* Address of cursor in video RAM, $0000..$1fff */

static UBYTE video_ram[0x2000]; /* 8 KB of RAM */
#define char_data(y, x)	(*(line_pointers[(y)]+(x)))
#define graph_data(y, x) (video_ram[(y)*XEP80_GRAPH_WIDTH/8+(x)])
#define tab_stops(x) (video_ram[0x1900+(x)])

static UBYTE const input_mask[2] = {0x02,0x20};
static UBYTE const output_mask[2] = {0x01,0x10};

UBYTE XEP80_screen_1[XEP80_SCRN_WIDTH*XEP80_MAX_SCRN_HEIGHT];
UBYTE XEP80_screen_2[XEP80_SCRN_WIDTH*XEP80_MAX_SCRN_HEIGHT];

UBYTE (*font)[XEP80_FONTS_CHAR_COUNT][XEP80_MAX_CHAR_HEIGHT][XEP80_CHAR_WIDTH];

/* Path to the XEP80's charset ROM image, marked as U12 on Jerzy Sobola's
   schematic: http://www.dereatari.republika.pl/schematy.htm
   The ROM image is also available there. */
static char charset_filename[FILENAME_MAX];

static void UpdateTVSystem(void)
{
	XEP80_char_height = pal_mode ? XEP80_CHAR_HEIGHT_PAL : XEP80_CHAR_HEIGHT_NTSC;
	XEP80_scrn_height = XEP80_HEIGHT * XEP80_char_height;
#if SUPPORTS_CHANGE_VIDEOMODE
	VIDEOMODE_UpdateXEP80();
#endif
}

/* --------------------------------------
   Functions for blitting display buffer.
   -------------------------------------- */

static void BlitChar(int x, int y, int cur)
{
	int screen_col;
	int font_row, font_col;
	UBYTE *from, *to;
	UBYTE ch;
	UBYTE on, off, blink;
	int font_index, font_double, font_blank, font_blink;
	int blink_rev;
	int last_double_cur = FALSE;

	/* Don't Blit characters that aren't on the screen at the moment. */
	if (x < xscroll || x >= xscroll + XEP80_LINE_LEN)
		return;

	screen_col = x-xscroll;
	ch = char_data(y, x);

	/* Dispaly Atari EOL's as spaces */
	if (ch == XEP80_ATARI_EOL && ((font_a_index & XEP80_FONTS_BLK_FONT_BIT) == 0)
	    && char_set != CHAR_SET_INTERNAL)
		ch = 0x20;

	if (ch & 0x80) {
		font_index = font_b_index;
		font_double = font_b_double;
		font_blank = font_b_blank;
		font_blink = font_b_blink;
	}
	else {
		font_index = font_a_index;
		font_double = font_a_double;
		font_blank = font_a_blank;
		font_blink = font_a_blink;
	}

	if (font_blink && blink_reverse && (font_index & XEP80_FONTS_REV_FONT_BIT))
		blink_rev = TRUE;
	else
		blink_rev = FALSE;

	if (inverse_mode)
		font_index ^= XEP80_FONTS_REV_FONT_BIT;

	if (ch==XEP80_ATARI_EOL) {
		if (inverse_mode)
			font_index |= XEP80_FONTS_REV_FONT_BIT;
		else
			font_index &= ~XEP80_FONTS_REV_FONT_BIT;
	}

	/* Skip the charcter if the last one was a displayed double */
	if (screen_col != 0 && !cur) {
		if (IS_DOUBLE(x-1,y)) {
			int firstd;

			firstd = x-1;
			while (firstd > xscroll) {
				if (!IS_DOUBLE(firstd,y)) {
					firstd++;
					break;
				}
				firstd--;
			}
			if ((x-firstd) % 2)
				return;
		}
	}

	/* Check if we are doing a cursor, and the charcter before is double */
	if (cur) {
		if (screen_col != 0) {
			if (IS_DOUBLE(x-1,y))
				last_double_cur = TRUE;
		}
	}

	if (inverse_mode) {
		on = XEP80_FONTS_offcolor;
		off = XEP80_FONTS_oncolor;
	}
	else {
		on = XEP80_FONTS_oncolor;
		off = XEP80_FONTS_offcolor;
	}

	if (font_index & XEP80_FONTS_REV_FONT_BIT)
		blink = on;
	else
		blink = off;

	if (font_blank) {
		UBYTE color;

		to = &XEP80_screen_1[XEP80_SCRN_WIDTH * XEP80_char_height * y +
		                     screen_col * XEP80_CHAR_WIDTH];
		for (font_row=0;font_row < XEP80_char_height; font_row++) {
			if (cur || (font_index & XEP80_FONTS_REV_FONT_BIT))
				color = on;
			else
				color = off;

			for (font_col=0; font_col < XEP80_CHAR_WIDTH; font_col++) {
				if (font_double)
					*to++ = color;
				*to++ = color;
			}
			if (font_double)
				to += XEP80_SCRN_WIDTH - 2*XEP80_CHAR_WIDTH;
			else
				to += XEP80_SCRN_WIDTH - 1*XEP80_CHAR_WIDTH;
		}

		to = &XEP80_screen_2[XEP80_SCRN_WIDTH * XEP80_char_height * y +
		                     screen_col * XEP80_CHAR_WIDTH];
		for (font_row=0;font_row < XEP80_char_height; font_row++) {
			if ((cur && !cursor_blink) || (font_index & XEP80_FONTS_REV_FONT_BIT))
				color = on;
			else
				color = off;

			for (font_col=0; font_col < XEP80_CHAR_WIDTH; font_col++) {
				if (font_double)
					*to++ = color;
				*to++ = color;
			}
			if (font_double)
				to += XEP80_SCRN_WIDTH - 2*XEP80_CHAR_WIDTH;
			else
				to += XEP80_SCRN_WIDTH - 1*XEP80_CHAR_WIDTH;
		}
	}
	else if (font_double && !cur) {
		int width;

		if (screen_col == 79)
			width = XEP80_CHAR_WIDTH/2;
		else
			width = XEP80_CHAR_WIDTH;

		to = &XEP80_screen_1[XEP80_SCRN_WIDTH * XEP80_char_height * y +
		                     screen_col * XEP80_CHAR_WIDTH];
		for (font_row=0;font_row < XEP80_char_height; font_row++) {
			from = XEP80_FONTS_atari_fonts[char_set][font_index][ch][font_row];

			for (font_col=0; font_col < width; font_col++) {
				*to++ = *from;
				*to++ = *from++;
			}
			to += XEP80_SCRN_WIDTH - 2*XEP80_CHAR_WIDTH;
		}

		to = &XEP80_screen_2[XEP80_SCRN_WIDTH * XEP80_char_height * y +
		                     screen_col * XEP80_CHAR_WIDTH];
		for (font_row=0;font_row < XEP80_char_height; font_row++) {
			if (blink_rev)
				from = XEP80_FONTS_atari_fonts[char_set][font_index ^ XEP80_FONTS_REV_FONT_BIT][ch][font_row];
			else
				from = XEP80_FONTS_atari_fonts[char_set][font_index][ch][font_row];

			for (font_col=0; font_col < width; font_col++) {
				if (font_blink && !cur && !blink_rev) {
					if ((font_index & XEP80_FONTS_UNDER_FONT_BIT) && font_row == XEP80_FONTS_UNDER_ROW) {
						*to++ = *from;
						*to++ = *from++;
					}
					else {
						*to++ = blink;
						*to++ = blink;
						from++;
					}
				}
				else {
					*to++ = *from;
					*to++ = *from++;
				}
			}
			to += XEP80_SCRN_WIDTH - 2*XEP80_CHAR_WIDTH;
		}
	}
	else if ((font_double || last_double_cur) && cur && !cursor_overwrite) {
		int first_half, start_col, end_col;

		/* Determine if this is a double first or second half */
		if (screen_col == 0)
			first_half = TRUE;
		else {
			if (IS_DOUBLE(x-1,y)) {
				int firstd;

				firstd = x-1;
				while (firstd > xscroll) {
					if (!IS_DOUBLE(firstd,y)) {
						firstd++;
						break;
					}
					firstd--;
				}
				first_half = (((x-firstd) % 2) == 0);
			}
			else
				first_half = TRUE;
		}

		if (first_half) {
			start_col = 0;
			end_col = 3;
		}
		else {
			start_col = 3;
			end_col = 6;
			ch = char_data(y, x-1);
		}

		to = &XEP80_screen_1[XEP80_SCRN_WIDTH * XEP80_char_height * y +
		                     screen_col * XEP80_CHAR_WIDTH];
		for (font_row=0;font_row < XEP80_char_height; font_row++) {
			from = XEP80_FONTS_atari_fonts[char_set][font_index ^ XEP80_FONTS_REV_FONT_BIT][ch][font_row] + start_col;
			if (first_half)
				*to++ = *from++;
			for (font_col=start_col; font_col < end_col; font_col++) {
				*to++ = *from;
				*to++ = *from++;
			}
			if (!first_half)
				*to++ = *from;
			to += XEP80_SCRN_WIDTH - XEP80_CHAR_WIDTH;
		}
		to = &XEP80_screen_2[XEP80_SCRN_WIDTH * XEP80_char_height * y +
		                     screen_col * XEP80_CHAR_WIDTH];
		for (font_row=0;font_row < XEP80_char_height; font_row++) {
			if (!cursor_blink)
				from = XEP80_FONTS_atari_fonts[char_set][font_index ^ XEP80_FONTS_REV_FONT_BIT][ch][font_row] + start_col;
			else
				from = XEP80_FONTS_atari_fonts[char_set][font_index][ch][font_row] + start_col;
			if (first_half)
				*to++ = *from++;
			for (font_col=start_col; font_col < end_col; font_col++) {
				*to++ = *from;
				*to++ = *from++;
			}
			if (!first_half)
				*to++ = *from;
			to += XEP80_SCRN_WIDTH - XEP80_CHAR_WIDTH;
		}
	}
	else {
		to = &XEP80_screen_1[XEP80_SCRN_WIDTH * XEP80_char_height * y +
		                     screen_col * XEP80_CHAR_WIDTH];
		if (cur & cursor_overwrite) {
			for (font_row=0;font_row < XEP80_char_height; font_row++) {
				for (font_col=0; font_col < XEP80_CHAR_WIDTH; font_col++)
					*to++ = on;
				to += XEP80_SCRN_WIDTH - XEP80_CHAR_WIDTH;
			}
		}
		else {
			for (font_row=0;font_row < XEP80_char_height; font_row++) {
				if (cur)
					from = XEP80_FONTS_atari_fonts[char_set][font_index ^ XEP80_FONTS_REV_FONT_BIT][ch][font_row];
				else
					from = XEP80_FONTS_atari_fonts[char_set][font_index][ch][font_row];

				for (font_col=0; font_col < XEP80_CHAR_WIDTH; font_col++)
					*to++ = *from++;
				to += XEP80_SCRN_WIDTH - XEP80_CHAR_WIDTH;
			}
		}

		to = &XEP80_screen_2[XEP80_SCRN_WIDTH * XEP80_char_height * y +
		                     screen_col * XEP80_CHAR_WIDTH];
		if (cur & cursor_overwrite) {
			if (cursor_blink) {
				for (font_row=0;font_row < XEP80_char_height; font_row++) {
					for (font_col=0; font_col < XEP80_CHAR_WIDTH; font_col++)
						*to++ = off;
					to += XEP80_SCRN_WIDTH - XEP80_CHAR_WIDTH;
				}
			}
			else {
				for (font_row=0;font_row < XEP80_char_height; font_row++) {
					for (font_col=0; font_col < XEP80_CHAR_WIDTH; font_col++)
						*to++ = on;
					to += XEP80_SCRN_WIDTH - XEP80_CHAR_WIDTH;
				}
			}
		}
		else {
			for (font_row=0;font_row < XEP80_char_height; font_row++) {
				if (cur && !cursor_blink)
					from = XEP80_FONTS_atari_fonts[char_set][font_index ^ XEP80_FONTS_REV_FONT_BIT][ch][font_row];
				else {
					if (blink_rev)
						from = XEP80_FONTS_atari_fonts[char_set][font_index ^ XEP80_FONTS_REV_FONT_BIT][ch][font_row];
					else
						from = XEP80_FONTS_atari_fonts[char_set][font_index][ch][font_row];
				}
				for (font_col=0; font_col < XEP80_CHAR_WIDTH; font_col++) {
					if (font_blink && !cur) {
						if ((font_index & XEP80_FONTS_UNDER_FONT_BIT) && font_row == XEP80_FONTS_UNDER_ROW)
							*to++ = *from++;
						else {
							*to++ = blink;
							from++;
						}
					}
					else
						*to++ = *from++;
				}
				to += XEP80_SCRN_WIDTH - XEP80_CHAR_WIDTH;
			}
		}
	}
}

static void UpdateCursor(void)
{
	if (!graphics_mode && cursor_on) {
		/* Redraw character cursor was at */
		BlitChar(cursor_x, cursor_y, FALSE);
		/* Handle reblitting double wide's which cursor may have overwritten */
		if (cursor_x != 0)
			BlitChar(cursor_x-1, cursor_y, FALSE);
		/* Redraw cursor at new location */
		BlitChar(xpos, ypos, TRUE);
	}
	cursor_x = xpos;
	cursor_y = ypos;
	curs = line_pointers[ypos] + xpos - video_ram;
}

static void BlitCharScreen(void)
{
	int screen_row, screen_col;

	for (screen_row = 0; screen_row < XEP80_HEIGHT; screen_row++) {
		for (screen_col = xscroll; screen_col < xscroll + XEP80_LINE_LEN;
		     screen_col++)
			BlitChar(screen_col, screen_row, FALSE);
	}
	UpdateCursor();
}

static void BlitRows(int y_start, int y_end)
{
	int screen_row, screen_col;

	for (screen_row = y_start; screen_row <= y_end; screen_row++) {
		for (screen_col = xscroll; screen_col < xscroll + XEP80_LINE_LEN;
		     screen_col++)
			BlitChar(screen_col, screen_row, FALSE);
	}
}

static void BlitGraphChar(int x, int y)
{
	int graph_col;
	UBYTE *to1,*to2;
	UBYTE ch;
	UBYTE on, off;

	if (inverse_mode) {
		on = XEP80_FONTS_offcolor;
		off = XEP80_FONTS_oncolor;
	}
	else {
		on = XEP80_FONTS_oncolor;
		off = XEP80_FONTS_offcolor;
	}

	ch = graph_data(y, x);

	to1 = &XEP80_screen_1[XEP80_SCRN_WIDTH * (y + GRAPH_Y_OFFSET)
	                      + x * 8 + GRAPH_X_OFFSET];
	to2 = &XEP80_screen_2[XEP80_SCRN_WIDTH * (y + GRAPH_Y_OFFSET)
	                      + x * 8 + GRAPH_X_OFFSET];

	for (graph_col=0; graph_col < 8; graph_col++) {
		if (ch & (1<<graph_col)) {
			*to1++ = on;
			*to2++ = on;
		}
		else {
			*to1++ = off;
			*to2++ = off;
		}
	}
}

static void BlitGraphScreen(void)
{
	int x, y;

	memset(XEP80_screen_1, XEP80_FONTS_offcolor, XEP80_SCRN_WIDTH*XEP80_MAX_SCRN_HEIGHT);
	memset(XEP80_screen_2, XEP80_FONTS_offcolor, XEP80_SCRN_WIDTH*XEP80_MAX_SCRN_HEIGHT);
	for (x=0; x<XEP80_GRAPH_WIDTH/8; x++)
		for (y=0; y<XEP80_GRAPH_HEIGHT; y++)
			BlitGraphChar(x,y);
}

static void BlitScreen(void)
{
	if (graphics_mode)
		BlitGraphScreen();
	else
		BlitCharScreen();
}

/* --------------------------------------------------
   Functions that simulate procedures in XEP80's ROM.
   -------------------------------------------------- */

/* Set whole 8 KB of video RAM to C.
   ROM location: 0643 */
static void FillMem(UBYTE c)
{
	memset(video_ram,c,sizeof(video_ram));
}
/* Initialise the XEP80.
   ROM location: 001f, 0056 */
static void ColdStart(void)
{
	static UBYTE const initial_tab_stops[0x100] =
		{0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,
		 0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,
		 0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,
		 0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,
		 0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,
		 0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,
		 0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,
		 0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,
		 0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,
		 0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,
		 0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,
		 0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,
		 0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1};

	/* Set VCR. */
	cursor_blink = FALSE;
	cursor_overwrite = FALSE;
	blink_reverse = FALSE;
	inverse_mode = FALSE;
	char_set = CHAR_SET_A;

	attrib_a = 0xff;
	font_a_index = 0;
	font_a_double = FALSE;
	font_a_blank = FALSE;
	font_a_blink = FALSE;
	attrib_b = 0xff;
	font_b_index = 0;
	font_b_double = FALSE;
	font_b_blank = FALSE;
	font_b_blink = FALSE;
	cursor_x-= cursor_y = 0;
	curs = 0;

	/* Set TCP. */
	graphics_mode = FALSE;
	pal_mode = FALSE;
	cursor_on = TRUE;
	UpdateTVSystem();

	/* Set RAM registers. */
	burst_mode = FALSE;
	screen_output = TRUE;
	escape_mode = FALSE;
	list_mode = FALSE;
	old_ypos = 0xff;
	old_xpos = 0xff;
	input_count = 0;
	xscroll = 0;
	last_char = 0;
	xpos = 0;
	lmargin = 0;
	rmargin = 0x4f;

	{
		int i;
		for (i = 0; i < XEP80_HEIGHT; ++i)
			line_pointers[i] = video_ram + 0x100*i;
	}
	ypos = 0;

	FillMem(XEP80_ATARI_EOL);
	memcpy(&video_ram[0x1900], initial_tab_stops, 0x100);

	BlitCharScreen();
}

/* Put a word to send back to host in the queue. */
static void InputWord(int word)
{
	input_queue[input_count] = word;
/*	Log_print("XEP80 -> %03x [i]", word, input_count);*/
	input_count++;
}

/* Checks if cursor position changed and sends it back to host.
   ROM location: 0200 */
static void	SendCursorStatus(void)
{
	if (xpos != old_xpos || ypos == old_ypos) {
		/* Send X cursor position if it changed, or if both X and Y dind't change. */
		int pos = xpos > 0x4f ? 0x150 : (xpos | 0x100);
		if (ypos != old_ypos) /* Indicate Y position will follow */
			pos |= 0x80;
		InputWord(pos);
		old_xpos = xpos;
	}
	if (ypos != old_ypos) {
		/* Send Y position if it changed. */
		InputWord(ypos | 0x1e0);
		old_ypos = ypos;
	}
}

/* Scrolls the screen down starting at line Y.
   ROM location: 053d */
static void ScrollDown(int y)
{
	UBYTE *ptr = line_pointers[XEP80_HEIGHT-2];

	memmove(line_pointers+y+1, line_pointers+y, sizeof(UBYTE*) * (XEP80_HEIGHT-2-y));
	line_pointers[y] = ptr;
}

/* Fills line Y with EOL. */
static void ClearLine(int y)
{
	memset(line_pointers[y]+xscroll, XEP80_ATARI_EOL, XEP80_LINE_LEN);
}

/* Clears (fills with EOL) the current line and moves cursor to left margin.
   ROM location: 06b6 */
static void ClearLineCursorToLeftMargin(void)
{
	ClearLine(ypos);
	xpos = lmargin;
}

/* Scrolls the whole screen up, and clears the last line. */
static void ScrollScreenUp(void)
{
	UBYTE *ptr = line_pointers[0];

	memmove(line_pointers, line_pointers+1, sizeof(UBYTE*) * (XEP80_HEIGHT-2));
	line_pointers[XEP80_HEIGHT-2] = ptr;
	ClearLine(XEP80_HEIGHT-2);
}

/* Sreolls the whole screen up, clears the last line, and moves cursor to left
   margin of the last line.
   ROM location: 0652 */
static void ScrollScreenUpCursorToLeftMargin(void)
{
	ScrollScreenUp();
	ypos = XEP80_HEIGHT-2;
	xpos = lmargin;

	BlitScreen();
}

/* Process the "Insert Line" ATASCII character.
   ROM location: 0537 */
static void InsertLine(void)
{
	ScrollDown(ypos);
	ClearLineCursorToLeftMargin();
	BlitRows(ypos, XEP80_HEIGHT-2);
	UpdateCursor();
}

/* Advance the cursor right. If necessary, scroll the screen or extend logical line.
   ROM location: 05cb */
static void AdvanceCursor(UBYTE prev_char_under_cursor)
{
	if (xpos != rmargin) {
		++xpos;
		UpdateCursor();
		return;
	}
	if (ypos == 23) { /* last non-status line */
		ScrollScreenUpCursorToLeftMargin();
		UpdateCursor();
		return;
	}
	if (ypos == 24) {
		xpos = 0;
		UpdateCursor();
		return;
	}
	++ypos;
	if (prev_char_under_cursor == XEP80_ATARI_EOL) {
		InsertLine();
		return;
	}
	xpos = 0;
	UpdateCursor();
}

/* Add ATASCII character BYTE at cursor position, and advance the cursor.
   ROM location: 05c3 */
static void AddCharAtCursor(UBYTE byte)
{
	UBYTE prev_char = video_ram[curs];
	video_ram[curs] = byte;
	BlitChar(xpos, ypos, FALSE);
	escape_mode = FALSE;
	AdvanceCursor(prev_char);
}

/* Process the "Cursor Up" ATASCII character.
   ROM location: 0523 */
static void CursorUp(void)
{
	if (--ypos < 0)
		ypos = XEP80_HEIGHT-2;
	UpdateCursor();
}

/* Process the "Cursor Down" ATASCII character.
   ROM location: 052d */
static void CursorDown(void)
{
	if (++ypos > XEP80_HEIGHT-2)
		ypos = 0;
	UpdateCursor();
}

/* Process the "Cursor Left" ATASCII character.
   ROM location: 0552 */
static void CursorLeft(void)
{
	if (xpos == lmargin)
		xpos = rmargin;
	else
		--xpos;
	UpdateCursor();
}

/* Process the "Cursor Right" ATASCII character.
   ROM location: 055c */
static void CursorRight(void)
{
	if (xpos == rmargin)
		xpos = lmargin;
	else {
		if (video_ram[curs] == XEP80_ATARI_EOL)
			video_ram[curs] = 0x20;
		++xpos;
	}
	UpdateCursor();
}

/* Process the "Clear Screen" ATASCII character.
   ROM location: 056f */
static void	ClearScreen(void)
{
	int y;

	for (y=0; y<XEP80_HEIGHT-1; y++)
		memset(line_pointers[y] + xscroll, XEP80_ATARI_EOL, XEP80_LINE_LEN);
	xpos = lmargin;
	ypos = 0;
	BlitCharScreen();
}

/* Process the "Backspace" ATASCII character.
   ROM location: 057a */
static void Backspace(void)
{
	if (xpos != lmargin) {
		--xpos;
	}
	else if (ypos != 0 && char_data(ypos-1, rmargin) != XEP80_ATARI_EOL){
		xpos= rmargin;
		--ypos;
	}
	char_data(ypos, xpos) = 0x20;
	BlitChar(xpos, ypos, FALSE);
	UpdateCursor();
}

/* Shift contents of line Y left, from position X to right margin.
   Put the character PREV_DROPPED at the line's end. Return character that
   was dropped out of the shifted line.
   ROM location: 07a7 */
static UBYTE ShiftLeft(int y, int x, UBYTE prev_dropped)
{
	UBYTE new_dropped = *(line_pointers[y]+x);
	memmove(line_pointers[y]+x, line_pointers[y]+x+1, rmargin-x);
	*(line_pointers[y]+rmargin) = prev_dropped;
	return new_dropped;
}

/* Process the "Delete Character" ATASCII character.
   ROM location: 0760 */
static void DeleteChar(void)
{
	UBYTE prev_dropped = XEP80_ATARI_EOL;
	int y_end;
	int y;

	/* Go down; find a line with EOL at end. */
	for (y_end = ypos; char_data(y_end, rmargin) != XEP80_ATARI_EOL && y_end < XEP80_HEIGHT-2; ++y_end);

	for (y = y_end; y > ypos; --y) {
		if (lmargin == rmargin) {
			video_ram[curs] = prev_dropped;
			BlitRows(ypos, y_end);
			UpdateCursor();
			return;
		}
		else {
			prev_dropped = ShiftLeft(y, lmargin, prev_dropped);
		}
	}

	if (xpos == rmargin) {
		video_ram[curs] = prev_dropped;
	}
	else {
		ShiftLeft(y, xpos, prev_dropped);
	}
	BlitRows(ypos, y_end);
	UpdateCursor();
}

/* Process the "Insert Character" ATASCII character.
   ROM location: 047a */
static void InsertChar(void)
{
	UBYTE prev_dropped = 0x20;
	int y = ypos;
	int x = xpos;
	UBYTE to_drop;
	UBYTE new_last_char;

	for (;;) {
		if (y == rmargin) {
			to_drop = video_ram[curs];
			video_ram[curs] = prev_dropped;
			new_last_char = prev_dropped;
		}
		else {
			to_drop = *(line_pointers[y]+rmargin);
			memmove(line_pointers[y]+x+1, line_pointers[y]+x, rmargin-x);
			*(line_pointers[y]+x) = prev_dropped;
			new_last_char = *(line_pointers[y]+rmargin);
		}
		prev_dropped = to_drop;
		if (to_drop == XEP80_ATARI_EOL) {
			if (new_last_char == XEP80_ATARI_EOL)
				break;

			/* Need to extend logical line. */
			if (y == XEP80_HEIGHT - 2) {
				/* Finished in the last line */
				if (ypos == 0)
					break;
				ScrollScreenUp();
				--ypos;
				BlitCharScreen();
				UpdateCursor();
				return;
			}
			else {
				ScrollDown(y+1);
				ClearLine(y+1);
				BlitRows(ypos, XEP80_HEIGHT-2);
				UpdateCursor();
				return;
			}
		}
		if (y == XEP80_HEIGHT-2)
			break;
		++y;
		x = lmargin;
	}
	BlitRows(ypos, y);
	UpdateCursor();
}

/* Process the "Tab" ATASCII character.
   ROM location: 05a7 */
static void GoToNextTab(void)
{
	for (;;) {
		UBYTE prev = video_ram[curs];
		if (prev == XEP80_ATARI_EOL)
			video_ram[curs] = 0x20;
		AdvanceCursor(prev);
		if (xpos == rmargin)
			return;
		if (tab_stops(xpos))
			return;
	}
}

/* Process the "EOL" ATASCII character.
   ROM location: 0253 */
static void AddEOL(void)
{
	xpos = rmargin;
	escape_mode = FALSE;
	AdvanceCursor(0);
}

/* Process the "Delete Line" ATASCII character.
   ROM location: 07b4 */
static void DeleteLogicalLine(void)
{
	if (ypos == XEP80_HEIGHT - 2) {
		ClearLineCursorToLeftMargin();
	}
	else {
		for (;;) {
			UBYTE prev = char_data(ypos, rmargin);
			UBYTE *ptr = line_pointers[ypos];
			memmove(line_pointers+ypos, line_pointers+ypos+1, sizeof(UBYTE*) * (XEP80_HEIGHT-2-ypos));
			line_pointers[XEP80_HEIGHT - 2] = ptr;
			/* Clear last line */
			memset(ptr+xscroll, XEP80_ATARI_EOL, XEP80_LINE_LEN);
			if (prev == XEP80_ATARI_EOL)
				break;
		}
		xpos = lmargin;
	}
	BlitRows(ypos, XEP80_HEIGHT-2);
	UpdateCursor();
}

/* Reverses bit order of a byte. Bytes have to be reversed in graphics mode,
   because NS405 displays graphics from LSB (left) to MSB (right).
   ROM location: 02d7 */
static UBYTE ReverseByte(unsigned long int b)
{
	return ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;
}

/* Puts a byte of grsphics in RAM (reversing its bit order) and displays it.
   ROM location: 0296 */
static void AddGraphCharAtCursor(UBYTE byte)
{
	int y = (curs & 0x1fff) / (XEP80_GRAPH_WIDTH/8);
	video_ram[curs & 0x1fff] = ReverseByte(byte);

	if (y < XEP80_GRAPH_HEIGHT) {
		BlitGraphChar((curs & 0x1fff) % (XEP80_GRAPH_WIDTH/8), y);
	}
	curs = (curs+1) & 0xffff;
}

/* Process receiving of a character (ie. not a command).
   ROM location: around 024b */
static void ReceiveChar(UBYTE byte)
{
	if (!screen_output) {
		/* Printer characters are thrown away, handled elsewhere.  The
		 * XEP80 driver needs to be set up to send printer characters
		 * to the existing P: driver. */
	}
	else if (graphics_mode)
		AddGraphCharAtCursor(byte);
	else if (byte == XEP80_ATARI_EOL)
		AddEOL();
	else if (escape_mode || list_mode)
		AddCharAtCursor(byte);
	else if (byte == 0x1b) /* Escape - Print next char even if a control char */
		escape_mode = TRUE;
	else if (ypos == 24) {
		if (byte == 0x9c) { /* Delete Line */
			/* ROM location: 07df */
			ClearLineCursorToLeftMargin();
			BlitRows(ypos, XEP80_HEIGHT-2);
			UpdateCursor();
		}
		else
			AddCharAtCursor(byte);
	}
	else {
		switch(byte) {
		case 0x1c: /* Cursor Up */
			CursorUp();
			break;
		case 0x1d: /* Cursor Down */
			CursorDown();
			break;
		case 0x1e: /* Cursor Left */
			CursorLeft();
			break;
		case 0x1f: /* Cursor Right */
			CursorRight();
			break;
		case 0x7d: /* Clear Screen */
			ClearScreen();
			break;
		case 0x7e: /* Backspace */
			Backspace();
			break;
		case 0x7f: /* Tab */
			GoToNextTab();
			break;
		case 0x9c: /* Delete Line */
			DeleteLogicalLine();
			break;
		case 0x9d: /* Insert Line */
			InsertLine();
			break;
		case 0x9e: /* Clear tab */
			tab_stops(xpos) = FALSE;
			break;
		case 0x9f: /* Set Tab */
			tab_stops(xpos) = TRUE;
			break;
		case 0xfd: /* Sound Bell */
			/* Do nothing here */
			break;
		case 0xfe: /* Delete Char */
			DeleteChar();
			break;
		case 0xff: /* Insert Char */
			InsertChar();
			break;
		default:
			AddCharAtCursor(byte);
			break;
		}
	}
}

/* Process commands:
   100****** - Horizontal Cursor Position ($00-3F)
   10100**** - Horizontal Cursor Position ($40-4F)
   ROM location: 0311 */
static void SetXCur(UBYTE new_xpos)
{
	xpos = old_xpos = new_xpos;
	UpdateCursor();
}

/* Process command 10101**** - Horiz Curs Pos High Nibble - for wide screen
   ROM location: 0318 */
static void SetXCurHigh(UBYTE new_xcur)
{
	xpos = old_xpos = ((UBYTE)xpos & 0x0f) | ((new_xcur & 0x0f) << 4);
	UpdateCursor();
}

/* Process command 10110**** - Left Margin Low Nibble - sets high nibble to 00
   ROM location: 0320 */
static void SetLeftMarginLow(UBYTE margin)
{
	lmargin = margin & 0x0f;
}

/* Process command 10111**** - Left Margin High Nibble
   ROM location: 0325 */
static void SetLeftMarginHigh(UBYTE margin)
{
	lmargin = ((UBYTE)lmargin & 0x0f) | ((margin & 0x0f) << 4);
}

/* Process commands:
   11000**** - Vertical Cursor Position ($00-0F)
   110010*** - Vertical Cursor Position ($10-17)
   110011000 - Set Cursor to Status Row ($18) See caution, Pg. 6
   ROM location: 032d, 03fb */
static void SetYCur(UBYTE new_ypos)
{
	ypos = old_ypos = new_ypos;
	UpdateCursor();
}

/* Process command 110011001 - Set Graphics to 60 Hz
   ROM location: 02b1 */
static void SetGraphics60Hz(void)
{
	graphics_mode = TRUE;

	blink_reverse = FALSE;
	cursor_blink = FALSE;
	cursor_overwrite = FALSE;
	inverse_mode = FALSE;

	cursor_x = 0;
	cursor_y = 0;
	curs = 0;

	pal_mode = FALSE;

	UpdateTVSystem();
	BlitGraphScreen();
}

/* Process command 110011010 - Modify Graphics to 50 Hz
   ROM location: 02cd */
static void SetGraphics50Hz(void)
{
	pal_mode = TRUE;
	UpdateTVSystem();
	BlitGraphScreen();
}

/* Process command 11010**** - Right Margin Low Nibble - sets high nibble to 04
   ROM location: 033a */
static void SetRightMarginLow(UBYTE margin)
{
	rmargin = margin ^ 0xe0;
}

/* Process command 11011**** - Right Margin High Nibble
   ROM location: 033f */
static void SetRightMarginHigh(UBYTE margin)
{
	rmargin = ((UBYTE)rmargin & 0x0f) | ((margin & 0x0f) << 4);
}

/* Process command 111000000 - Get Character from XEP80 at cursor (and advance)
   ROM location: 034c */
static void GetChar(void)
{
	InputWord(video_ram[curs]);
	AdvanceCursor(0x00);
	SendCursorStatus();
}

/* Process command 111000001 - Request Horizontal Cursor
   ROM location: 0613 */
static void GetXCur(void)
{
	InputWord(xpos);
}

/* Process command 111000010 - Master Reset
   ROM location: 0615 */
static void MasterReset(void)
{
	ColdStart();
	InputWord(0x01);
}

/* Process command 111000011 - Printer Port Status
   ROM location: 0631 */
static void GetPrinterStatus(void)
{
	/* Printer port is currently not emulated. */
	InputWord(0x01);
}

/* Process commands
   111010000 - Clear List Flag
   111010001 - Set List Flag
   ROM location: 0376 */
static void SetList(int list)
{
	list_mode = list;
}

/* Process commands
   111010010 - Set Screen Normal Mode - cursor returned each char
   111010011 - Set Screen Burst Mode - no cursor returned
   111011101 - Set Printer Output
   ROM location: 03e1, 03e7 */
static void SetOutputDevice(int screen, int burst)
{
	screen_output = screen;
	burst_mode = burst;
}

/* Process commands
   111010100 - Select Character Set A - Atari graphics (ATASCII)
   111010101 - Select Character Set B - Atari international
   111010110 - Select XEP80 Internal Character Set
   ROM location: 037c, 039b */
static void SetCharSet(int set)
{
	char_set = set;
}

/* Process command 111010111 - Modify Text to 50 Hz Operation
   ROM location: 03a6 */
static void SetText50Hz(void)
{
	pal_mode = TRUE;
	UpdateTVSystem();
	BlitCharScreen();
}

/* Process commands
   111011000 - Cursor Off
   111011001 - Cursor On Continuous
   111011010 - Cursor On Blink
   ROM location: 03af, 03b5 */
static void SetCursorMode(int on, int blink)
{
	cursor_on = on;
	cursor_blink = blink;
	if (!graphics_mode) {
		if (!cursor_on)
			BlitChar(xpos, ypos, FALSE);
		else
			UpdateCursor();
	}
}

/* Process command 111011011 - Move Cursor to Start of Logical Line
   ROM location: 03c5 */
static void SetXCurStart(void)
{
	for (;;) {
		if (ypos == 0)
			break;
		--ypos;
		if (char_data(ypos, rmargin) == XEP80_ATARI_EOL) {
			++ypos;
			break;
		}
	}
	UpdateCursor();
}

/* Process command 111011100 - Set Scroll Window to Cursor X Value
   ROM location: 03dc */
static void SetScrollWindow(void)
{
	xscroll = xpos;
	BlitScreen();
}

/* Process commands
   111011110 - Select White Characters on Black Background
   111011111 - Select Black Characters on White Background
   ROM location: 03ed */
static void SetInverse(int inverse)
{
	inverse_mode = inverse;
	BlitScreen();
}

/* Process command 111101101 - Reserved
   ROM location: 0432 */
static void SetVideoCtrl(UBYTE video_ctrl)
{
	if (video_ctrl & 0x08)
		inverse_mode = TRUE;
	else
		inverse_mode = FALSE;
	if (video_ctrl & 0x02)
		cursor_blink = FALSE;
	else
		cursor_blink = TRUE;
	if (video_ctrl & 0x04)
		cursor_overwrite = FALSE;
	else
		cursor_overwrite = TRUE;
	if (video_ctrl & 0x01)
		blink_reverse = TRUE;
	else
		blink_reverse = FALSE;
	BlitScreen();
}

static void UpdateAttributeBits(UBYTE attrib, int *font_index, int *font_double, int *font_blank, int *font_blink)
{
	*font_index = 0;
	if (!(attrib & 0x01))
		*font_index |= XEP80_FONTS_REV_FONT_BIT;
	if (!(attrib & 0x20))
		*font_index |= XEP80_FONTS_UNDER_FONT_BIT;
	if (!(attrib & 0x80))
		*font_index |= XEP80_FONTS_BLK_FONT_BIT;
	if (!(attrib & 0x10))
		*font_double = TRUE;
	else
		*font_double = FALSE;
	if (!(attrib & 0x40))
		*font_blank = TRUE;
	else
		*font_blank = FALSE;
	if (!(attrib & 0x04))
		*font_blink = TRUE;
	else
		*font_blink = FALSE;
}

/* Process command 111110100 - Reserved
   ROM location: 044d */
static void SetAttributeA(UBYTE attrib)
{
	attrib_a = attrib;
	UpdateAttributeBits(attrib, &font_a_index, &font_a_double, &font_a_blank, &font_a_blink);
	BlitScreen();
}

/* Process command 111110101 - Reserved
   ROM location: 0450 */
static void SetAttributeB(UBYTE attrib)
{
	attrib_b = attrib;
	UpdateAttributeBits(attrib, &font_b_index, &font_b_double, &font_b_blank, &font_b_blink);
	BlitScreen();
}

/* Process 1111xxxxx "Reserved" commands. In reality they set values of various
   internal NS405 registers.
   ROM location: 03ff */
static void SetReserved(UBYTE byte)
{
	byte &= 0x1f;
	if (byte == 0)
		return;
	switch (byte) {
	case CMD_VIDEO_CTRL & 0x1f:
		SetVideoCtrl(last_char);
		break;
	case CMD_ATTRIB_A & 0x1f:
		SetAttributeA(last_char);
		break;
	case CMD_ATTRIB_B & 0x1f:
		SetAttributeB(last_char);
		break;
	default:
		/* Other 1111xxxxx reserved commands are not currently emulated -
		   implementation would require exact emulation of the whole NS405.
		   111100001, 111100010: Set CURS
		   111100011: Put character under CURS
		   111100100, 111100101: Put byte into internal RAM
		   111100110, 111100111: Set HOME
		   111101000: Set MASK
		   111101001: Set PSW
		   111101010: Set PORT
		   111101011: Set TIMER
		   111101100: Set SCR
		   111101110, 111101111: Set BEGD
		   111110000, 111110001: Set ENDD
		   111110010, 111110011: Set SROW
		   111110110: Set TCP
		   111110111: Put byte under TCP
		   111111000: Set VINT
		   111111001, 111111010: Set PSR/BAUD
		   111111011: Set UCR
		   111111100: Set UMX
		   111111101: Set XMTR
		   111111110: Ignore
		   111111111: Strobe the parallel port */
		Log_print("XEP80 received not emulated command %03h", 0x100 & byte);
	}
	last_char = byte - 0x1f;
}

/* Process a word received from host. */
static void OutputWord(int word)
{
	UBYTE byte = word & 0xFF;

	/* Is it a command or data word? */
	if (word & 0x100) {
		switch(byte & CMD_XX_MASK) {
		case CMD_00:
			SetXCur(byte);
			break;
		case CMD_01:
			switch(byte & CMD_01_MASK) {
			case CMD_X_CUR_UPPER:
				SetXCur(byte);
				break;
			case CMD_X_CUR_HIGH:
				SetXCurHigh(byte);
				break;
			case CMD_LEFT_MAR_L:
				SetLeftMarginLow(byte);
				break;
			case CMD_LEFT_MAR_H:
				SetLeftMarginHigh(byte);
				break;
			}
			break;
		case CMD_10:
			switch(byte & CMD_10_MASK) {
			case CMD_Y_CUR_LOW:
				SetYCur(byte & 0x0F);
				break;
			case CMD_1001:
				if ((byte & CMD_1001_MASK) == CMD_Y_CUR_HIGH)
					SetYCur(byte & 0x17);
				else {
					switch(byte) {
					case CMD_Y_CUR_STATUS:
						SetYCur(24);
						break;
					case CMD_GRAPH_50HZ:
						SetGraphics50Hz();
						break;
					case CMD_GRAPH_60HZ:
						SetGraphics60Hz();
						break;
					}
				}
				break;
			case CMD_RIGHT_MAR_L:
				SetRightMarginLow(byte);
				break;
			case CMD_RIGHT_MAR_H:
				SetRightMarginHigh(byte);
				break;
			}
			break;
		case CMD_11:
			if ((byte & 0xe0) == 0xe0) {
				SetReserved(byte);
			}
			else {
				switch (byte) {
				case CMD_GET_CHAR:
					GetChar();
					break;
				case CMD_REQ_X_CUR:
					GetXCur();
					break;
				case CMD_MRST:
					MasterReset();
					break;
				case CMD_PRT_STAT:
					GetPrinterStatus();
					break;
				case CMD_FILL_PREV:
					/* Reverts bits in the last char. For use in graphics mode.
					   ROM location: 0636 */
					FillMem(ReverseByte(last_char));
					BlitScreen();
					InputWord(0x01);
					break;
				case CMD_FILL_SPACE:
					/* ROM location: 063d */
					FillMem(0x20);
					BlitScreen();
					InputWord(0x01);
					break;
				case CMD_FILL_EOL:
					/* ROM location: 0641 */
					FillMem(XEP80_ATARI_EOL);
					BlitScreen();
					InputWord(0x01);
					break;
				case CMD_CLR_LIST:
					SetList(FALSE);
					break;
				case CMD_SET_LIST:
					SetList(TRUE);
					break;
				case CMD_SCR_NORMAL:
					SetOutputDevice(TRUE, FALSE);
					break;
				case CMD_SCR_BURST:
					SetOutputDevice(TRUE, TRUE);
					break;
				case CMD_SET_PRINT:
					SetOutputDevice(FALSE, TRUE);
					break;
				case CMD_CHAR_SET_A:
					SetCharSet(CHAR_SET_A);
					BlitScreen();
					break;
				case CMD_CHAR_SET_B:
					SetCharSet(CHAR_SET_B);
					BlitScreen();
					break;
				case CMD_CHAR_SET_INT:
					SetCharSet(CHAR_SET_INTERNAL);
					BlitScreen();
					break;
				case CMD_TEXT_50HZ:
					SetText50Hz();
					break;
				case CMD_CUR_OFF:
					SetCursorMode(FALSE, FALSE);
					break;
				case CMD_CUR_ON:
					SetCursorMode(TRUE, FALSE);
					break;
				case CMD_CUR_BLINK:
					SetCursorMode(TRUE, TRUE);
					break;
				case CMD_CUR_ST_LINE:
					SetXCurStart();
					break;
				case CMD_SET_SCRL_WIN:
					SetScrollWindow();
					break;
				case CMD_WHT_ON_BLK:
					SetInverse(FALSE);
					break;
				case CMD_BLK_ON_WHT:
					SetInverse(TRUE);
					break;
				default:
					/* All command left are 111000111 and 111001xxx, marked as Reserved.
					   Actually they return values of various internal NS405 registers.
					   Not currently emulated - implementation would require exact
					   emulation of the whole NS405.
					   111000111: Get byte at CURS
					   111001000: Get INTR
					   111001001: Get PSW
					   111001010: Get PORT
					   111001011: Get TIMER
					   111001100: Get HPEN
					   111001101: Get VPEN
					   111001110: Get STAT
					   111001111: Get RCVR */
					Log_print("XEP80 received not emulated command %03h", word);
				}
			}
			break;
		}
	}
	/* If it's data, then handle it as a character */
	else {
		last_char = byte;
		ReceiveChar(byte);
		if (!burst_mode)
			SendCursorStatus();
	}
}

/* ----------------
   Other functions.
   ---------------- */

void XEP80_ChangeColors(void)
{
	BlitScreen();
}

int XEP80_ReadConfig(char *string, char *ptr)
{
	if (strcmp(string, "XEP80_CHARSET") == 0)
		Util_strlcpy(charset_filename, ptr, sizeof(charset_filename));
	else return FALSE; /* no match */
	return TRUE; /* matched something */
}

void XEP80_WriteConfig(FILE *fp)
{
	fprintf(fp, "XEP80_CHARSET=%s\n", charset_filename);
}

int XEP80_SetEnabled(int value)
{
	if (value && !XEP80_FONTS_inited && !XEP80_FONTS_InitFonts(charset_filename))
		return FALSE;
	XEP80_enabled = value;
	return TRUE;
}

int XEP80_Initialise(int *argc, char *argv[])
{
	int i, j;
	int help_only = FALSE;
	for (i = j = 1; i < *argc; i++) {
		int i_a = (i + 1 < *argc);		/* is argument available? */
		int a_m = FALSE;			/* error, argument missing! */

		if (strcmp(argv[i], "-xep80") == 0) {
			XEP80_enabled = TRUE;
		}
		else if (strcmp(argv[i], "-xep80port") == 0) {
			if (i_a) {
				XEP80_port = Util_sscandec(argv[++i]);
				if (XEP80_port != 0 && XEP80_port != 1) {
					Log_print("Invalid XEP80 port - should be 0 or 1");
					return FALSE;
				}
			}
			else a_m = TRUE;
		}
		else {
			if (strcmp(argv[i], "-help") == 0) {
				help_only = TRUE;
				Log_print("\t-xep80           Emulate the XEP80");
				Log_print("\t-xep80port <n>   Use XEP80 on joystick port <n>");
			}
			argv[j++] = argv[i];
		}

		if (a_m) {
			Log_print("Missing argument for '%s'", argv[i]);
			return FALSE;
		}
	}
	*argc = j;

	if (help_only)
		return TRUE;

	if (XEP80_enabled && !XEP80_SetEnabled(XEP80_enabled)) {
		XEP80_enabled = FALSE;
		Log_print("Couldn't load XEP80 charset image: %s", charset_filename);
		return FALSE;
	}

	start_trans_cpu_clock = ANTIC_CPU_CLOCK;
	ColdStart();

	return TRUE;
}

UBYTE XEP80_GetBit(void)
{
	UBYTE ret = 0xFF;
	int word_bit_num;
	int input_word;
	int input_word_num;
	/* Number of CPU ticks since start of word receiving.
	   TODO: Avoid overflows in this value (minimal issue, since transmission
	   rate is 15.7 kHz - way too low to allow for overflows). */
	int num_ticks = (int)(ANTIC_CPU_CLOCK - start_trans_cpu_clock);

	int bit_no = num_ticks / ANTIC_LINE_C;

	/* If there is not input to be sent, just return */
	if (input_count == 0 || num_ticks < 0)
		return ret;

	/* Figure out which word of the queue it is in based on bit */
	input_word_num = (bit_no / 11);

	/* If it is greater than we have, then clear queue and return */
	if (input_word_num >= input_count) {
		input_count = 0;
		return ret;
	}

	/* Get the word from the queue, and calculate which bit of the
	 * word we are sending */
	input_word = input_queue[input_word_num];
	word_bit_num = bit_no % 11;

	/* Send the return value based on the bit */
	switch(word_bit_num) {
	case 0: /* Start Bit - 0 */
		ret = 0xFF & ~input_mask[XEP80_port];
		break;
	case 1: /* 9 Data Bits */
	case 2:
	case 3:
	case 4:
	case 5:
	case 6:
	case 7:
	case 8:
	case 9:
		if (input_word & (1 << (word_bit_num-1)))
			ret = 0xFF;
		else
			ret = 0xFF & ~input_mask[XEP80_port];
		break;
	case 10: /* Stop Bit - 1 */
		ret = 0xFF;
		break;
	}

	return ret;
}

void XEP80_PutBit(UBYTE byte)
{
	/* Number of CPU ticks since start of word receiving.
	   TODO: Avoid overflows in this value (minimal issue, since transmission
	   rate is 15.7 kHz - way too low to allow for overflows). */
	int num_ticks = (int)(ANTIC_CPU_CLOCK - start_trans_cpu_clock);

	int bit_no = (num_ticks + ANTIC_LINE_C / 2) / ANTIC_LINE_C;

	byte &= output_mask[XEP80_port];

	if (receiving) {
		switch (bit_no) {
		case 0:
			return;
		case 1:
		case 2:
		case 3:
		case 4:
		case 5:
		case 6:
		case 7:
		case 8:
		case 9:
			if (byte)
				output_word |= 1 << (bit_no - 1);
			break;
		case 10:
			/* Stop bit */
			/* Clear any unread input from last command */
			input_count = 0;
			receiving = FALSE;
			if (byte) {
				/* Set the start position of the next possible output byte, to
				   0.5 scanline after end of the current stop bit. This is not
				   based on actual hardware - the delay was chosen to work with
				   the Atari and the SpartaDOS X XEP80 drivers. Starting
				   transmission immediately after end of the stop bit
				   (prev_cpu_clock += 11 * ANTIC_LINE_C) would break the SDX
				   driver. */
				start_trans_cpu_clock += 11 * ANTIC_LINE_C + ANTIC_LINE_C / 2;
				/* Handle the new word */
/*				Log_print("XEP80 <- %03x", output_word);*/
				OutputWord(output_word);
			}
			return;
		default:
			/* Transmission timed out without receiving stop bit. */
			receiving = FALSE;
		}
	}

	if (!receiving) {
		/* Either previous byte ended or no byte was received yet. */
		if (!byte) {
			/* Start bit encountered. */
			receiving = TRUE;
			start_trans_cpu_clock = ANTIC_CPU_CLOCK;
			output_word = 0;
		}
	}
}

void XEP80_StateSave(void)
{
	StateSav_SaveINT(&XEP80_enabled, 1);
	if (XEP80_enabled) {
		int num_ticks = (int)(ANTIC_CPU_CLOCK - start_trans_cpu_clock);
#if SUPPORTS_CHANGE_VIDEOMODE
		int show_xep80 = VIDEOMODE_80_column;
#else
		int show_xep80 = 1;
#endif /* SUPPORTS_CHANGE_VIDEOMODE */
		StateSav_SaveINT(&XEP80_port, 1);
		StateSav_SaveINT(&show_xep80, 1);
		StateSav_SaveINT(&num_ticks, 1);
		StateSav_SaveINT(&output_word, 1);
		StateSav_SaveINT(&input_count, 1);
		StateSav_SaveINT(&receiving, 1);
		StateSav_SaveUWORD(input_queue, IN_QUEUE_SIZE);
		StateSav_SaveINT(&receiving, 1);
		StateSav_SaveUBYTE(&last_char, 1);

		StateSav_SaveINT(&xpos, 1);
		StateSav_SaveINT(&xscroll, 1);
		StateSav_SaveINT(&ypos, 1);
		StateSav_SaveINT(&cursor_x, 1);
		StateSav_SaveINT(&cursor_y, 1);
		StateSav_SaveINT(&curs, 1);
		StateSav_SaveINT(&old_xpos, 1);
		StateSav_SaveINT(&old_ypos, 1);
		StateSav_SaveINT(&lmargin, 1);
		StateSav_SaveINT(&rmargin, 1);
		StateSav_SaveUBYTE(&attrib_a, 1);
		StateSav_SaveUBYTE(&attrib_b, 1);
		StateSav_SaveINT(&list_mode, 1);
		StateSav_SaveINT(&escape_mode, 1);
		StateSav_SaveINT(&char_set, 1);
		StateSav_SaveINT(&cursor_on, 1);
		StateSav_SaveINT(&cursor_blink, 1);
		StateSav_SaveINT(&cursor_overwrite, 1);
		StateSav_SaveINT(&blink_reverse, 1);
		StateSav_SaveINT(&inverse_mode, 1);
		StateSav_SaveINT(&screen_output, 1);
		StateSav_SaveINT(&burst_mode, 1);
		StateSav_SaveINT(&graphics_mode, 1);
		StateSav_SaveINT(&pal_mode, 1);
		{
			int i;
			for (i = 0; i < XEP80_HEIGHT; ++i) {
				UBYTE ptr = ((int)(line_pointers[i] - video_ram)) / 0x100;
				StateSav_SaveUBYTE(&ptr, 1);
			}
		}
		StateSav_SaveUBYTE(video_ram, 8192);
	}
}

void XEP80_StateRead(void)
{
	int local_xep80_enabled = FALSE;
	int local_show_xep80 = FALSE;

	/* test for end of file */
	StateSav_ReadINT(&local_xep80_enabled, 1);
	if (!XEP80_SetEnabled(local_xep80_enabled))
		XEP80_enabled = FALSE;

	if (local_xep80_enabled) {
		int num_ticks;
		StateSav_ReadINT(&XEP80_port, 1);
		StateSav_ReadINT(&local_show_xep80, 1);
		StateSav_ReadINT(&num_ticks, 1);
		start_trans_cpu_clock = ANTIC_CPU_CLOCK - num_ticks;
		StateSav_ReadINT(&output_word, 1);
		StateSav_ReadINT(&input_count, 1);
		StateSav_ReadINT(&receiving, 1);
		StateSav_ReadUWORD(input_queue, IN_QUEUE_SIZE);
		StateSav_ReadINT(&receiving, 1);
		StateSav_ReadUBYTE(&last_char, 1);

		StateSav_ReadINT(&xpos, 1);
		StateSav_ReadINT(&xscroll, 1);
		StateSav_ReadINT(&ypos, 1);
		StateSav_ReadINT(&cursor_x, 1);
		StateSav_ReadINT(&cursor_y, 1);
		StateSav_ReadINT(&curs, 1);
		StateSav_ReadINT(&old_xpos, 1);
		StateSav_ReadINT(&old_ypos, 1);
		StateSav_ReadINT(&lmargin, 1);
		StateSav_ReadINT(&rmargin, 1);
		StateSav_ReadUBYTE(&attrib_a, 1);
		UpdateAttributeBits(attrib_a, &font_a_index, &font_a_double, &font_a_blank, &font_a_blink);
		StateSav_ReadUBYTE(&attrib_b, 1);
		UpdateAttributeBits(attrib_b, &font_b_index, &font_b_double, &font_b_blank, &font_b_blink);
		StateSav_ReadINT(&list_mode, 1);
		StateSav_ReadINT(&escape_mode, 1);
		StateSav_ReadINT(&char_set, 1);
		StateSav_ReadINT(&cursor_on, 1);
		StateSav_ReadINT(&cursor_blink, 1);
		StateSav_ReadINT(&cursor_overwrite, 1);
		StateSav_ReadINT(&blink_reverse, 1);
		StateSav_ReadINT(&inverse_mode, 1);
		StateSav_ReadINT(&screen_output, 1);
		StateSav_ReadINT(&burst_mode, 1);
		StateSav_ReadINT(&graphics_mode, 1);
		StateSav_ReadINT(&pal_mode, 1);
		{
			int i;
			for (i = 0; i < XEP80_HEIGHT; ++i) {
				UBYTE ptr;
				StateSav_ReadUBYTE(&ptr, 1);
				line_pointers[i] = video_ram + 0x100*ptr;
			}
		}

		StateSav_ReadUBYTE(video_ram, 8192);
		UpdateTVSystem();
		BlitScreen(); /* Clear the old text screen */
	}
#if SUPPORTS_CHANGE_VIDEOMODE
	VIDEOMODE_Set80Column(local_show_xep80);
#endif
}

#endif /* XEP80 */

/*
vim:ts=4:sw=4:
*/
