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

  video/odyssey2.c

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

#include "emu.h"
#include "includes/odyssey2.h"


#define COLLISION_SPRITE_0			0x01
#define COLLISION_SPRITE_1			0x02
#define COLLISION_SPRITE_2			0x04
#define COLLISION_SPRITE_3			0x08
#define COLLISION_VERTICAL_GRID		0x10
#define COLLISION_HORIZ_GRID_DOTS	0x20
#define COLLISION_EXTERNAL_UNUSED	0x40
#define COLLISION_CHARACTERS		0x80

/* character sprite colors
   dark grey, red, green, orange, blue, violet, light grey, white
   dark back / grid colors
   black, dark blue, dark green, light green, red, violet, orange, light grey
   light back / grid colors
   black, blue, green, light green, red, violet, orange, light grey */

const UINT8 odyssey2_colors[] =
{
	/* Background,Grid Dim */
	0x00,0x00,0x00,
	0x00,0x00,0xFF,   /* Blue */
	0x00,0x80,0x00,   /* DK Green */
	0xff,0x9b,0x60,
	0xCC,0x00,0x00,   /* Red */
	0xa9,0x80,0xff,
	0x82,0xfd,0xdb,
	0xFF,0xFF,0xFF,

	/* Background,Grid Bright */
	0x80,0x80,0x80,
	0x50,0xAE,0xFF,   /* Blue */
	0x00,0xFF,0x00,   /* Dk Green */
	0x82,0xfb,0xdb,   /* Lt Grey */
	0xEC,0x02,0x60,   /* Red */
	0xa9,0x80,0xff,   /* Violet */
	0xff,0x9b,0x60,   /* Orange */
	0xFF,0xFF,0xFF,

	/* Character,Sprite colors */
	0x71,0x71,0x71,   /* Dark Grey */
	0xFF,0x80,0x80,   /* Red */
	0x00,0xC0,0x00,   /* Green */
	0xff,0x9b,0x60,   /* Orange */
	0x50,0xAE,0xFF,   /* Blue */
	0xa9,0x80,0xff,   /* Violet */
	0x82,0xfb,0xdb,   /* Lt Grey */
	0xff,0xff,0xff    /* White */
};

