// license:BSD-3-Clause
// copyright-holders:Curt Coder
/**********************************************************************

    VideoBrain UV201/UV202 video chip emulation

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

#include "emu.h"
#include "uv201.h"

#define VERBOSE (1)
#include "logmacro.h"

//**************************************************************************
//  MACROS / CONSTANTS
//**************************************************************************

// screen parameters
#define SCREEN_WIDTH                232
#define SCREEN_HEIGHT               262
#define VISAREA_WIDTH               193
#define VBLANK_WIDTH                21
#define HBLANK_WIDTH                39
#define HSYNC_WIDTH                 18
#define HFP_WIDTH                   16
#define HBP_WIDTH                   5
#define HBLANK_END                  HSYNC_WIDTH + HFP_WIDTH
#define HBLANK_START                HBLANK_END + VISAREA_WIDTH


// write-only registers
#define REGISTER_COMMAND            0xf7
#define REGISTER_BACKGROUND         0xf5
#define REGISTER_FINAL_MODIFIER     0xf2
#define REGISTER_Y_INTERRUPT        0xf0


// read-only registers
#define REGISTER_X_FREEZE           0xf8
#define REGISTER_Y_FREEZE_LOW       0xf9
#define REGISTER_Y_FREEZE_HIGH      0xfa
#define REGISTER_CURRENT_Y_LOW      0xfb


// read/write registers - RAM memory
#define RAM_RP_LO                   0x00    // cartridge pointer low order
#define RAM_RP_HI_COLOR             0x10    // cartridge pointer high order and color
#define RAM_DX_INT_XCOPY            0x20    // dX, intensity, X-copy
#define RAM_DY                      0x30    // dY
#define RAM_X                       0x40    // X value
#define RAM_Y_LO_A                  0x50    // Y value low order list A
#define RAM_Y_LO_B                  0x60    // Y value low order list B
#define RAM_XY_HI_A                 0x70    // Y value high order and X order list A
#define RAM_XY_HI_B                 0x80    // Y value high order and X order list B


// command register bits
#define COMMAND_YINT_H_O            0x80
#define COMMAND_A_B                 0x40
#define COMMAND_Y_ZM                0x20
#define COMMAND_KBD                 0x10
#define COMMAND_INT                 0x08
#define COMMAND_ENB                 0x04
#define COMMAND_FRZ                 0x02
#define COMMAND_X_ZM                0x01


#define IS_CHANGED(_bit) \
	((m_cmd & _bit) != (data & _bit))

#define RAM(_offset) \
	m_ram[_offset + i]

#define RAM_XORD(_offset) \
	m_ram[_offset + xord]

#define IS_VISIBLE(_y) \
	((_y >= cliprect.min_y) && (_y <= cliprect.max_y))

#define DRAW_PIXEL(_scanline, _dot) \
	if (IS_VISIBLE(_scanline)) bitmap.pix((_scanline), HSYNC_WIDTH + HFP_WIDTH + _dot) = m_palette_val[pixel];



//**************************************************************************
//  LIVE DEVICE
//**************************************************************************

// device type definition
DEFINE_DEVICE_TYPE(UV201, uv201_device, "uv201", "UV201")


//-------------------------------------------------
//  uv201_device - constructor
//-------------------------------------------------

uv201_device::uv201_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
	device_t(mconfig, UV201, tag, owner, clock),
	device_video_interface(mconfig, *this),
	m_write_ext_int(*this),
	m_write_hblank(*this),
	m_read_db(*this)
{
}


//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void uv201_device::device_start()
{
	// resolve callbacks
	m_write_ext_int.resolve_safe();
	m_write_hblank.resolve_safe();
	m_read_db.resolve_safe(0);

	// allocate timers
	m_timer_y_odd = timer_alloc(FUNC(uv201_device::y_update_tick), this);
	m_timer_y_even = timer_alloc(FUNC(uv201_device::y_update_tick), this);
	m_timer_hblank_on = timer_alloc(FUNC(uv201_device::hblank_on), this);
	m_timer_hblank_off = timer_alloc(FUNC(uv201_device::hblank_off), this);

	initialize_palette();

	memset(m_ram, 0x00, sizeof(m_ram));
	m_y_int = 0;
	m_fmod = 0;
	m_bg = 0;
	m_cmd = 0;
	m_freeze_x = 0;
	m_freeze_y = 0;
	m_field = 0;

	// state saving
	save_item(NAME(m_ram));
	save_item(NAME(m_y_int));
	save_item(NAME(m_fmod));
	save_item(NAME(m_bg));
	save_item(NAME(m_cmd));
	save_item(NAME(m_freeze_x));
	save_item(NAME(m_freeze_y));
	save_item(NAME(m_field));
}


//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void uv201_device::device_reset()
{
	m_write_ext_int(CLEAR_LINE);

	m_write_hblank(1);
	m_timer_hblank_off->adjust(attotime::from_ticks( HBLANK_END, m_clock ));
}


//-------------------------------------------------
//  timer events
//-------------------------------------------------

TIMER_CALLBACK_MEMBER(uv201_device::y_update_tick)
{
	if ((m_cmd & COMMAND_INT) && !(m_cmd & COMMAND_FRZ))
	{
		int scanline = screen().vpos();
		LOG("Y-Interrupt at scanline %u\n", scanline);

		m_freeze_y = scanline;

		m_write_ext_int(ASSERT_LINE);
		m_write_ext_int(CLEAR_LINE);
	}
}

TIMER_CALLBACK_MEMBER(uv201_device::hblank_on)
{
	m_write_hblank(1);
	m_timer_hblank_off->adjust(attotime::from_ticks(HBLANK_WIDTH, m_clock));
}

TIMER_CALLBACK_MEMBER(uv201_device::hblank_off)
{
	m_write_hblank(0);
	m_timer_hblank_on->adjust(attotime::from_ticks(VISAREA_WIDTH, m_clock));
}


//-------------------------------------------------
//  initialize_palette -
//-------------------------------------------------

void uv201_device::initialize_palette()
{
	uint8_t offlointensity = 0x00;
	uint8_t offhiintensity = 0xc0;

	uint8_t onlointensity = 0xa0;
	uint8_t onhiintensity = 0xff;

	for (int i = 0; i < 4; i++)
	{
		int offset = i * 8;
		uint8_t onvalue, offvalue;

		if (offset < 16)
		{
			offvalue = offlointensity;
			onvalue = onlointensity;
		}
		else
		{
			offvalue = offhiintensity;
			onvalue = onhiintensity;
		}

		m_palette_val[offset + 0] = rgb_t(offvalue, offvalue, offvalue); // black
		m_palette_val[offset + 1] = rgb_t(onvalue, offvalue, offvalue); // red
		m_palette_val[offset + 2] = rgb_t(offvalue, onvalue, offvalue); // green
		m_palette_val[offset + 3] = rgb_t(onvalue, onvalue, offvalue); // red-green
		m_palette_val[offset + 4] = rgb_t(offvalue, offvalue, onvalue); // blue
		m_palette_val[offset + 5] = rgb_t(onvalue, offvalue, onvalue); // red-blue
		m_palette_val[offset + 6] = rgb_t(offvalue, onvalue, onvalue); // green-blue
		m_palette_val[offset + 7] = rgb_t(onvalue, onvalue, onvalue); // white
	}
}


//-------------------------------------------------
//  get_field_vpos - get scanline within field
//-------------------------------------------------

int uv201_device::get_field_vpos()
{
	int vpos = screen().vpos();

	if (vpos >= SCREEN_HEIGHT)
	{
		// even field
		vpos -= SCREEN_HEIGHT;
	}

	return vpos;
}


//-------------------------------------------------
//  get_field - get video field
//-------------------------------------------------

int uv201_device::get_field()
{
	return screen().vpos() < SCREEN_HEIGHT;
}


//-------------------------------------------------
//  set_y_interrupt - set Y interrupt timer
//-------------------------------------------------

void uv201_device::set_y_interrupt()
{
	int scanline = ((m_cmd & COMMAND_YINT_H_O) << 1) | m_y_int;

	m_timer_y_odd->adjust(screen().time_until_pos(scanline), 0, screen().frame_period());
	//m_timer_y_even->adjust(screen().time_until_pos(scanline + SCREEN_HEIGHT), 0, screen().frame_period());
}


//-------------------------------------------------
//  do_partial_update - update screen
//-------------------------------------------------

void uv201_device::do_partial_update()
{
	int vpos = screen().vpos();

	LOG("Partial screen update at scanline %u\n", vpos);

	screen().update_partial(vpos);
}


//-------------------------------------------------
//  read -
//-------------------------------------------------

uint8_t uv201_device::read(offs_t offset)
{
	uint8_t data = 0xff;

	switch (offset)
	{
	case REGISTER_X_FREEZE:
		data = m_freeze_x;

		LOG("X-Freeze %02x\n", data);
		break;

	case REGISTER_Y_FREEZE_LOW:
		data = m_freeze_y & 0xff;

		LOG("Y-Freeze Low %02x\n", data);
		break;

	case REGISTER_Y_FREEZE_HIGH:
		/*

		    bit     signal      description

		    0       Y-F8        Y freeze high order (MSB) bit
		    1       Y-C8        current Y counter high order (MSB) bit
		    2
		    3
		    4
		    5
		    6
		    7       O/_E        odd/even field

		*/

		data = (get_field() << 7) | (BIT(get_field_vpos(), 8) << 1) | BIT(m_freeze_y, 8);

		LOG("Y-Freeze High %02x\n", data);
		break;

	case REGISTER_CURRENT_Y_LOW:
		data = get_field_vpos() & 0xff;

		LOG("Current-Y Low %02x\n", data);
		break;

	default:
		if (offset < 0x90)
			data = m_ram[offset];
		else
			LOG("Unknown VLSI read from %02x!\n", offset);
	}

	return data;
}


