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

    INTEL 8275 Programmable CRT Controller implementation

    25-05-2008 Initial implementation [Miodrag Milanovic]

    Copyright MESS team.
    Visit http://mamedev.org for licensing and usage restrictions.

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

#include "driver.h"
#include "i8275.h"

#define I8275_COMMAND_RESET				0
#define I8275_COMMAND_START_DISPLAY		1
#define I8275_COMMAND_STOP_DISPLAY		2
#define I8275_COMMAND_READ_LIGHT_PEN	3
#define I8275_COMMAND_LOAD_CURSOR		4
#define I8275_COMMAND_ENABLE_INTERRUPT	5
#define I8275_COMMAND_DISABLE_INTERRUPT	6
#define I8275_COMMAND_PRESET_COUNTERS	7

#define I8275_PARAM_RESET				4
#define I8275_PARAM_READ_LIGHT_PEN		2
#define I8275_PARAM_LOAD_CURSOR			2

#define I8275_PARAM_NONE				0
#define I8275_PARAM_READ				1
#define I8275_PARAM_WRITE				2

#define I8275_STATUS_FIFO_OVERRUN		0x01
#define I8275_STATUS_DMA_UNDERRUN		0x02
#define I8275_STATUS_VIDEO_ENABLE		0x04
#define I8275_STATUS_IMPROPER_COMMAND	0x08
#define I8275_STATUS_LIGHT_PEN			0x10
#define I8275_STATUS_INTERRUPT_REQUEST	0x20
#define I8275_STATUS_INTERRUPT_ENABLE	0x40

#define I8275_ROW_TYPE_NORMAL			0
#define I8275_ROW_TYPE_SPACED			1

#define I8275_LINE_COUNTER_MODE_0		0
#define I8275_LINE_COUNTER_MODE_1		1

#define I8275_FIELD_ATTRIBUTE_TRANSPARENT		0
#define I8275_FIELD_ATTRIBUTE_NONTRANSPARENT	1

#define I8275_CURSOR_BLINK_REVERSED		0
#define I8275_CURSOR_BLINK_UNDERLINE	1
#define I8275_CURSOR_NONBLINK_REVERSED	2
#define I8275_CURSOR_NONBLINK_UNDERLINE	3


#define	VERBOSE			1

#define	LOG(x)		do { if (VERBOSE) logerror x; } while (0)


typedef struct _i8275_t i8275_t;

struct _i8275_t
{
	const device_config *screen;

	const i8275_interface *intf;

	UINT8 status_reg;				/* value of status reggister */
	UINT8 num_of_params;			/* expected number of parameters */
	UINT8 current_command;			/* command currently executing */
	UINT8 param_type;				/* parameter type */

	UINT8 cursor_col;				/* current cursor column */
	UINT8 cursor_row;				/* current cursor row */

	UINT8 light_pen_col;			/* current light pen column */
	UINT8 light_pen_row;			/* current light pen row */

	/* reset command parameter values*/
	/* parameter 0 */
	UINT8 rows_type;
	UINT8 chars_per_row;
	/* parameter 1 */
	UINT8 vert_retrace_rows;
	UINT8 rows_per_frame;
	/* parameter 2 */
	UINT8 undeline_line_num;
	UINT8 lines_per_row;
	/* parameter 3 */
	UINT8 line_counter_mode;
	UINT8 field_attribute_mode;
	UINT8 cursor_format;
	UINT8 hor_retrace_count;

	/* values for start display command */
	UINT8 burst_space_code;
	UINT8 burst_count_code;

	/* buffers */
	UINT8 row_buffer_1[80];
	UINT8 row_buffer_2[80];
	UINT8 row_pos;
	UINT8 buffer_used;

	UINT8 fifo_buffer_1[16];
	UINT8 fifo_buffer_2[16];
	UINT8 fifo_write;

	int ypos;
	int current_row;

	UINT8 cursor_blink_cnt;
	UINT8 char_blink_cnt;

	UINT8 next_in_fifo;

	UINT8 lineattr;
	UINT8 rvv;
	UINT8 gpa;
	UINT8 hlgt;
	UINT8 underline;
	UINT8 blink;

	UINT8 last_data;
};

