/***************************************************************************

  apple1.c

  Functions to emulate the video hardware of the Apple I.

  The Apple I video hardware was basically a dumb video terminal; in
  fact it was based on Steve Wozniak's own design for a simple video
  terminal.  It had 40 columns by 24 lines of uppercase-only text.
  Text could only be output at 60 characters per second, one character
  per video frame.  The cursor (a blinking @) could only be advanced
  using spaces or carriage returns.  Carriage returns were the only
  control characters recognized.  Previously written text could not be
  altered, only scrolled off the top of the screen.

  The video memory used seven 1k-bit dynamic shift registers.  Six of
  these held the 6-bit visible character codes, and one stored the
  cursor location as a simple bitmap--the bit for the cursor position
  was set to 0, and all the other bits were 1.

  These shift registers were continuously recirculated, completing one
  cycle per video frame.  As a new line of characters was about to be
  scanned by the video beam, that character line would be recirculated
  into the shift registers and would simultaneously be stored into a
  6x40-bit line buffer (also a shift register).  At this point, if the
  cursor location was in this line, a new character could be written
  into that location in the shift registers and the cursor could be
  advanced.  (Carriage returns were not written into the shift
  registers; they only advanced the cursor.)

  The characters in the line buffer were recirculated 7 times to
  display the 8 scan lines of the characters, before being replaced by
  a new line of characters from the main shift registers.

  Cursor blinking was performed by a Signetics 555 timer IC whose
  output was gated into the character code signals as they passed into
  the line buffer.

  Character images were provided by a Signetics 2513 character
  generator ROM, a chip also used in computer terminals such as the
  ADM-3A.  This ROM had 9 address lines and 5 data lines; it contained
  64 character images, each 5 pixels wide by 8 pixels high, with one
  line of pixels being blank for vertical separation.  The video
  circuitry added the 2 pixels of horizontal separation for each
  character.

  A special CLEAR SCREEN switch on the keyboard, directly connected to
  the video hardware, could be used to clear the video memory and
  return the cursor to the home position.  This was completely
  independant of the processor.

  A schematic of the Apple I video hardware can be found in the
  Apple-1 Operation Manual; look for the schematic titled "Terminal
  Section".  Most of the functionality modeled here was determined by
  reading this schematic.  Many of the chips used were standard 74xx
  TTL chips, but the shift registers used for the video memory and
  line buffer were Signetics 25xx PMOS ICs.  These were already
  becoming obsolete when the Apple I was built, and detailed
  information on them is very hard to find today.

***************************************************************************/

#include "driver.h"
#include "vidhrdw/generic.h"
#include "includes/apple1.h"
#include "mscommon.h"

static struct terminal *apple1_terminal;

int apple1_vh_clrscrn_pressed = 0;		/* flag for CLEAR SCREEN switch */

/* The cursor blinking is generated by a free-running timer with a
   0.52-second period.  It is on for 2/3 of this period and off for
   1/3. */
#define CURSOR_OFF_LENGTH			TIME_IN_SEC(0.52/3)

/**************************************************************************/

static int apple1_getcursorcode(int original_code)
{
	/* Cursor uses symbol 0 (an @ sign) in the character generator ROM. */
	return 0;
}

/**************************************************************************/

VIDEO_START( apple1 )
{
	apple1_terminal = terminal_create(
		0,			/* graphics font 0 (the only one we have) */
		32,			/* Blank character is symbol 32 in the ROM */
		8,			/* use 8 bits for the character code */
		apple1_getcursorcode,
		40, 24);	/* 40 columns, 24 rows */
	if (!apple1_terminal)
		return 1;

	terminal_setcursor(apple1_terminal, 0, 0);
	return 0;
}

