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

    IBM EGA CRT Controller emulation

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

#ifndef __CRTC_EGA__
#define __CRTC_EGA__

#include "emu.h"

#define CRTC_EGA_INTERFACE(name) \
	const crtc_ega_interface (name) =

#define MCFG_CRTC_EGA_ADD(_tag, _clock, _intrf) \
	MCFG_DEVICE_ADD(_tag, CRTC_EGA, _clock) \
	MCFG_DEVICE_CONFIG(_intrf)

#define MCFG_CRTC_EGA_SET_SCREEN MCFG_VIDEO_SET_SCREEN


class crtc_ega_device;

/* callback definitions */
typedef void * (*crtc_ega_begin_update_func)(crtc_ega_device *device, bitmap_ind16 &bitmap, const rectangle &cliprect);
#define CRTC_EGA_BEGIN_UPDATE(name) void *name(crtc_ega_device *device, bitmap_ind16 &bitmap, const rectangle &cliprect)

typedef void (*crtc_ega_update_row_func)(crtc_ega_device *device, bitmap_ind16 &bitmap,
										const rectangle &cliprect, UINT16 ma, UINT8 ra,
										UINT16 y, UINT8 x_count, INT8 cursor_x, void *param);
#define CRTC_EGA_UPDATE_ROW(name)       void name(crtc_ega_device *device, bitmap_ind16 &bitmap,    \
												const rectangle &cliprect, UINT16 ma, UINT8 ra,                 \
												UINT16 y, UINT8 x_count, INT8 cursor_x, void *param)

typedef void (*crtc_ega_end_update_func)(crtc_ega_device *device, bitmap_ind16 &bitmap, const rectangle &cliprect, void *param);
#define CRTC_EGA_END_UPDATE(name)       void name(crtc_ega_device *device, bitmap_ind16 &bitmap, const rectangle &cliprect, void *param)


/* interface */
struct crtc_ega_interface
{
	int m_hpixels_per_column;       /* number of pixels per video memory address */

	/* if specified, this gets called before any pixel update,
	   optionally return a pointer that will be passed to the
	   update and tear down callbacks */
	crtc_ega_begin_update_func      m_begin_update;

	/* this gets called for every row, the driver must output
	   x_count * hpixels_per_column pixels.
	   cursor_x indicates the character position where the cursor is, or -1
	   if there is no cursor on this row */
	crtc_ega_update_row_func        m_update_row;

	/* if specified, this gets called after all row updating is complete */
	crtc_ega_end_update_func            m_end_update;

	/* if specified, this gets called for every change of the disply enable signal */
	devcb_write_line    m_out_de_func;

	/* if specified, this gets called for every change of the HSYNC signal */
	devcb_write_line    m_out_hsync_func;

	/* if specified, this gets called for every change of the VSYNC signal */
	devcb_write_line    m_out_vsync_func;

	devcb_write_line    m_out_vblank_func;
};


class crtc_ega_device : public device_t,
						public device_video_interface,
						public crtc_ega_interface
{
public:
	crtc_ega_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock);

	/* select one of the registers for reading or writing */
	DECLARE_WRITE8_MEMBER( address_w );

	/* read from the currently selected register */
	DECLARE_READ8_MEMBER( register_r );

	/* write to the currently selected register */
	DECLARE_WRITE8_MEMBER( register_w );

	/* return the current value on the MA0-MA15 pins */
	UINT16 get_ma();

	/* return the current value on the RA0-RA4 pins */
	UINT8 get_ra();

	/* simulates the LO->HI clocking of the light pen pin */
	void assert_light_pen_input();

	/* set the clock of the chip */
	void set_clock(int clock);

	/* set number of pixels per video memory address */
	void set_hpixels_per_column(int hpixels_per_column);

	/* updates the screen -- this will call begin_update(), */
	/* followed by update_row() reapeatedly and after all row */
	/* updating is complete, end_update() */
	UINT32 screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);

protected:
	// device-level overrides
	virtual void device_config_complete();
	virtual void device_start();
	virtual void device_reset();
	virtual void device_post_load();
	virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr);