static const UINT8 o2_shape[0x40][8]={
    { 0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00 }, // 0
    { 0x18,0x38,0x18,0x18,0x18,0x18,0x3C,0x00 },
    { 0x3C,0x66,0x0C,0x18,0x30,0x60,0x7E,0x00 },
    { 0x7C,0xC6,0x06,0x3C,0x06,0xC6,0x7C,0x00 },
    { 0xCC,0xCC,0xCC,0xFE,0x0C,0x0C,0x0C,0x00 },
    { 0xFE,0xC0,0xC0,0x7C,0x06,0xC6,0x7C,0x00 },
    { 0x7C,0xC6,0xC0,0xFC,0xC6,0xC6,0x7C,0x00 },
    { 0xFE,0x06,0x0C,0x18,0x30,0x60,0xC0,0x00 },
    { 0x7C,0xC6,0xC6,0x7C,0xC6,0xC6,0x7C,0x00 },
    { 0x7C,0xC6,0xC6,0x7E,0x06,0xC6,0x7C,0x00 },
    { 0x00,0x18,0x18,0x00,0x18,0x18,0x00,0x00 },
    { 0x18,0x7E,0x58,0x7E,0x1A,0x7E,0x18,0x00 },
    { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },
    { 0x3C,0x66,0x0C,0x18,0x18,0x00,0x18,0x00 },
    { 0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xFE,0x00 },
    { 0xFC,0xC6,0xC6,0xFC,0xC0,0xC0,0xC0,0x00 },
    { 0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00 },
    { 0xC6,0xC6,0xC6,0xD6,0xFE,0xEE,0xC6,0x00 },
    { 0xFE,0xC0,0xC0,0xF8,0xC0,0xC0,0xFE,0x00 },
    { 0xFC,0xC6,0xC6,0xFC,0xD8,0xCC,0xC6,0x00 },
    { 0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x00 },
    { 0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00 },
    { 0x3C,0x18,0x18,0x18,0x18,0x18,0x3C,0x00 },
    { 0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00 },
    { 0x7C,0xC6,0xC6,0xC6,0xDE,0xCC,0x76,0x00 },
    { 0x7C,0xC6,0xC0,0x7C,0x06,0xC6,0x7C,0x00 },
    { 0xFC,0xC6,0xC6,0xC6,0xC6,0xC6,0xFC,0x00 },
    { 0xFE,0xC0,0xC0,0xF8,0xC0,0xC0,0xC0,0x00 },
    { 0x7C,0xC6,0xC0,0xC0,0xCE,0xC6,0x7E,0x00 },
    { 0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0x00 },
    { 0x06,0x06,0x06,0x06,0x06,0xC6,0x7C,0x00 },
    { 0xC6,0xCC,0xD8,0xF0,0xD8,0xCC,0xC6,0x00 },
    { 0x38,0x6C,0xC6,0xC6,0xFE,0xC6,0xC6,0x00 },
    { 0x7E,0x06,0x0C,0x18,0x30,0x60,0x7E,0x00 },
    { 0xC6,0xC6,0x6C,0x38,0x6C,0xC6,0xC6,0x00 },
    { 0x7C,0xC6,0xC0,0xC0,0xC0,0xC6,0x7C,0x00 },
    { 0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x00 },
    { 0xFC,0xC6,0xC6,0xFC,0xC6,0xC6,0xFC,0x00 },
    { 0xC6,0xEE,0xFE,0xD6,0xC6,0xC6,0xC6,0x00 },
    { 0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00 },
    { 0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00 },
    { 0x00,0x66,0x3C,0x18,0x3C,0x66,0x00,0x00 },
    { 0x00,0x18,0x00,0x7E,0x00,0x18,0x00,0x00 },
    { 0x00,0x00,0x7E,0x00,0x7E,0x00,0x00,0x00 },
    { 0x66,0x66,0x66,0x3C,0x18,0x18,0x18,0x00 },
    { 0xC6,0xE6,0xF6,0xFE,0xDE,0xCE,0xC6,0x00 },
    { 0x03,0x06,0x0C,0x18,0x30,0x60,0xC0,0x00 },
    { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00 },
    { 0xCE,0xDB,0xDB,0xDB,0xDB,0xDB,0xCE,0x00 },
    { 0x00,0x00,0x3C,0x7E,0x7E,0x7E,0x3C,0x00 },
    { 0x1C,0x1C,0x18,0x1E,0x18,0x18,0x1C,0x00 },
    { 0x1C,0x1C,0x18,0x1E,0x18,0x34,0x26,0x00 },
    { 0x38,0x38,0x18,0x78,0x18,0x2C,0x64,0x00 },
    { 0x38,0x38,0x18,0x78,0x18,0x18,0x38,0x00 },
    { 0x00,0x18,0x0C,0xFE,0x0C,0x18,0x00,0x00 },
    { 0x18,0x3C,0x7E,0xFF,0xFF,0x18,0x18,0x00 },
    { 0x03,0x07,0x0F,0x1F,0x3F,0x7F,0xFF,0x00 },
    { 0xC0,0xE0,0xF0,0xF8,0xFC,0xFE,0xFF,0x00 },
    { 0x38,0x38,0x12,0xFE,0xB8,0x28,0x6C,0x00 },
    { 0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x00 },
    { 0x00,0x00,0x0C,0x08,0x08,0x7F,0x3E,0x00 },
    { 0x00,0x03,0x63,0xFF,0xFF,0x18,0x08,0x00 },
    { 0x00,0x00,0x00,0x10,0x38,0xFF,0x7E,0x00 }
};




PALETTE_INIT( odyssey2 )
{
	int i;

	for ( i = 0; i < 24; i++ )
	{
		palette_set_color_rgb( machine, i, odyssey2_colors[i*3], odyssey2_colors[i*3+1], odyssey2_colors[i*3+2] );
	}
}

