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

  MOS video interface chip 6560
  PeT mess@utanet.at

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

#include <math.h>
#include <stdio.h>
#include "driver.h"
#include "utils.h"
#include "sound/custom.h"

#define VERBOSE_DBG 0
#include "includes/cbm.h"
#include "includes/vc20.h"
#include "includes/cbmserb.h"
#include "includes/vc1541.h"
#include "includes/vc20tape.h"

#include "includes/vic6560.h"

/* usage of GFX system for lightpenpointer drawing,
 * needs little more testing with working dipswitches */
/*#define GFX */

const unsigned char vic6560_palette[] =
{
/* ripped from vice, a very excellent emulator */
/* black, white, red, cyan */
	0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0xf0,
/* purple, green, blue, yellow */
	0x60, 0x00, 0x60, 0x00, 0xa0, 0x00, 0x00, 0x00, 0xf0, 0xd0, 0xd0, 0x00,
/* orange, light orange, pink, light cyan, */
	0xc0, 0xa0, 0x00, 0xff, 0xa0, 0x00, 0xf0, 0x80, 0x80, 0x00, 0xff, 0xff,
/* light violett, light green, light blue, light yellow */
	0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xa0, 0xff, 0xff, 0xff, 0x00
};

const struct CustomSound_interface vic6560_sound_interface =
{
	vic6560_custom_start
};

UINT8 vic6560[16];

/* lightpen delivers values from internal counters
 * they do not start with the visual area or frame area */
#define VIC6560_X_BEGIN 38
#define VIC6560_Y_BEGIN -6			   /* first 6 lines after retrace not for lightpen! */
#define VIC6561_X_BEGIN 38
#define VIC6561_Y_BEGIN -6
#define VIC656X_X_BEGIN (vic6560_pal?VIC6561_X_BEGIN:VIC6560_X_BEGIN)
#define VIC656X_Y_BEGIN (vic6560_pal?VIC6561_Y_BEGIN:VIC6560_Y_BEGIN)
/* lightpen behaviour in pal or mono multicolor not tested */
#define VIC6560_X_VALUE ((LIGHTPEN_X_VALUE+VIC656X_X_BEGIN \
			  +VIC656X_MAME_XPOS)/2)
#define VIC6560_Y_VALUE ((LIGHTPEN_Y_VALUE+VIC656X_Y_BEGIN \
			  +VIC656X_MAME_YPOS)/2)

#define INTERLACE (vic6560&0x80)	   /*only ntsc version */
/* ntsc 1 - 8 */
/* pal 5 - 19 */
#define XPOS (((int)vic6560[0]&0x7f)*4)
#define YPOS ((int)vic6560[1]*2)

/* ntsc values >= 31 behave like 31 */
/* pal value >= 32 behave like 32 */
#define CHARS_X ((int)vic6560[2]&0x7f)
#define CHARS_Y (((int)vic6560[3]&0x7e)>>1)

#define MATRIX8X16 (vic6560[3]&1)	   /* else 8x8 */
#define CHARHEIGHT (MATRIX8X16?16:8)
#define XSIZE (CHARS_X*8)
#define YSIZE (CHARS_Y*CHARHEIGHT)

/* colorram and backgroundcolor are changed */
#define INVERTED (!(vic6560[0x0f]&8))

#define CHARGENADDR (((int)vic6560[5]&0xf)<<10)
#define VIDEOADDR ( ( ((int)vic6560[5]&0xf0)<<(10-4))\
		    | ( ((int)vic6560[2]&0x80)<<(9-7)) )
#define VIDEORAMSIZE (YSIZE*XSIZE)
#define CHARGENSIZE (256*HEIGHTPIXEL)

#define HELPERCOLOR (vic6560[0xe]>>4)
#define BACKGROUNDCOLOR (vic6560[0xf]>>4)
#define FRAMECOLOR (vic6560[0xf]&7)

int vic6560_pal;

static mame_bitmap *vic6560_bitmap;
static int rasterline = 0, lastline = 0;
static void vic6560_drawlines (int start, int last);

static int (*vic_dma_read) (int);
static int (*vic_dma_read_color) (int);

static int vic656x_xsize, vic656x_ysize, vic656x_lines, vic656x_vretracerate;
static int charheight, matrix8x16, inverted;
static int chars_x, chars_y;
static int xsize, ysize, xpos, ypos;
static int chargenaddr, videoaddr;