INLINE i8275_t *get_safe_token(const device_config *device)
{
	assert(device != NULL);
	assert(device->token != NULL);

	return (i8275_t *)device->token;
}


/* Register Access */
static UINT8 i8275_get_parameter_light_pen(const device_config *device, offs_t offset)
{
	i8275_t *i8275 = get_safe_token(device);
	UINT8 val = 0;

	switch(offset) {
		case 0 :
				val = i8275->light_pen_col;
				break;
		case 1 :
				val = i8275->light_pen_row;
				break;
	}
	return val;
}

READ8_DEVICE_HANDLER( i8275_r )
{
	UINT8 val;
	i8275_t *i8275 = get_safe_token(device);

	if (offset & 0x01)
	{
		/* Status register */
		val = i8275->status_reg;
		/* status reset after read */
		i8275->status_reg &= ~I8275_STATUS_FIFO_OVERRUN;
		i8275->status_reg &= ~I8275_STATUS_DMA_UNDERRUN;
		i8275->status_reg &= ~I8275_STATUS_IMPROPER_COMMAND;
		i8275->status_reg &= ~I8275_STATUS_LIGHT_PEN;
		i8275->status_reg &= ~I8275_STATUS_INTERRUPT_REQUEST;
	}
	else
	{
		/* Parameter register */
		val = 0x00;
		if (i8275->param_type==I8275_PARAM_READ) {
			if (i8275->num_of_params > 0) {
				val = i8275_get_parameter_light_pen(device, 2 - i8275->num_of_params);
				i8275->num_of_params--;
			} else {
				i8275->status_reg |= I8275_STATUS_IMPROPER_COMMAND;
			}
		} else {
			LOG(("i8275 : ERROR reading parameter\n"));
			i8275->status_reg |= I8275_STATUS_IMPROPER_COMMAND;
		}
	}
	return val;
}

static void i8275_recompute_parameters(const device_config *device)
{
	i8275_t *i8275 = get_safe_token(device);
	int horiz_pix_total = 0;
	int vert_pix_total = 0;
	rectangle visarea;

	horiz_pix_total = (i8275->chars_per_row + 1) * i8275->intf->width;
	vert_pix_total  = (i8275->lines_per_row + 1) * (i8275->rows_per_frame + 1);
	if (i8275->rows_type==1) {
		vert_pix_total *= 2; // Use of spaced rows
	}

	visarea.min_x = 0;
	visarea.min_y = 0;
	visarea.max_x = horiz_pix_total - 1;
	visarea.max_y = vert_pix_total - 1;

	video_screen_configure(i8275->screen, horiz_pix_total, vert_pix_total, &visarea,
				video_screen_get_frame_period(i8275->screen).attoseconds);
}

static void i8275_set_parameter_reset(const device_config *device, offs_t offset, UINT8 data)
{
	i8275_t *i8275 = get_safe_token(device);
	switch(offset) {
		case 0 :
				i8275->rows_type = (data >> 7) & 1;
				i8275->chars_per_row = data & 0x7f;
				break;
		case 1 :
				i8275->vert_retrace_rows = (data >> 6) & 3;
				i8275->rows_per_frame = data & 0x3f;
				break;
		case 2 :
				i8275->undeline_line_num = (data >> 4) & 0x0f;
				i8275->lines_per_row = data & 0x0f;
				break;
		case 3 :
				i8275->line_counter_mode = (data >> 7) & 1;
				i8275->field_attribute_mode = (data >> 6) & 1;
				i8275->cursor_format  = (data >> 4) & 3;
				i8275->hor_retrace_count = data & 0x0f;
				break;
	}
}

static void i8275_set_parameter_cursor(const device_config *device, offs_t offset, UINT8 data)
{
	i8275_t *i8275 = get_safe_token(device);
	switch(offset) {
		case 0 :
				i8275->cursor_col = data;
				break;
		case 1 :
				i8275->cursor_row = data;
				break;
	}
}