private:
	devcb_resolved_write_line   m_res_out_de_func;
	devcb_resolved_write_line   m_res_out_cur_func;
	devcb_resolved_write_line   m_res_out_hsync_func;
	devcb_resolved_write_line   m_res_out_vsync_func;
	devcb_resolved_write_line   m_res_out_vblank_func;

	/* ega/vga register file */
	UINT8   m_horiz_char_total; /* 0x00 */
	UINT8   m_horiz_disp;           /* 0x01 */
	UINT8   m_horiz_blank_start;    /* 0x02 */
	UINT8   m_horiz_blank_end;  /* 0x03/0x05 */
	UINT8   m_ena_vert_access;  /* 0x03 */
	UINT8   m_de_skew;          /* 0x03 */
	UINT8   m_horiz_retr_start; /* 0x04 */
	UINT8   m_horiz_retr_end;       /* 0x05 */
	UINT8   m_horiz_retr_skew;  /* 0x05 */
	UINT16  m_vert_total;           /* 0x06/0x07 */
	UINT8   m_preset_row_scan;  /* 0x08 */
	UINT8   m_byte_panning;     /* 0x08 */
	UINT8   m_max_ras_addr;     /* 0x09 */
	UINT8   m_scan_doubling;        /* 0x09 */
	UINT8   m_cursor_start_ras; /* 0x0a */
	UINT8   m_cursor_disable;       /* 0x0a */
	UINT8   m_cursor_end_ras;       /* 0x0b */
	UINT8   m_cursor_skew;      /* 0x0b */
	UINT16  m_disp_start_addr;  /* 0x0c/0x0d */
	UINT16  m_cursor_addr;      /* 0x0e/0x0f */
	UINT16  m_light_pen_addr;       /* 0x10/0x11 */
	UINT16  m_vert_retr_start;  /* 0x10/0x07 */
	UINT8   m_vert_retr_end;        /* 0x11 */
	UINT8   m_protect;          /* 0x11 */
	UINT8   m_bandwidth;            /* 0x11 */
	UINT16  m_vert_disp_end;        /* 0x12/0x07 */
	UINT8   m_offset;               /* 0x13 */
	UINT8   m_underline_loc;        /* 0x14 */
	UINT16  m_vert_blank_start; /* 0x15/0x07/0x09 */
	UINT8   m_vert_blank_end;       /* 0x16 */
	UINT8   m_mode_control;     /* 0x17 */
	UINT16  m_line_compare;     /* 0x18/0x07/0x09 */

	/* other internal state */
	UINT8   m_register_address_latch;
	bool    m_cursor_state; /* 0 = off, 1 = on */
	UINT8   m_cursor_blink_count;

	/* output signals */
	int     m_cur;
	int     m_hsync;
	int     m_vsync;
	int     m_vblank;
	int     m_de;

	/* internal counters */
	UINT8   m_character_counter;
	UINT8   m_hsync_width_counter;
	UINT16  m_line_counter;
	UINT8   m_raster_counter;
	UINT8   m_vsync_width_counter;
	bool    m_line_enable_ff;       /* Internal flip flop which is set when the line_counter is reset and reset when vert_disp is reached */
	UINT8   m_vsync_ff;
	UINT8   m_adjust_active;
	UINT16  m_line_address;
	INT16   m_cursor_x;

	/* timers */
	static const device_timer_id TIMER_LINE = 0;
	static const device_timer_id TIMER_DE_OFF = 1;
	static const device_timer_id TIMER_CUR_ON = 2;
	static const device_timer_id TIMER_CUR_OFF = 3;
	static const device_timer_id TIMER_HSYNC_ON = 4;
	static const device_timer_id TIMER_HSYNC_OFF = 5;
	static const device_timer_id TIMER_LIGHT_PEN_LATCH = 6;

	emu_timer *m_line_timer;
	emu_timer *m_de_off_timer;
	emu_timer *m_cur_on_timer;
	emu_timer *m_cur_off_timer;
	emu_timer *m_hsync_on_timer;
	emu_timer *m_hsync_off_timer;
	emu_timer *m_light_pen_latch_timer;

	/* computed values - do NOT state save these! */
	UINT16  m_horiz_pix_total;
	UINT16  m_vert_pix_total;
	UINT16  m_max_visible_x;
	UINT16  m_max_visible_y;
	UINT16  m_hsync_on_pos;
	UINT16  m_hsync_off_pos;
	UINT16  m_vsync_on_pos;
	UINT16  m_vsync_off_pos;
	UINT16  m_current_disp_addr;    /* the display address currently drawn */
	UINT8   m_light_pen_latched;
	bool    m_has_valid_parameters;

	void recompute_parameters(bool postload);
	void update_counters();
	void set_de(int state);
	void set_hsync(int state);
	void set_vsync(int state);
	void set_vblank(int state);
	void set_cur(int state);
	void handle_line_timer();
	void update_cursor_state();
};

extern const device_type CRTC_EGA;

#endif