/* values in videoformat */
static UINT16 backgroundcolor, framecolor, helpercolor, white, black;

/* arrays for bit to color conversion without condition checking */
static UINT16 mono[2], monoinverted[2], multi[4], multiinverted[4];

/* transparent, white, black */
static UINT32 pointercolortable[3] =
{0};
static const gfx_layout pointerlayout =
{
	8, 8,
	1,
	2,
	{0, 64},
	{0, 1, 2, 3, 4, 5, 6, 7},
	{0 * 8, 1 * 8, 2 * 8, 3 * 8, 4 * 8, 5 * 8, 6 * 8, 7 * 8},
	8 * 8
};

static const UINT8 pointermask[] =
{
	0x00, 0x70, 0x60, 0x50, 0x08, 0x04, 0x00, 0x00,		/* blackmask */
	0xf0, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00	/* whitemask */
};

static gfx_element *pointerelement;

static void vic656x_init (void)
{
	vic656x_xsize = VIC656X_XSIZE;
	vic656x_ysize = VIC656X_YSIZE;
	vic656x_lines = VIC656X_LINES;
	vic656x_vretracerate = VIC656X_VRETRACERATE;
}

void vic6560_init (int (*dma_read) (int), int (*dma_read_color) (int))
{
	vic6560_pal = FALSE;
	vic_dma_read = dma_read;
	vic_dma_read_color = dma_read_color;
	vic656x_init ();
}

void vic6561_init (int (*dma_read) (int), int (*dma_read_color) (int))
{
	vic6560_pal = TRUE;
	vic_dma_read = dma_read;
	vic_dma_read_color = dma_read_color;
	vic656x_init ();
}

static void vic6560_video_stop(running_machine *machine)
{
	freegfx(pointerelement);
}

VIDEO_START( vic6560 )
{
	black = machine->pens[0];
	white = machine->pens[1];
	pointerelement = allocgfx(&pointerlayout);
	decodegfx(pointerelement, pointermask, 0, 1);
	/* 7-Sep-2007 - After 0.118u5, you cannot revector the color table */
	/* pointerelement->colortable = pointercolortable; */
	pointercolortable[1] = machine->pens[1];
	pointercolortable[2] = machine->pens[0];
	pointerelement->total_colors = 3;
	vic6560_bitmap = auto_bitmap_alloc(machine->screen[0].width, machine->screen[0].height, BITMAP_FORMAT_INDEXED16);
	add_exit_callback(machine, vic6560_video_stop);
}

WRITE8_HANDLER ( vic6560_port_w )
{
	DBG_LOG (1, "vic6560_port_w", (errorlog, "%.4x:%.2x\n", offset, data));
	switch (offset)
	{
	case 0xa:
	case 0xb:
	case 0xc:
	case 0xd:
	case 0xe:
		vic6560_soundport_w (offset, data);
		break;
	}
	if (vic6560[offset] != data)
	{
		switch (offset)
		{
		case 0:
		case 1:
		case 2:
		case 3:
		case 5:
		case 0xe:
		case 0xf:
			vic6560_drawlines (lastline, rasterline);
			break;
		}
		vic6560[offset] = data;
		switch (offset)
		{
		case 0:
			xpos = XPOS;
			break;
		case 1:
			ypos = YPOS;
			break;
		case 2:
			/* ntsc values >= 31 behave like 31 */
			/* pal value >= 32 behave like 32 */
			chars_x = CHARS_X;
			videoaddr = VIDEOADDR;
			xsize = XSIZE;
			break;
		case 3:
			matrix8x16 = MATRIX8X16;
			charheight = CHARHEIGHT;
			chars_y = CHARS_Y;
			ysize = YSIZE;
			break;
		case 5:
			chargenaddr = CHARGENADDR;
			videoaddr = VIDEOADDR;
			break;
		case 0xe:
			multi[3] = multiinverted[3] = helpercolor = Machine->pens[HELPERCOLOR];
			break;
		case 0xf:
			inverted = INVERTED;
			multi[1] = multiinverted[1] = framecolor = Machine->pens[FRAMECOLOR];
			mono[0] = monoinverted[1] =
				multi[0] = multiinverted[2] = backgroundcolor = Machine->pens[BACKGROUNDCOLOR];
			break;
		}
	}
}

 READ8_HANDLER ( vic6560_port_r )
{
	static double lightpenreadtime = 0.0;
	int val;

	switch (offset)
	{
	case 3:
		val = ((rasterline & 1) << 7) | (vic6560[offset] & 0x7f);
		break;
	case 4:						   /*rasterline */
		vic6560_drawlines (lastline, rasterline);
		val = (rasterline / 2) & 0xff;
		break;
	case 6:						   /*lightpen horicontal */
	case 7:						   /*lightpen vertical */
		if (LIGHTPEN_BUTTON
			&& ((attotime_to_double(timer_get_time ()) - lightpenreadtime) * VIC6560_VRETRACERATE >= 1))
		{
			/* only 1 update each frame */
			/* and diode must recognize light */
			if (1)
			{
				vic6560[6] = VIC6560_X_VALUE;
				vic6560[7] = VIC6560_Y_VALUE;
			}
			lightpenreadtime = attotime_to_double(timer_get_time ());
		}
		val = vic6560[offset];
		break;
	case 8:						   /* poti 1 */
		val = PADDLE1_VALUE;
		break;
	case 9:						   /* poti 2 */
		val = PADDLE2_VALUE;
		break;
	default:
		val = vic6560[offset];
		break;
	}
	DBG_LOG (3, "vic6560_port_r", (errorlog, "%.4x:%.2x\n", offset, val));
	return val;
}