READ8_HANDLER( odyssey2_video_r )
{
	odyssey2_state *state = space->machine().driver_data<odyssey2_state>();
    UINT8 data = 0;

    switch (offset)
    {
        case 0xa1:
			data = state->m_control_status;
			state->m_iff = 0;
			cputag_set_input_line(space->machine(), "maincpu", 0, CLEAR_LINE);
			state->m_control_status &= ~ 0x08;
			if ( space->machine().primary_screen->hpos() < I824X_START_ACTIVE_SCAN || space->machine().primary_screen->hpos() > I824X_END_ACTIVE_SCAN )
			{
				data |= 1;
			}

            break;

        case 0xa2:
			data = state->m_collision_status;
			state->m_collision_status = 0;

            break;

        case 0xa4:

            if ((state->m_o2_vdc.s.control & VDC_CONTROL_REG_STROBE_XY))
                state->m_y_beam_pos = space->machine().primary_screen->vpos() - state->m_start_vpos;

            data = state->m_y_beam_pos;

            break;


        case 0xa5:

            if ((state->m_o2_vdc.s.control & VDC_CONTROL_REG_STROBE_XY))
			{
                state->m_x_beam_pos = space->machine().primary_screen->hpos();
				if ( state->m_x_beam_pos < I824X_START_ACTIVE_SCAN )
				{
					state->m_x_beam_pos = state->m_x_beam_pos - I824X_START_ACTIVE_SCAN + I824X_LINE_CLOCKS;
				}
				else
				{
					state->m_x_beam_pos = state->m_x_beam_pos - I824X_START_ACTIVE_SCAN;
				}
			}

            data = state->m_x_beam_pos;

            break;

        default:
            data = state->m_o2_vdc.reg[offset];
    }

    return data;
}

WRITE8_HANDLER( odyssey2_video_w )
{
	odyssey2_state *state = space->machine().driver_data<odyssey2_state>();
	/* Update the sound */
	if( offset >= 0xa7 && offset <= 0xaa )
		state->m_sh_channel->update();

    if (offset == 0xa0)
    {
        if (    state->m_o2_vdc.s.control & VDC_CONTROL_REG_STROBE_XY
             && !(data & VDC_CONTROL_REG_STROBE_XY))
        {
            /* Toggling strobe bit, tuck away values */
            state->m_x_beam_pos = space->machine().primary_screen->hpos();
			if ( state->m_x_beam_pos < I824X_START_ACTIVE_SCAN )
			{
				state->m_x_beam_pos = state->m_x_beam_pos - I824X_START_ACTIVE_SCAN + 228;
			}
			else
			{
				state->m_x_beam_pos = state->m_x_beam_pos - I824X_START_ACTIVE_SCAN;
			}

            state->m_y_beam_pos = space->machine().primary_screen->vpos() - state->m_start_vpos;

            /* This is wrong but more games work with it, TODO: Figure
             * out correct change.  Maybe update the screen here??
             * It seems what happens is 0x0 is written to $A0 just before
             * VLBANK (video update) so no screen updates happen.
             */

            return;
        }
    }

    state->m_o2_vdc.reg[offset] = data;
}

WRITE8_HANDLER ( odyssey2_lum_w )
{
	odyssey2_state *state = space->machine().driver_data<odyssey2_state>();
	state->m_lum = data;
}

READ8_HANDLER( odyssey2_t1_r )
{
	odyssey2_state *state = space->machine().driver_data<odyssey2_state>();
	if ( space->machine().primary_screen->vpos() > state->m_start_vpos && space->machine().primary_screen->vpos() < state->m_start_vblank )
	{
		if ( space->machine().primary_screen->hpos() >= I824X_START_ACTIVE_SCAN && space->machine().primary_screen->hpos() < I824X_END_ACTIVE_SCAN )
		{
			return 1;
		}
	}
	return 0;
}