WRITE8_DEVICE_HANDLER( i8275_w )
{
	i8275_t *i8275 = get_safe_token(device);

	if (offset & 0x01)
	{
		/* Command register */
		if (i8275->num_of_params != 0) {
			i8275->status_reg |= I8275_STATUS_IMPROPER_COMMAND;
			return;
		}
		i8275->current_command = (data >> 5) & 7;
		i8275->num_of_params = I8275_PARAM_NONE;
		i8275->param_type = I8275_PARAM_NONE;
		switch(i8275->current_command) {
			case I8275_COMMAND_RESET		:
											i8275->num_of_params = I8275_PARAM_RESET;
											i8275->param_type = I8275_PARAM_WRITE;
											/* set status register */
											i8275->status_reg &= ~I8275_STATUS_INTERRUPT_ENABLE;
											i8275->status_reg &= ~I8275_STATUS_VIDEO_ENABLE;
											break;
			case I8275_COMMAND_START_DISPLAY:
											i8275->burst_space_code = (data >> 2) & 7;
											i8275->burst_count_code = data & 3;
											/* set status register */
											i8275->status_reg |= I8275_STATUS_VIDEO_ENABLE;
											i8275->status_reg |= I8275_STATUS_INTERRUPT_ENABLE;
											i8275_recompute_parameters(device);
											break;
			case I8275_COMMAND_STOP_DISPLAY :
											/* set status register */
											i8275->status_reg &= ~I8275_STATUS_VIDEO_ENABLE;
											break;
			case I8275_COMMAND_READ_LIGHT_PEN:
											i8275->num_of_params = I8275_PARAM_READ_LIGHT_PEN;
											i8275->param_type = I8275_PARAM_READ;
											break;
			case I8275_COMMAND_LOAD_CURSOR	:
											i8275->num_of_params = I8275_PARAM_LOAD_CURSOR;
											i8275->param_type = I8275_PARAM_WRITE;
											break;
			case I8275_COMMAND_ENABLE_INTERRUPT	:
											/* set status register */
											i8275->status_reg |= I8275_STATUS_INTERRUPT_ENABLE;
											break;
			case I8275_COMMAND_DISABLE_INTERRUPT:
											/* set status register */
											i8275->status_reg &= ~I8275_STATUS_INTERRUPT_ENABLE;
											break;
			case I8275_COMMAND_PRESET_COUNTERS	:
											break;
		}
	}
	else
	{
		/* Parameter register */
		if (i8275->param_type==I8275_PARAM_WRITE) {
			if (i8275->num_of_params > 0) {
				if (i8275->current_command == I8275_COMMAND_RESET) {
					i8275_set_parameter_reset(device, 4 - i8275->num_of_params ,data);
				} else {
					i8275_set_parameter_cursor(device, 2 - i8275->num_of_params, data);
				}
				i8275->num_of_params--;
			} else {
				i8275->status_reg |= I8275_STATUS_IMPROPER_COMMAND;
			}
		} else {
			LOG(("i8275 : ERROR writing parameter\n"));
			i8275->status_reg |= I8275_STATUS_IMPROPER_COMMAND;
		}
	}
}