static int DOCLIP (rectangle *r1, const rectangle *r2)
{
	if (r1->min_x > r2->max_x)
		return 0;
	if (r1->max_x < r2->min_x)
		return 0;
	if (r1->min_y > r2->max_y)
		return 0;
	if (r1->max_y < r2->min_y)
		return 0;
	if (r1->min_x < r2->min_x)
		r1->min_x = r2->min_x;
	if (r1->max_x > r2->max_x)
		r1->max_x = r2->max_x;
	if (r1->min_y < r2->min_y)
		r1->min_y = r2->min_y;
	if (r1->max_y > r2->max_y)
		r1->max_y = r2->max_y;
	return 1;
}

static void vic6560_draw_character (int ybegin, int yend,
									int ch, int yoff, int xoff,
									UINT16 *color)
{
	int y, code;

	for (y = ybegin; y <= yend; y++)
	{
		code = vic_dma_read ((chargenaddr + ch * charheight + y) & 0x3fff);
		*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 0) = color[code >> 7];
		*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 1) = color[(code >> 6) & 1];
		*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 2) = color[(code >> 5) & 1];
		*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 3) = color[(code >> 4) & 1];
		*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 4) = color[(code >> 3) & 1];
		*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 5) = color[(code >> 2) & 1];
		*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 6) = color[(code >> 1) & 1];
		*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 7) = color[code & 1];
	}
}

static void vic6560_draw_character_multi (int ybegin, int yend,
										  int ch, int yoff, int xoff, UINT16 *color)
{
	int y, code;

	for (y = ybegin; y <= yend; y++)
	{
		code = vic_dma_read ((chargenaddr + ch * charheight + y) & 0x3fff);
		*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 0) =
			*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 1) = color[code >> 6];
		*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 2) =
			*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 3) = color[(code >> 4) & 3];
		*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 4) =
			*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 5) = color[(code >> 2) & 3];
		*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 6) =
			*BITMAP_ADDR16(vic6560_bitmap, y + yoff, xoff + 7) = color[code & 3];
	}
}


#ifndef GFX
INLINE void vic6560_draw_pointer (mame_bitmap *bitmap,
								  rectangle *visible, int xoff, int yoff)
{
	/* this is a a static graphical object */
	/* should be easy to convert to gfx_element!? */
	static UINT8 blackmask[] =
	{0x00, 0x70, 0x60, 0x50, 0x08, 0x04, 0x00, 0x00};
	static UINT8 whitemask[] =
	{0xf0, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00};
	int i, j, y, x;

	for (y = visible->min_y, j = yoff; y <= visible->max_y; y++, j++)
	{
		for (x = visible->min_x, i = xoff; x <= visible->max_x; x++, i++)
		{
			if ((blackmask[j] << i) & 0x80)
				*BITMAP_ADDR16(bitmap, y, x) = black;
			else if ((whitemask[j] << (i & ~1)) & 0x80)
				*BITMAP_ADDR16(bitmap, y, x) = white;
		}
	}
}
#endif