static TIMER_CALLBACK( i824x_scanline_callback )
{
	odyssey2_state *state = machine.driver_data<odyssey2_state>();
	UINT8	collision_map[160];
	int		vpos = machine.primary_screen->vpos();

	if ( vpos < state->m_start_vpos )
		return;

	if ( vpos == state->m_start_vpos )
	{
		state->m_control_status &= ~0x08;
	}

	if ( vpos < state->m_start_vblank )
	{
		rectangle rect;
		//static const int sprite_width[4] = { 8, 8, 8, 8 };
		int i;

		state->m_control_status &= ~ 0x01;

		/* Draw a line */
		rect.min_y = rect.max_y = vpos;
		rect.min_x = I824X_START_ACTIVE_SCAN;
		rect.max_x = I824X_END_ACTIVE_SCAN - 1;
		bitmap_fill( state->m_tmp_bitmap, &rect , ( (state->m_o2_vdc.s.color >> 3) & 0x7 ) | ( ( state->m_lum << 3 ) ^ 0x08 ));

		/* Clear collision map */
		memset( collision_map, 0, sizeof( collision_map ) );

		/* Display grid if enabled */
		if ( state->m_o2_vdc.s.control & 0x08 )
		{
			UINT16	color = ( state->m_o2_vdc.s.color & 7 ) | ( ( state->m_o2_vdc.s.color >> 3 ) & 0x08 ) | ( ( state->m_lum << 3 ) ^ 0x08 );
			int		x_grid_offset = 8;
			int 	y_grid_offset = 24;
			int		width = 16;
			int		height = 24;
			int		w = ( state->m_o2_vdc.s.control & 0x80 ) ? width : 2;
			int		j, k, y;

			/* Draw horizontal part of grid */
			for ( j = 1, y = 0; y < 9; y++, j <<= 1 )
			{
				if ( y_grid_offset + y * height <= ( vpos - state->m_start_vpos ) && ( vpos - state->m_start_vpos ) < y_grid_offset + y * height + 3 )
				{
					for ( i = 0; i < 9; i++ )
					{
						if ( ( state->m_o2_vdc.s.hgrid[0][i] & j ) || ( state->m_o2_vdc.s.hgrid[1][i] & ( j >> 8 ) ) )
						{
							for ( k = 0; k < width + 2; k++ )
							{
								int px = x_grid_offset + i * width + k;
								collision_map[ px ] |= COLLISION_HORIZ_GRID_DOTS;
								*BITMAP_ADDR16( state->m_tmp_bitmap, vpos, I824X_START_ACTIVE_SCAN + px ) = color;
							}
						}
					}
				}
			}

			/* Draw vertical part of grid */
			for( j = 1, y = 0; y < 8; y++, j <<= 1 )
			{
				if ( y_grid_offset + y * height <= ( vpos - state->m_start_vpos ) && ( vpos - state->m_start_vpos ) < y_grid_offset + ( y + 1 ) * height )
				{
					for ( i = 0; i < 10; i++ )
					{
						if ( state->m_o2_vdc.s.vgrid[i] & j )
						{
							for ( k = 0; k < w; k++ )
							{
								int px = x_grid_offset + i * width + k;

								/* Check if we collide with an already drawn source object */
								if ( collision_map[ px ] & state->m_o2_vdc.s.collision )
								{
									state->m_collision_status |= COLLISION_VERTICAL_GRID;
								}
								/* Check if an already drawn object would collide with us */
								if ( COLLISION_VERTICAL_GRID & state->m_o2_vdc.s.collision && collision_map[ px ] )
								{
									state->m_collision_status |= collision_map[ px ];
								}
								collision_map[ px ] |= COLLISION_VERTICAL_GRID;
								*BITMAP_ADDR16( state->m_tmp_bitmap, vpos, I824X_START_ACTIVE_SCAN + px ) = color;
							}
						}
					}
				}
			}
		}

		/* Display objects if enabled */
		if ( state->m_o2_vdc.s.control & 0x20 )
		{
			/* Regular foreground objects */
			for ( i = 0; i < ARRAY_LENGTH( state->m_o2_vdc.s.foreground ); i++ )
			{
				int	y = state->m_o2_vdc.s.foreground[i].y;
				int	height = 8 - ( ( ( y >> 1 ) + state->m_o2_vdc.s.foreground[i].ptr ) & 7 );

				if ( y <= ( vpos - state->m_start_vpos ) && ( vpos - state->m_start_vpos ) < y + height * 2 )
				{
					UINT16	color = 16 + ( ( state->m_o2_vdc.s.foreground[i].color & 0x0E ) >> 1 );
					int		offset = ( state->m_o2_vdc.s.foreground[i].ptr | ( ( state->m_o2_vdc.s.foreground[i].color & 0x01 ) << 8 ) ) + ( y >> 1 ) + ( ( vpos - state->m_start_vpos - y ) >> 1 );
					UINT8	chr = ((char*)o2_shape)[ offset & 0x1FF ];
					int		x = state->m_o2_vdc.s.foreground[i].x;
					UINT8	m;

					for ( m = 0x80; m > 0; m >>= 1, x++ )
					{
						if ( chr & m )
						{
							if ( x >= 0 && x < 160 )
							{
								/* Check if we collide with an already drawn source object */
								if ( collision_map[ x ] & state->m_o2_vdc.s.collision )
								{
									state->m_collision_status |= COLLISION_CHARACTERS;
								}
								/* Check if an already drawn object would collide with us */
								if ( COLLISION_CHARACTERS & state->m_o2_vdc.s.collision && collision_map[ x ] )
								{
									state->m_collision_status |= collision_map[ x ];
								}
								collision_map[ x ] |= COLLISION_CHARACTERS;
								*BITMAP_ADDR16( state->m_tmp_bitmap, vpos, I824X_START_ACTIVE_SCAN + x ) = color;
							}
						}
					}
				}
			}

			/* Quad objects */
			for ( i = 0; i < ARRAY_LENGTH( state->m_o2_vdc.s.quad ); i++ )
			{
				int y = state->m_o2_vdc.s.quad[i].single[0].y;
				int height = 8;

				if ( y <= ( vpos - state->m_start_vpos ) && ( vpos - state->m_start_vpos ) < y + height * 2 )
				{
					int	x = state->m_o2_vdc.s.quad[i].single[0].x;
					int j;

					for ( j = 0; j < ARRAY_LENGTH( state->m_o2_vdc.s.quad[0].single ); j++, x += 8 )
					{
						int		char_height = 8 - ( ( ( y >> 1 ) + state->m_o2_vdc.s.quad[i].single[j].ptr ) & 7 );
						if ( y <= ( vpos - state->m_start_vpos ) && ( vpos - state->m_start_vpos ) < y + char_height * 2 )
						{
							UINT16 color = 16 + ( ( state->m_o2_vdc.s.quad[i].single[j].color & 0x0E ) >> 1 );
							int	offset = ( state->m_o2_vdc.s.quad[i].single[j].ptr | ( ( state->m_o2_vdc.s.quad[i].single[j].color & 0x01 ) << 8 ) ) + ( y >> 1 ) + ( ( vpos - state->m_start_vpos - y ) >> 1 );
							UINT8	chr = ((char*)o2_shape)[ offset & 0x1FF ];
							UINT8	m;
							for ( m = 0x80; m > 0; m >>= 1, x++ )
							{
								if ( chr & m )
								{
									if ( x >= 0 && x < 160 )
									{
										/* Check if we collide with an already drawn source object */
										if ( collision_map[ x ] & state->m_o2_vdc.s.collision )
										{
											state->m_collision_status |= COLLISION_CHARACTERS;
										}
										/* Check if an already drawn object would collide with us */
										if ( COLLISION_CHARACTERS & state->m_o2_vdc.s.collision && collision_map[ x ] )
										{
											state->m_collision_status |= collision_map[ x ];
										}
										collision_map[ x ] |= COLLISION_CHARACTERS;
										*BITMAP_ADDR16( state->m_tmp_bitmap, vpos, I824X_START_ACTIVE_SCAN + x ) = color;
									}
								}
							}
						}
						else
						{
							x += 8;
						}
					}
				}
			}

			/* Sprites */
			for ( i = 0; i < ARRAY_LENGTH( state->m_o2_vdc.s.sprites ); i++ )
			{
				int y = state->m_o2_vdc.s.sprites[i].y;
				int height = 8;
				if ( state->m_o2_vdc.s.sprites[i].color & 4 )
				{
					/* Zoomed sprite */
					//sprite_width[i] = 16;
					if ( y <= ( vpos - state->m_start_vpos ) && ( vpos - state->m_start_vpos ) < y + height * 4 )
					{
						UINT16 color = 16 + ( ( state->m_o2_vdc.s.sprites[i].color >> 3 ) & 0x07 );
						UINT8	chr = state->m_o2_vdc.s.shape[i][ ( ( vpos - state->m_start_vpos - y ) >> 2 ) ];
						int		x = state->m_o2_vdc.s.sprites[i].x;
						UINT8	m;

						for ( m = 0x01; m > 0; m <<= 1, x += 2 )
						{
							if ( chr & m )
							{
								if ( x >= 0 && x < 160 )
								{
									/* Check if we collide with an already drawn source object */
									if ( collision_map[ x ] & state->m_o2_vdc.s.collision )
									{
										state->m_collision_status |= ( 1 << i );
									}
									/* Check if an already drawn object would collide with us */
									if ( ( 1 << i ) & state->m_o2_vdc.s.collision && collision_map[ x ] )
									{
										state->m_collision_status |= collision_map[ x ];
									}
									collision_map[ x ] |= ( 1 << i );
									*BITMAP_ADDR16( state->m_tmp_bitmap, vpos, I824X_START_ACTIVE_SCAN + x ) = color;
								}
								if ( x >= -1 && x < 159 )
								{
									/* Check if we collide with an already drawn source object */
									if ( collision_map[ x ] & state->m_o2_vdc.s.collision )
									{
										state->m_collision_status |= ( 1 << i );
									}
									/* Check if an already drawn object would collide with us */
									if ( ( 1 << i ) & state->m_o2_vdc.s.collision && collision_map[ x ] )
									{
										state->m_collision_status |= collision_map[ x ];
									}
									collision_map[ x ] |= ( 1 << i );
									*BITMAP_ADDR16( state->m_tmp_bitmap, vpos, I824X_START_ACTIVE_SCAN + x + 1 ) = color;
								}
							}
						}
					}
				}
				else
				{
					/* Regular sprite */
					if ( y <= ( vpos - state->m_start_vpos ) && ( vpos - state->m_start_vpos ) < y + height * 2 )
					{
						UINT16 color = 16 + ( ( state->m_o2_vdc.s.sprites[i].color >> 3 ) & 0x07 );
						UINT8	chr = state->m_o2_vdc.s.shape[i][ ( ( vpos - state->m_start_vpos - y ) >> 1 ) ];
						int		x = state->m_o2_vdc.s.sprites[i].x;
						UINT8	m;

						for ( m = 0x01; m > 0; m <<= 1, x++ )
						{
							if ( chr & m )
							{
								if ( x >= 0 && x < 160 )
								{
									/* Check if we collide with an already drawn source object */
									if ( collision_map[ x ] & state->m_o2_vdc.s.collision )
									{
										state->m_collision_status |= ( 1 << i );
									}
									/* Check if an already drawn object would collide with us */
									if ( ( 1 << i ) & state->m_o2_vdc.s.collision && collision_map[ x ] )
									{
										state->m_collision_status |= collision_map[ x ];
									}
									collision_map[ x ] |= ( 1 << i );
									*BITMAP_ADDR16( state->m_tmp_bitmap, vpos, I824X_START_ACTIVE_SCAN + x ) = color;
								}
							}
						}
					}
				}
			}
		}
	}

	/* Check for start of VBlank */
	if ( vpos == state->m_start_vblank )
	{
		state->m_control_status |= 0x08;
		if ( ! state->m_iff )
		{
			cputag_set_input_line(machine, "maincpu", 0, ASSERT_LINE);
			state->m_iff = 1;
		}
	}
}