static void i8275_draw_char_line(const device_config *device)
{
	i8275_t *i8275 = get_safe_token(device);
	int xpos = 0;
	int line = 0;
	UINT8 lc = 0;
	UINT8 vsp = 0;
	UINT8 lten = 0;
	UINT8 fifo_read = 0;

	for(line=0;line<=i8275->lines_per_row;line++) {
		// If line counter is 1 then select right values
		lc = (i8275->line_counter_mode==1) ? (line - 1) % i8275->lines_per_row : line;
		fifo_read = 0;
		for(xpos=0;xpos<=i8275->chars_per_row;xpos++) {
			UINT8 chr =	(i8275->buffer_used==0) ? i8275->row_buffer_2[xpos] : i8275->row_buffer_1[xpos];
			if (i8275->undeline_line_num & 0x08) {
				vsp = (line==0 || line==i8275->lines_per_row) ? 1 : 0;
			}

			if ((chr & 0x80)==0x80) {
				if ((chr & 0xc0)==0xc0) {
					// character attribute code
					i8275->lineattr = 0;
				} else {
					// field attribute code
					i8275->hlgt = chr & 1;
					i8275->blink = (chr >> 1) & 1;
					i8275->gpa = (chr >> 2) & 3;
					i8275->rvv = (chr >> 4) & 1;
					i8275->underline = (chr >> 5) & 1;
				}

				if (i8275->field_attribute_mode==0) {
					chr = (i8275->buffer_used==0) ? i8275->fifo_buffer_2[fifo_read] : i8275->fifo_buffer_1[fifo_read];
					fifo_read = (fifo_read + 1 ) % 16;
				} else {
					vsp = 1;
				}
			}
			if (vsp==0 && i8275->blink) {
				vsp = (i8275->char_blink_cnt < 32)  ? 1: 0;
			}
			if ((i8275->current_row == i8275->cursor_row) && (xpos ==  i8275->cursor_col - i8275->intf->char_delay)) {
				int vis = 1;
				if ((i8275->cursor_format & 2)==0) {
					vis = (i8275->cursor_blink_cnt<16) ? 1 : 0;
				}
				if ((i8275->cursor_format & 1)==1) {
					lten = (line == i8275->undeline_line_num) ? vis : 0; //underline
				} else {
					lten = vis; // block cursor
				}
			} else {
				lten = 0;
			}

			i8275->intf->display_pixels(device,
				xpos * i8275->intf->width, // x position on screen of starting point
			 	i8275->ypos, // y position on screen
				lc, // current line of char
				(chr & 0x7f),  // char code to be displayed
				i8275->lineattr,  // line attribute code
				lten | i8275->underline,  // light enable signal
				i8275->rvv,  // reverse video signal
				vsp, // video suppression
				i8275->gpa,  // general purpose attribute code
				i8275->hlgt  // highlight
			);
			vsp = 0;
		}
		i8275->ypos++;
	}
	i8275->current_row++;
}

void i8275_dack_set_data(const device_config *device, UINT8 data)
{
	i8275_t *i8275 = get_safe_token(device);

	if (i8275->next_in_fifo == 1)
	{
		i8275->next_in_fifo = 0;

		if(i8275->buffer_used == 0)
		{
			i8275->fifo_buffer_1[i8275->fifo_write] = data & 0x7f;
		}
		else
		{
			i8275->fifo_buffer_2[i8275->fifo_write] = data & 0x7f;
		}
		i8275->fifo_write = (i8275->fifo_write + 1) % 16;

		if (i8275->last_data == 0xf1) {
			i8275->row_pos = i8275->chars_per_row + 1;
		}
	}
	else
	{
		if(i8275->buffer_used == 0)
		{
			i8275->row_buffer_1[i8275->row_pos++] = data;
		}
		else
		{
			i8275->row_buffer_2[i8275->row_pos++] = data;
		}
		if (i8275->field_attribute_mode==0)
		{
			if ((data & 0x80)==0x80)
			{
				i8275->last_data = data;
				i8275->next_in_fifo = 1;
			}
		}
	}

	if ((i8275->row_pos - 1)==i8275->chars_per_row )
	{
		i8275->buffer_used = (i8275->buffer_used==0) ? 1 : 0;
		i8275->row_pos = 0;
		i8275->fifo_write = 0;
		i8275_draw_char_line(device);
	}
	if (i8275->current_row == (i8275->rows_per_frame + 1))
	{
		i8275->ypos = 0;
		i8275->current_row = 0;
		i8275->intf->dma_request(device,0);
	}
}

/* Screen Update */
void i8275_update(const device_config *device, bitmap_t *bitmap, const rectangle *cliprect)
{
	i8275_t *i8275 = get_safe_token(device);
	i8275->ypos = 0;
	i8275->lineattr = 0;
	i8275->rvv = 0;
	i8275->gpa = 0;
	i8275->hlgt = 0;
	i8275->underline = 0;
	i8275->blink = 0;
	i8275->row_pos = 0;
	i8275->fifo_write = 0;

	if ((i8275->status_reg & I8275_STATUS_VIDEO_ENABLE)==0) {
		bitmap_fill(bitmap, cliprect, get_black_pen(device->machine));
	} else {
		// if value < 16 it is visible otherwise not
		i8275->cursor_blink_cnt++;
		if(i8275->cursor_blink_cnt==32) i8275->cursor_blink_cnt = 0;
		// if value < 32 it is visible otherwise not
		i8275->char_blink_cnt++;
		if(i8275->char_blink_cnt==64) i8275->char_blink_cnt = 0;
		i8275->intf->dma_request(device,1);
	}
	if (i8275->status_reg & I8275_STATUS_INTERRUPT_ENABLE) {
		i8275->status_reg |= I8275_STATUS_INTERRUPT_REQUEST;
	}
}