static void vic6560_drawlines (int first, int last)
{
	int line, vline;
	int offs, yoff, xoff, ybegin, yend, i;
	int attr, ch;

	lastline = last;
	if (first >= last)
		return;

	for (line = first; (line < ypos) && (line < last); line++)
	{
		memset16 (BITMAP_ADDR16(vic6560_bitmap, line, 0), framecolor, vic656x_xsize);
	}

	for (vline = line - ypos; (line < last) && (line < ypos + ysize);)
	{
		if (matrix8x16)
		{
			offs = (vline >> 4) * chars_x;
			yoff = (vline & ~0xf) + ypos;
			ybegin = vline & 0xf;
			yend = (vline + 0xf < last - ypos) ? 0xf : ((last - line) & 0xf) + ybegin;
		}
		else
		{
			offs = (vline >> 3) * chars_x;
			yoff = (vline & ~7) + ypos;
			ybegin = vline & 7;
			yend = (vline + 7 < last - ypos) ? 7 : ((last - line) & 7) + ybegin;
		}

		if (xpos > 0)
		{
			for (i = ybegin; i <= yend; i++)
				memset16 (BITMAP_ADDR16(vic6560_bitmap, yoff + i, 0), framecolor, xpos);
		}
		for (xoff = xpos; (xoff < xpos + xsize) && (xoff < vic656x_xsize); xoff += 8, offs++)
		{
			ch = vic_dma_read ((videoaddr + offs) & 0x3fff);
			attr = (vic_dma_read_color ((videoaddr + offs) & 0x3fff)) & 0xf;
			if (inverted)
			{
				if (attr & 8)
				{
					multiinverted[0] = Machine->pens[attr & 7];
					vic6560_draw_character_multi (ybegin, yend, ch, yoff, xoff,
												  multiinverted);
				}
				else
				{
					monoinverted[0] = Machine->pens[attr];
					vic6560_draw_character (ybegin, yend, ch, yoff, xoff, monoinverted);
				}
			}
			else
			{
				if (attr & 8)
				{
					multi[2] = Machine->pens[attr & 7];
					vic6560_draw_character_multi (ybegin, yend, ch, yoff, xoff, multi);
				}
				else
				{
					mono[1] = Machine->pens[attr];
					vic6560_draw_character (ybegin, yend, ch, yoff, xoff, mono);
				}
			}
		}
		if (xoff < vic656x_xsize)
		{
			for (i = ybegin; i <= yend; i++)
			{
				memset16 (BITMAP_ADDR16(vic6560_bitmap, yoff + i, xoff),
					framecolor, vic656x_xsize - xoff);
			}
		}
		if (matrix8x16)
		{
			vline = (vline + 16) & ~0xf;
			line = vline + ypos;
		}
		else
		{
			vline = (vline + 8) & ~7;
			line = vline + ypos;
		}
	}

	for (; line < last; line++)
	{
		memset16 (BITMAP_ADDR16(vic6560_bitmap, line, 0), framecolor, vic656x_xsize);
	}
}

INTERRUPT_GEN( vic656x_raster_interrupt )
{
	rectangle r;

	rasterline++;
	if (rasterline >= vic656x_lines)
	{
		rasterline = 0;
		vic6560_drawlines (lastline, vic656x_lines);
		lastline = 0;

		if (LIGHTPEN_POINTER)
		{

			r.min_x = LIGHTPEN_X_VALUE - 1 + VIC656X_MAME_XPOS;
			r.max_x = r.min_x + 8 - 1;
			r.min_y = LIGHTPEN_Y_VALUE - 1 + VIC656X_MAME_YPOS;
			r.max_y = r.min_y + 8 - 1;

			if (DOCLIP (&r, &Machine->screen[0].visarea))
			{
#ifndef GFX
				vic6560_draw_pointer (vic6560_bitmap, &r,
									  r.min_x - (LIGHTPEN_X_VALUE + VIC656X_MAME_XPOS - 1),
									  r.min_y - (LIGHTPEN_Y_VALUE + VIC656X_MAME_YPOS - 1));
#else
				drawgfx (vic6560_bitmap, pointerelement, 0, 0, 0, 0,
						 r.min_x - 1, r.min_y - 1, &r, TRANSPARENCY_PEN, 0);
#endif
			}
		}
	}
}

VIDEO_UPDATE( vic6560 )
{
	copybitmap(bitmap, vic6560_bitmap, 0, 0, 0, 0, cliprect, TRANSPARENCY_NONE, 0);
	return 0;
}