//-------------------------------------------------
//  write -
//-------------------------------------------------

void uv201_device::write(offs_t offset, uint8_t data)
{
	switch (offset)
	{
	case REGISTER_Y_INTERRUPT:
		LOG("Y-Interrupt %02x\n", data);

		if (m_y_int != data)
		{
			m_y_int = data;
			set_y_interrupt();
		}
		break;

	case REGISTER_FINAL_MODIFIER:
		/*

		    bit     signal      description

		    0       RED         red
		    1       GREEN       green
		    2       BLUE        blue
		    3       INT 0       intensity 0
		    4       INT 1       intensity 1
		    5       not used
		    6       not used
		    7       not used

		*/

		LOG("Final Modifier %02x\n", data);

		do_partial_update();
		m_fmod = data & 0x1f;
		break;

	case REGISTER_BACKGROUND:
		/*

		    bit     signal      description

		    0       RED         red
		    1       GREEN       green
		    2       BLUE        blue
		    3       INT 0       intensity 0
		    4       INT 1       intensity 1
		    5       not used
		    6       not used
		    7       not used

		*/

		LOG("Background %02x\n", data);

		do_partial_update();
		m_bg = data & 0x1f;
		break;

	case REGISTER_COMMAND:
		/*

		    bit     signal      description

		    0       X-ZM        X zoom
		    1       FRZ         freeze
		    2       ENB         video enable
		    3       INT         interrupt enable
		    4       KBD         general purpose output
		    5       Y-ZM        Y zoom
		    6       A/_B        list selection
		    7       YINT H.O.   Y COMMAND_INT register high order bit

		*/

		LOG("Command %02x\n", data);

		if (IS_CHANGED(COMMAND_YINT_H_O))
		{
			set_y_interrupt();
		}

		if (IS_CHANGED(COMMAND_A_B) || IS_CHANGED(COMMAND_Y_ZM) || IS_CHANGED(COMMAND_X_ZM))
		{
			do_partial_update();
		}

		m_cmd = data;
		break;

	default:
		if (offset < 0x90)
			m_ram[offset] = data;
		else
			logerror("Unknown VLSI write %02x to %02x!\n", data, offset);
	}
}