static TIMER_CALLBACK( i824x_hblank_callback )
{
	odyssey2_state *state = machine.driver_data<odyssey2_state>();
	int vpos = machine.primary_screen->vpos();

	if ( vpos < state->m_start_vpos - 1 )
		return;

	if ( vpos < state->m_start_vblank - 1 )
	{
		state->m_control_status |= 0x01;
	}
}

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

  Start the video hardware emulation.

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

VIDEO_START( odyssey2 )
{
	odyssey2_state *state = machine.driver_data<odyssey2_state>();
	screen_device *screen = machine.first_screen();
	int width = screen->width();
	int height = screen->height();

	memset(state->m_o2_vdc.reg, 0, 0x100);

	state->m_o2_snd_shift[0] = state->m_o2_snd_shift[1] = 0;
	state->m_x_beam_pos = 0;
	state->m_y_beam_pos = 0;
	state->m_control_status = 0;
	state->m_collision_status = 0;
	state->m_iff = 0;
	state->m_start_vpos = 0;
	state->m_start_vblank = 0;
	state->m_lum = 0;
	state->m_lfsr = 0;

	state->m_o2_snd_shift[0] = machine.sample_rate() / 983;
	state->m_o2_snd_shift[1] = machine.sample_rate() / 3933;

	state->m_start_vpos = I824X_START_Y;
	state->m_start_vblank = I824X_START_Y + I824X_SCREEN_HEIGHT;
	state->m_control_status = 0;
	state->m_iff = 0;

	state->m_tmp_bitmap = auto_bitmap_alloc( machine, width, height, screen->format() );

	state->m_i824x_line_timer = machine.scheduler().timer_alloc(FUNC(i824x_scanline_callback));
	state->m_i824x_line_timer->adjust( machine.primary_screen->time_until_pos(1, I824X_START_ACTIVE_SCAN ), 0,  machine.primary_screen->scan_period() );

	state->m_i824x_hblank_timer = machine.scheduler().timer_alloc(FUNC(i824x_hblank_callback));
	state->m_i824x_hblank_timer->adjust( machine.primary_screen->time_until_pos(1, I824X_END_ACTIVE_SCAN + 18 ), 0, machine.primary_screen->scan_period() );
}

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

  Refresh the video screen

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