/* This function handles all writes to the video display. */
void apple1_vh_dsp_w (int data)
{
	int	x, y;
	int cursor_x, cursor_y;

	/* While CLEAR SCREEN is being held down, the hardware is forced
	   to clear the video memory, so video writes have no effect. */
	if (apple1_vh_clrscrn_pressed)
		return;

	/* The video display port only accepts the 7 lowest bits of the char. */
	data &= 0x7f;

	terminal_getcursor(apple1_terminal, &cursor_x, &cursor_y);

	if (data == '\r') {
		/* Carriage-return moves the cursor to the start of the next
		   line. */
		cursor_x = 0;
		cursor_y++;
	}
	else if (data < ' ') {
		/* Except for carriage-return, the video hardware completely
		   ignores all control characters. */
		return;
	}
	else {
		/* For visible characters, only 6 bits of the ASCII code are
		   used, because the 2513 character generator ROM only
		   contains 64 symbols.  The low 5 bits of the ASCII code are
		   used directly.  Bit 6 is ignored, since it is the same for
		   all the available characters in the ROM.  Bit 7 is inverted
		   before being used as the high bit of the 6-bit ROM symbol
		   index, because the block of 32 ASCII symbols containing the
		   uppercase letters comes first in the ROM. */

		int romindx = (data & 0x1f) | (((data ^ 0x40) & 0x40) >> 1);

		terminal_putchar(apple1_terminal, cursor_x, cursor_y, romindx);
		if (cursor_x < 39)
		{
			cursor_x++;
		}
		else
		{
			cursor_x = 0;
			cursor_y++;
		}
	}

	/* If the cursor went past the bottom line, scroll the text up one line. */
	if (cursor_y == 24)
	{
		for (y = 1; y < 24; y++)
			for (x = 0; x < 40; x++)
				terminal_putchar(apple1_terminal, x, y-1,
								 terminal_getchar(apple1_terminal, x, y));

		for (x = 0; x < 40; x++)
			terminal_putblank(apple1_terminal, x, 23);

		cursor_y--;
	}

	terminal_setcursor(apple1_terminal, cursor_x, cursor_y);
}

/* This function handles clearing the video display on cold-boot or in
   response to a press of the CLEAR SCREEN switch. */
void apple1_vh_dsp_clr (void)
{
	terminal_setcursor(apple1_terminal, 0, 0);
	terminal_clear(apple1_terminal);
}

/* Calculate how long it will take for the display to assert the RDA
   signal in response to a video display write.  This signal indicates
   the display has completed the write and is ready to accept another
   write. */
double apple1_vh_dsp_time_to_ready (void)
{
	int cursor_x, cursor_y;
	int cursor_scanline;
	double scanline_period = cpu_getscanlineperiod();
	double cursor_hfrac;

	/* The video hardware refreshes the screen by reading the
	   character codes from its circulating shift-register memory.
	   Because of the way this memory works, a new character can only
	   be written into the cursor location at the moment this location
	   is about to be read.  This happens during the first scanline of
	   the cursor's character line, when the beam reaches the cursor's
	   horizontal position. */

	terminal_getcursor(apple1_terminal, &cursor_x, &cursor_y);
	cursor_scanline = cursor_y * apple1_charlayout.height;

	/* Each scanline is composed of 455 pixel times.  The first 175 of
	   these are the horizontal blanking period; the remaining 280 are
	   for the visible part of the scanline. */
	cursor_hfrac = (175 + cursor_x * apple1_charlayout.width) / 455;

	if (cpu_getscanline() == cursor_scanline) {
		/* cpu_gethorzbeampos() doesn't account for the horizontal
		   blanking interval; it acts as if the scanline period is
		   entirely composed of visible pixel times.  However, we can
		   still use it to find what fraction of the current scanline
		   period has elapsed. */
		double current_hfrac = cpu_gethorzbeampos() /
							   Machine->drv->screen_width;
		if (current_hfrac < cursor_hfrac)
			return scanline_period * (cursor_hfrac - current_hfrac);
	}

	return cpu_getscanlinetime(cursor_scanline) + 
		   scanline_period * cursor_hfrac;
}

/* Blink the cursor on or off, as appropriate. */
static void apple1_vh_cursor_blink (void)
{
	static int blink_on = 1;		/* cursor is visible initially */
	int new_blink_on;

	/* The cursor is on for 2/3 of its blink period and off for 1/3.
	   This is most easily handled by dividing the total elapsed time
	   by the length of the off-portion of the cycle, giving us the
	   number of one-third-cycles elapsed, then checking the result
	   modulo 3. */

	if (((int) (timer_get_time() / CURSOR_OFF_LENGTH)) % 3 < 2)
		new_blink_on = 1;
	else
		new_blink_on = 0;

	if (new_blink_on != blink_on) {		/* have we changed state? */
		if (new_blink_on)
			terminal_showcursor(apple1_terminal);
		else
			terminal_hidecursor(apple1_terminal);
		blink_on = new_blink_on;
	}
}

VIDEO_UPDATE( apple1 )
{
	apple1_vh_cursor_blink();
	terminal_draw(bitmap, NULL, apple1_terminal);
}