/* Device Interface */

static DEVICE_START( i8275 )
{
	i8275_t *i8275 = get_safe_token(device);
	// validate arguments

	assert(device != NULL);
	assert(device->tag != NULL);
	assert(device->static_config != NULL);

	i8275->intf = device->static_config;

	assert(i8275->intf->dma_request != NULL);
	assert(i8275->intf->display_pixels != NULL);

	// get the screen device

	i8275->screen = devtag_get_device(device->machine, i8275->intf->screen_tag);
	assert(i8275->screen != NULL);

	// register for state saving
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->status_reg);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->num_of_params);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->current_command);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->param_type);

	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->cursor_col);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->cursor_row);

	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->light_pen_col);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->light_pen_row);

	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->rows_type);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->chars_per_row);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->vert_retrace_rows);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->rows_per_frame);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->undeline_line_num);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->lines_per_row);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->line_counter_mode);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->field_attribute_mode);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->cursor_format);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->hor_retrace_count);

	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->burst_space_code);
	state_save_register_item(device->machine, "I8275", device->tag, 0, i8275->burst_count_code);
}

static DEVICE_RESET( i8275 )
{
	i8275_t *i8275 = get_safe_token(device);

	i8275->status_reg = 0;
	i8275->num_of_params = 0;
	i8275->current_command = 0;
	i8275->param_type = 0;

	i8275->cursor_col = 0;
	i8275->cursor_row = 0;

	i8275->light_pen_col = 0;
	i8275->light_pen_row = 0;

	i8275->rows_type = 0;
	i8275->chars_per_row = 0;
	i8275->vert_retrace_rows = 0;
	i8275->rows_per_frame = 0;
	i8275->undeline_line_num = 0;
	i8275->lines_per_row = 0;
	i8275->line_counter_mode = 0;
	i8275->field_attribute_mode = 0;
	i8275->cursor_format = 0;
	i8275->hor_retrace_count = 0;

	i8275->burst_space_code = 0;
	i8275->burst_count_code = 0;

	i8275->row_pos = 0;
	i8275->buffer_used = 0;

	i8275->fifo_write = 0;

	i8275->ypos = 0;
	i8275->current_row = 0;

	i8275->cursor_blink_cnt = 0;
	i8275->char_blink_cnt = 0;
	i8275->next_in_fifo = 0;

	i8275->lineattr = 0;
	i8275->rvv = 0;
	i8275->gpa = 0;
	i8275->hlgt = 0;
	i8275->underline = 0;
	i8275->blink = 0;
}

static DEVICE_SET_INFO( i8275 )
{
	switch (state)
	{
		/* no parameters to set */
	}
}

DEVICE_GET_INFO( i8275 )
{
	switch (state)
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case DEVINFO_INT_TOKEN_BYTES:					info->i = sizeof(i8275_t);					break;
		case DEVINFO_INT_INLINE_CONFIG_BYTES:			info->i = 0;								break;
		case DEVINFO_INT_CLASS:							info->i = DEVICE_CLASS_PERIPHERAL;			break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_SET_INFO:						info->set_info = DEVICE_SET_INFO_NAME(i8275); break;
		case DEVINFO_FCT_START:							info->start = DEVICE_START_NAME(i8275);		break;
		case DEVINFO_FCT_STOP:							/* Nothing */								break;
		case DEVINFO_FCT_RESET:							info->reset = DEVICE_RESET_NAME(i8275);		break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case DEVINFO_STR_NAME:							strcpy(info->s, "Intel 8275");				break;
		case DEVINFO_STR_FAMILY:						strcpy(info->s, "Intel 8275");				break;
		case DEVINFO_STR_VERSION:						strcpy(info->s, "1.0");						break;
		case DEVINFO_STR_SOURCE_FILE:					strcpy(info->s, __FILE__);					break;
		case DEVINFO_STR_CREDITS:						strcpy(info->s, "Copyright MESS Team");		break;
	}
}