SCREEN_UPDATE( odyssey2 )
{
	odyssey2_state *state = screen->machine().driver_data<odyssey2_state>();
	copybitmap( bitmap, state->m_tmp_bitmap, 0, 0, 0, 0, cliprect );

	return 0;
}

static DEVICE_START( odyssey2_sound )
{
	odyssey2_state *state = device->machine().driver_data<odyssey2_state>();
	state->m_sh_channel = device->machine().sound().stream_alloc(*device, 0, 1, device->clock()/(I824X_LINE_CLOCKS*4), 0, odyssey2_sh_update );
}


DEVICE_GET_INFO( odyssey2_sound )
{
	switch (state)
	{
		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:							info->start = DEVICE_START_NAME(odyssey2_sound);	break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case DEVINFO_STR_NAME:							strcpy(info->s, "P8244/P8245");				break;
		case DEVINFO_STR_SOURCE_FILE:					strcpy(info->s, __FILE__);						break;
	}
}

STREAM_UPDATE( odyssey2_sh_update )
{
	odyssey2_state *state = device->machine().driver_data<odyssey2_state>();
	UINT32 signal;
	int ii;
	int period;
	stream_sample_t *buffer = outputs[0];

	/* Generate the signal */
	signal = state->m_o2_vdc.s.shift3 | (state->m_o2_vdc.s.shift2 << 8) | (state->m_o2_vdc.s.shift1 << 16);

	if( state->m_o2_vdc.s.sound & 0x80 )	/* Sound is enabled */
	{
		for( ii = 0; ii < samples; ii++, buffer++ )
		{
			*buffer = 0;
			*buffer = signal & 0x1;
			period = (state->m_o2_vdc.s.sound & 0x20) ? 1 : 4;
			if( ++state->m_sh_count >= period )
			{
				state->m_sh_count = 0;
				signal >>= 1;
				/* Loop sound */
				if( state->m_o2_vdc.s.sound & 0x40 )
				{
					signal |= *buffer << 23;
				}
				/* Noise poly is : L=16 W=2 10000000000100001 */
				state->m_lfsr = ( state->m_lfsr >> 1 ) | ( ( ( state->m_lfsr & 0x01 ) ^ ( ( state->m_lfsr & 0x800 ) >> 11 ) ) << 15 );
				if ( ! state->m_lfsr )
				{
					state->m_lfsr = 0xFFFF;
				}
				/* Check if noise should be applied */
				if ( state->m_o2_vdc.s.sound & 0x10 )
				{
					*buffer |= ( state->m_lfsr & 0x01 );
				}
				state->m_o2_vdc.s.shift3 = signal & 0xFF;
				state->m_o2_vdc.s.shift2 = ( signal >> 8 ) & 0xFF;
				state->m_o2_vdc.s.shift1 = ( signal >> 16 ) & 0xFF;
			}

			/* Throw an interrupt if enabled */
			if( state->m_o2_vdc.s.control & 0x4 )
			{
				cputag_set_input_line(device->machine(), "maincpu", 1, HOLD_LINE); /* Is this right? */
			}

			/* Adjust volume */
			*buffer *= state->m_o2_vdc.s.sound & 0xf;
			/* Pump the volume up */
			*buffer <<= 10;
		}
	}
	else
	{
		/* Sound disabled, so clear the buffer */
		for( ii = 0; ii < samples; ii++, buffer++ )
			*buffer = 0;
	}
}