//-------------------------------------------------
//  ext_int_w - external interrupt write
//-------------------------------------------------

WRITE_LINE_MEMBER( uv201_device::ext_int_w )
{
	if (!state && (m_cmd & COMMAND_FRZ))
	{
		m_freeze_y = get_field_vpos();
		m_freeze_x = screen().hpos();
	}
}


//-------------------------------------------------
//  kbd_r - keyboard select read
//-------------------------------------------------

READ_LINE_MEMBER( uv201_device::kbd_r )
{
	return (m_cmd & COMMAND_KBD) ? 1 : 0;
}


//-------------------------------------------------
//  screen_update -
//-------------------------------------------------

uint32_t uv201_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
	bitmap.fill(rgb_t(0x00,0x00,0x00), cliprect);

	if (!(m_cmd & COMMAND_ENB))
	{
		return 0;
	}

	for (int y = 0; y < SCREEN_HEIGHT; y++)
	{
		for (int x = 0; x < VISAREA_WIDTH; x++)
		{
			int pixel = m_bg;
			DRAW_PIXEL(y, x);
		}
	}

	for (int i = 0; i < 16; i++)
	{
		uint8_t xy_hi = (m_cmd & COMMAND_A_B) ? RAM(RAM_XY_HI_A) : RAM(RAM_XY_HI_B);
		uint8_t y_lo = (m_cmd & COMMAND_A_B) ? RAM(RAM_Y_LO_A) : RAM(RAM_Y_LO_B);
		uint16_t y = (BIT(xy_hi, 7) << 8) | y_lo;
		int xord = xy_hi & 0x0f;

		uint8_t rp_hi_color = RAM_XORD(RAM_RP_HI_COLOR);
		uint8_t rp_lo = RAM_XORD(RAM_RP_LO);
		uint16_t rp = ((rp_hi_color << 8) | rp_lo) & 0x1fff;

		if (rp < 0x800) rp |= 0x2000;

		uint8_t dx_int_xcopy = RAM_XORD(RAM_DX_INT_XCOPY);
		int color = ((dx_int_xcopy & 0x60) >> 2) | (BIT(rp_hi_color, 5) << 2) | (BIT(rp_hi_color, 6) << 1) | (BIT(rp_hi_color, 7));
		uint8_t dx = dx_int_xcopy & 0x1f;
		uint8_t dy = RAM_XORD(RAM_DY);
		int xcopy = BIT(dx_int_xcopy, 7);
		uint8_t x = RAM_XORD(RAM_X);

		LOG("Object %u xord %u y %u x %u dy %u dx %u xcopy %u color %u rp %04x\n", i, xord, y, x, dy, dx, xcopy, color, rp);

		if (rp == 0) continue;
		if (y > SCREEN_HEIGHT) continue;

		for (int sy = 0; sy < dy; sy++)
		{
			for (int sx = 0; sx < dx; sx++)
			{
				uint8_t data = m_read_db(rp);

				for (int bit = 0; bit < 8; bit++)
				{
					int pixel = ((BIT(data, 7) ? color : m_bg) ^ m_fmod) & 0x1f;

					if (m_cmd & COMMAND_Y_ZM)
					{
						int scanline = y + (sy * 2);

						if (m_cmd & COMMAND_X_ZM)
						{
							int dot = (x * 2) + (sx * 16) + (bit * 2);

							DRAW_PIXEL(scanline, dot);
							DRAW_PIXEL(scanline, dot + 1);
							DRAW_PIXEL(scanline + 1, dot);
							DRAW_PIXEL(scanline + 1, dot + 1);
						}
						else
						{
							int dot = x + (sx * 8) + bit;

							DRAW_PIXEL(scanline, dot);
							DRAW_PIXEL(scanline + 1, dot);
						}
					}
					else
					{
						int scanline = y + sy;

						if (m_cmd & COMMAND_X_ZM)
						{
							int dot = (x * 2) + (sx * 16) + (bit * 2);

							DRAW_PIXEL(scanline, dot);
							DRAW_PIXEL(scanline, dot + 1);
						}
						else
						{
							int dot = x + (sx * 8) + bit;

							DRAW_PIXEL(scanline, dot);
						}
					}

					data <<= 1;
				}

				if (!xcopy) rp++;
			}

			if (xcopy) rp++;
		}
	}

	return 0;
}