/*
    Thomson EF9340/EF9341 extra chips in the g7400
 */

#ifdef UNUSED_FUNCTION
INLINE UINT16 ef9341_get_c_addr( odyssey2_state *state )
{
	if ( ( state->m_ef9341.Y & 0x0C ) == 0x0C )
	{
		return 0x318 | ( ( state->m_ef9341.X & 0x38 ) << 2 ) | ( state->m_ef9341.X & 0x07 );
	}
	if ( state->m_ef9341.X & 0x20 )
	{
		return 0x300 | ( ( state->m_ef9341.Y & 0x07 ) << 5 ) | ( state->m_ef9341.Y & 0x18 ) | ( state->m_ef9341.X & 0x07 );
	}
	return ( state->m_ef9341.Y << 5 ) | state->m_ef9341.X;
}

INLINE void ef9341_inc_c( odyssey2_state *state )
{
	state->m_ef9341.X++;
	if ( state->m_ef9341.X >= 40 )
	{
		state->m_ef9341.Y = ( state->m_ef9341.Y + 1 ) % 24;
	}
}

void odyssey2_ef9341_w( running_machine &machine, int command, int b, UINT8 data )
{
	odyssey2_state *state = machine.driver_data<odyssey2_state>();
	logerror("ef9341 %s write, t%s, data %02X\n", command ? "command" : "data", b ? "B" : "A", data );

	if ( command )
	{
		if ( b )
		{
			state->m_ef9341.TB = data;
			state->m_ef9341.busy = 0x80;
			switch( state->m_ef9341.TB & 0xE0 )
			{
			case 0x00:	/* Begin row */
				state->m_ef9341.X = 0;
				state->m_ef9341.Y = state->m_ef9341.TA & 0x1F;
				break;
			case 0x20:	/* Load Y */
				state->m_ef9341.Y = state->m_ef9341.TA & 0x1F;
				break;
			case 0x40:	/* Load X */
				state->m_ef9341.X = state->m_ef9341.TA & 0x3F;
				break;
			case 0x60:	/* INC C */
				ef9341_inc_c(state);
				break;
			case 0x80:	/* Load M */
				state->m_ef9341.M = state->m_ef9341.TA;
				break;
			case 0xA0:	/* Load R */
				state->m_ef9341.R = state->m_ef9341.TA;
				break;
			case 0xC0:	/* Load Y0 */
				state->m_ef9341.Y0 = state->m_ef9341.TA & 0x3F;
				break;
			}
			state->m_ef9341.busy = 0;
		}
		else
		{
			state->m_ef9341.TA = data;
		}
	}
	else
	{
		if ( b )
		{
			state->m_ef9341.TB = data;
			state->m_ef9341.busy = 0x80;
			switch ( state->m_ef9341.M & 0xE0 )
			{
			case 0x00:	/* Write */
				state->m_ef9341.ram[ ef9341_get_c_addr(state) ] = state->m_ef9341.TB;
				ef9341_inc_c(state);
				break;
			case 0x20:	/* Read */
				logerror("ef9341 unimplemented data action %02X\n", state->m_ef9341.M & 0xE0 );
				ef9341_inc_c(state);
				break;
			case 0x40:	/* Write without increment */
			case 0x60:	/* Read without increment */
			case 0x80:	/* Write slice */
			case 0xA0:	/* Read slice */
				logerror("ef9341 unimplemented data action %02X\n", state->m_ef9341.M & 0xE0 );
				break;
			}
			state->m_ef9341.busy = 0;
		}
		else
		{
			state->m_ef9341.TA = data;
		}
	}
}

UINT8 odyssey2_ef9341_r( running_machine &machine, int command, int b )
{
	odyssey2_state *state = machine.driver_data<odyssey2_state>();
	UINT8	data = 0xFF;

	logerror("ef9341 %s read, t%s\n", command ? "command" : "data", b ? "B" : "A" );
	if ( command )
	{
		if ( b )
		{
			data = 0xFF;
		}
		else
		{
			data = state->m_ef9341.busy;
		}
	}
	else
	{
		if ( b )
		{
			data = state->m_ef9341.TB;
			state->m_ef9341.busy = 0x80;
		}
		else
		{
			data = state->m_ef9341.TA;
		}
	}
	return data;
}
#endif

DEFINE_LEGACY_SOUND_DEVICE(ODYSSEY2, odyssey2_sound);
