/*  Namco System NA1/2 Video Hardware */

/*
TODO:
- dynamic screen resolution (changes between emeralda test mode and normal game)
- non-shadow pixels for sprites flagged to enable shadows have bad colors
*/

#include "driver.h"
#include "namcona1.h"

#define NAMCONA1_NUM_TILEMAPS 4

/* public variables */
UINT16 *namcona1_vreg;
UINT16 *namcona1_scroll;
UINT16 *namcona1_workram;
//UINT16 *namcona1_sparevram;
//UINT16 *namcona1_pixmap;

/* private variables */
static UINT16 *shaperam;
static UINT16 *cgram;
static tilemap *roz_tilemap;
static int roz_palette;
static tilemap *bg_tilemap[NAMCONA1_NUM_TILEMAPS];
static int tilemap_palette_bank[NAMCONA1_NUM_TILEMAPS];
static int palette_is_dirty;

static void tilemap_get_info(
	running_machine *machine,
	tile_data *tileinfo,
	int tile_index,
	const UINT16 *tilemap_videoram,
	int tilemap_color,
	int use_4bpp_gfx )
{
	UINT16 *source;
	static UINT8 mask_data[8];

	int data = tilemap_videoram[tile_index];
	int tile = data&0xfff;
	int gfx;

	if( use_4bpp_gfx )
	{
		gfx = 1;
		tilemap_color *= 0x10;
		tilemap_color += (data & 0x7000)>>12;
	}
	else
	{
		gfx = 0;
	}

	if( data & 0x8000 )
	{
		SET_TILE_INFO( gfx,tile,tilemap_color,TILE_FORCE_LAYER0 );
	}
	else
	{
		SET_TILE_INFO( gfx,tile,tilemap_color,0 );
		if (ENDIANNESS_NATIVE == ENDIANNESS_BIG)
			tileinfo->mask_data = (UINT8 *)(shaperam+4*tile);
		else
		{
			source = shaperam+4*tile;
			mask_data[0] = source[0]>>8;
			mask_data[1] = source[0]&0xff;
			mask_data[2] = source[1]>>8;
			mask_data[3] = source[1]&0xff;
			mask_data[4] = source[2]>>8;
			mask_data[5] = source[2]&0xff;
			mask_data[6] = source[3]>>8;
			mask_data[7] = source[3]&0xff;
			tileinfo->mask_data = mask_data;
		}
	}
} /* tilemap_get_info */

static TILE_GET_INFO( tilemap_get_info0 ){ tilemap_get_info(machine,tileinfo,tile_index,0*0x1000+videoram16,tilemap_palette_bank[0],namcona1_vreg[0xbc/2]&1); }
static TILE_GET_INFO( tilemap_get_info1 ){ tilemap_get_info(machine,tileinfo,tile_index,1*0x1000+videoram16,tilemap_palette_bank[1],namcona1_vreg[0xbc/2]&2); }
static TILE_GET_INFO( tilemap_get_info2 ){ tilemap_get_info(machine,tileinfo,tile_index,2*0x1000+videoram16,tilemap_palette_bank[2],namcona1_vreg[0xbc/2]&4); }
static TILE_GET_INFO( tilemap_get_info3 ){ tilemap_get_info(machine,tileinfo,tile_index,3*0x1000+videoram16,tilemap_palette_bank[3],namcona1_vreg[0xbc/2]&8); }

static TILE_GET_INFO( roz_get_info )
{
	/* each logical tile is constructed from 4*4 normal tiles */
	int tilemap_color = roz_palette;
	int use_4bpp_gfx = namcona1_vreg[0xbc/2]&16; /* ? */
	int c = tile_index%0x40;
	int r = tile_index/0x40;
	int data = videoram16[0x8000/2+(r/4)*0x40+c/4]&0xfbf; /* mask out bit 0x40 - patch for Emeraldia Japan */
	int tile = (data+(c%4)+(r%4)*0x40)&0xfff;
	int gfx = use_4bpp_gfx;
	if( use_4bpp_gfx )
	{
		tilemap_color *= 0x10;
		tilemap_color += (data & 0x7000)>>12;
	}
	if( data & 0x8000 )
	{
		SET_TILE_INFO( gfx,tile,tilemap_color,TILE_FORCE_LAYER0 );
	}
	else
	{
		UINT8 *mask_data = (UINT8 *)(shaperam+4*tile);

		if (ENDIANNESS_NATIVE == ENDIANNESS_LITTLE)
		{
			UINT16 *source = (UINT16 *)mask_data;
			static UINT8 conv_data[9];
			conv_data[0] = source[0]>>8;
			conv_data[1] = source[0]&0xff;
			conv_data[2] = source[1]>>8;
			conv_data[3] = source[1]&0xff;
			conv_data[4] = source[2]>>8;
			conv_data[5] = source[2]&0xff;
			conv_data[6] = source[3]>>8;
			conv_data[7] = source[3]&0xff;
			mask_data = conv_data;
		}
		SET_TILE_INFO( gfx,tile,tilemap_color,0 );
		tileinfo->mask_data = mask_data;
	}
} /* roz_get_info */

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

WRITE16_HANDLER( namcona1_videoram_w )
{
	COMBINE_DATA( &videoram16[offset] );
	if( offset<0x8000/2 )
	{
		tilemap_mark_tile_dirty( bg_tilemap[offset/0x1000], offset&0xfff );
	}
	else if( offset<0xa000/2 )
	{
		tilemap_mark_all_tiles_dirty( roz_tilemap );
	}
} /* namcona1_videoram_w */

READ16_HANDLER( namcona1_videoram_r )
{
	return videoram16[offset];
} /* namcona1_videoram_r */

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

static void
UpdatePalette(running_machine *machine, int offset )
{
	UINT16 data = paletteram16[offset]; /* -RRRRRGG GGGBBBBB */
	/**
     * sprites can be configured to use an alternate interpretation of palette ram
     * (used in-game in Emeraldia)
     *
     * RRRGGGBB RRRGGGBB
     */
	int r = (((data&0x00e0)>>5)+((data&0xe000)>>13)*2)*0xff/(0x7*3);
	int g = (((data&0x001c)>>2)+((data&0x1c00)>>10)*2)*0xff/(0x7*3);
	int b = (((data&0x0003)>>0)+((data&0x0300)>>8)*2)*0xff/(0x3*3);
	palette_set_color_rgb( machine, offset+0x1000, r, g, b);

	palette_set_color_rgb( machine, offset, pal5bit(data >> 10), pal5bit(data >> 5), pal5bit(data >> 0));
} /* namcona1_paletteram_w */

READ16_HANDLER( namcona1_paletteram_r )
{
	return paletteram16[offset];
} /* namcona1_paletteram_r */

WRITE16_HANDLER( namcona1_paletteram_w )
{
	COMBINE_DATA( &paletteram16[offset] );
	if( namcona1_vreg[0x8e/2] )
	{ /* graphics enabled; update palette immediately */
		UpdatePalette(space->machine, offset );
	}
	else
	{
		palette_is_dirty = 1;
	}
}

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

static const gfx_layout shape_layout =
{
	8,8,
	0x1000,
	1,
	{ 0 },
	{ 0,1,2,3,4,5,6,7 },
#ifdef LSB_FIRST
	{ 8*1,8*0,8*3,8*2,8*5,8*4,8*7,8*6 },
#else
	{ 8*0,8*1,8*2,8*3,8*4,8*5,8*6,8*7 },
#endif
	8*8
}; /* shape_layout */

static const gfx_layout cg_layout_8bpp =
{
	8,8,
	0x1000,
	8, /* 8BPP */
	{ 0,1,2,3,4,5,6,7 },
#ifdef LSB_FIRST
	{ 8*1,8*0,8*3,8*2,8*5,8*4,8*7,8*6 },
#else
	{ 8*0,8*1,8*2,8*3,8*4,8*5,8*6,8*7 },
#endif
	{ 64*0,64*1,64*2,64*3,64*4,64*5,64*6,64*7 },
	64*8
}; /* cg_layout_8bpp */

static const gfx_layout cg_layout_4bpp =
{
	8,8,
	0x1000,
	4, /* 4BPP */
	{ 4,5,6,7 },
#ifdef LSB_FIRST
	{ 8*1,8*0,8*3,8*2,8*5,8*4,8*7,8*6 },
#else
	{ 8*0,8*1,8*2,8*3,8*4,8*5,8*6,8*7 },
#endif
	{ 64*0,64*1,64*2,64*3,64*4,64*5,64*6,64*7 },
	64*8
}; /* cg_layout_4bpp */

READ16_HANDLER( namcona1_gfxram_r )
{
	UINT16 type = namcona1_vreg[0x0c/2];
	if( type == 0x03 )
	{
		if( offset<0x4000 )
		{
			return shaperam[offset];
		}
	}
	else if( type == 0x02 )
	{
		return cgram[offset];
	}
	return 0x0000;
} /* namcona1_gfxram_r */

WRITE16_HANDLER( namcona1_gfxram_w )
{
	UINT16 type = namcona1_vreg[0x0c/2];
	UINT16 old_word;

	if( type == 0x03 )
	{
		if( offset<0x4000 )
		{
			old_word = shaperam[offset];
			COMBINE_DATA( &shaperam[offset] );
			if( shaperam[offset]!=old_word )
				gfx_element_mark_dirty(space->machine->gfx[2], offset/4);
		}
	}
	else if( type == 0x02 )
	{
		old_word = cgram[offset];
		COMBINE_DATA( &cgram[offset] );
		if( cgram[offset]!=old_word )
		{
			gfx_element_mark_dirty(space->machine->gfx[0], offset/0x20);
			gfx_element_mark_dirty(space->machine->gfx[1], offset/0x20);
		}
	}
} /* namcona1_gfxram_w */

static void UpdateGfx(running_machine *machine)
{
} /* UpdateGfx */

VIDEO_START( namcona1 )
{
	static const tile_get_info_func get_info[4] = { tilemap_get_info0, tilemap_get_info1, tilemap_get_info2, tilemap_get_info3 };
	int i;

	roz_tilemap = tilemap_create( machine, roz_get_info, tilemap_scan_rows, 8,8,64,64 );
	roz_palette = -1;

	for( i=0; i<NAMCONA1_NUM_TILEMAPS; i++ )
	{
		bg_tilemap[i] = tilemap_create( machine, get_info[i], tilemap_scan_rows, 8,8,64,64 );
		tilemap_palette_bank[i] = -1;
	}

	shaperam		     = auto_alloc_array(machine, UINT16, 0x2000*4/2 );
	cgram			     = auto_alloc_array(machine, UINT16, 0x1000*0x40/2 );

	machine->gfx[0] = gfx_element_alloc( machine, &cg_layout_8bpp, (UINT8 *)cgram, machine->config->total_colors/256, 0 );
	machine->gfx[1] = gfx_element_alloc( machine, &cg_layout_4bpp, (UINT8 *)cgram, machine->config->total_colors/16, 0 );
	machine->gfx[2] = gfx_element_alloc( machine, &shape_layout, (UINT8 *)shaperam, machine->config->total_colors/2, 0 );

} /* namcona1_vh_start */

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

static void pdraw_tile(running_machine *machine,
		bitmap_t *dest_bmp,
		const rectangle *clip,
		UINT32 code,
		int color,
		int sx, int sy,
		int flipx, int flipy,
		int priority,
		int bShadow,
		int bOpaque,
		int gfx_region )
{
	const gfx_element *gfx = machine->gfx[gfx_region];
	const gfx_element *mask = machine->gfx[2];

	int pal_base = gfx->color_base + gfx->color_granularity * (color % gfx->total_colors);
	const UINT8 *source_base = gfx_element_get_data(gfx, (code % gfx->total_elements));
	const UINT8 *mask_base = gfx_element_get_data(mask, (code % mask->total_elements));

	int sprite_screen_height = ((1<<16)*gfx->height+0x8000)>>16;
	int sprite_screen_width  = ((1<<16)*gfx->width+0x8000)>>16;

	if (sprite_screen_width && sprite_screen_height)
	{
		/* compute sprite increment per screen pixel */
		int dx = (gfx->width<<16)/sprite_screen_width;
		int dy = (gfx->height<<16)/sprite_screen_height;

		int ex = sx+sprite_screen_width;
		int ey = sy+sprite_screen_height;

		int x_index_base;
		int y_index;

		if( flipx )
		{
			x_index_base = (sprite_screen_width-1)*dx;
			dx = -dx;
		}
		else
		{
			x_index_base = 0;
		}

		if( flipy )
		{
			y_index = (sprite_screen_height-1)*dy;
			dy = -dy;
		}
		else
		{
			y_index = 0;
		}

		if( clip )
		{
			if( sx < clip->min_x)
			{ /* clip left */
				int pixels = clip->min_x-sx;
				sx += pixels;
				x_index_base += pixels*dx;
			}
			if( sy < clip->min_y )
			{ /* clip top */
				int pixels = clip->min_y-sy;
				sy += pixels;
				y_index += pixels*dy;
			}
			/* NS 980211 - fixed incorrect clipping */
			if( ex > clip->max_x+1 )
			{ /* clip right */
				int pixels = ex-clip->max_x-1;
				ex -= pixels;
			}
			if( ey > clip->max_y+1 )
			{ /* clip bottom */
				int pixels = ey-clip->max_y-1;
				ey -= pixels;
			}
		}

		if( ex>sx )
		{ /* skip if inner loop doesn't draw anything */
			int y;

			for( y=sy; y<ey; y++ )
			{
				const UINT8 *source = source_base + (y_index>>16) * gfx->line_modulo;
				const UINT8 *mask_addr = mask_base + (y_index>>16) * mask->line_modulo;
				UINT16 *dest = BITMAP_ADDR16(dest_bmp, y, 0);
				UINT8 *pri = BITMAP_ADDR8(priority_bitmap, y, 0);

				int x, x_index = x_index_base;
				for( x=sx; x<ex; x++ )
				{
					if( bOpaque )
					{
						if (pri[x] <= priority)
						{
							int c = source[x_index>>16];
							dest[x] = pal_base + c;
						}

						pri[x] = 0xff;
					}
					else
					{
						/* sprite pixel is opaque */
						if( mask_addr[x_index>>16] != 0 )
						{
							if (pri[x] <= priority)
							{
								int c = source[x_index>>16];

								/* render a shadow only if the sprites color is $F (8bpp) or $FF (4bpp) */
								if( bShadow )
								{
									if( (gfx_region == 0 && color == 0x0f) ||
									    (gfx_region == 1 && color == 0xff) )
									{
										pen_t *palette_shadow_table = machine->shadow_table;
										dest[x] = palette_shadow_table[dest[x]];
									}
									else
									{
										dest[x] = pal_base + c + 0x1000;
									}
								}
								else
								{
									dest[x] = pal_base + c;
								}
							}
							pri[x] = 0xff;
						}
					}

					x_index += dx;
				}

				y_index += dy;
			}
		}
	}
} /* pdraw_tile */

static void draw_sprites(running_machine *machine, bitmap_t *bitmap, const rectangle *cliprect)
{
	int which;
	const UINT16 *source = spriteram16;
	UINT16 sprite_control;
	UINT16 ypos,tile,color,xpos;
	int priority;
	int width,height;
	int flipy,flipx;
	int row,col;
	int sx,sy;

	sprite_control = namcona1_vreg[0x22/2];
	if( sprite_control&1 ) source += 0x400; /* alternate spriteram bank */

	for( which=0; which<0x100; which++ )
	{ /* max 256 sprites */
		int bpp4,palbase;
		ypos	= source[0];	/* FHHH---Y YYYYYYYY    flipy, height, ypos */
		tile	= source[1];	/* O???TTTT TTTTTTTT    opaque tile */
		color	= source[2];	/* FSWWOOOO CCCCBPPP    flipx, shadow, width, color offset for 4bpp, color, 4bbp - 8bpp mode, pri*/
		xpos	= source[3];	/* -------X XXXXXXXX    xpos */

		priority = color&0x7;
		width = ((color>>12)&0x3)+1;
		height = ((ypos>>12)&0x7)+1;
		flipy = ypos&0x8000;
		flipx = color&0x8000;

		if( color&8 )
		{
			palbase = ((color>>4)&0xf) * 0x10 + ((color & 0xf00) >> 8);
			bpp4 = 1;
		}
		else
		{
			palbase = (color>>4)&0xf;
			bpp4 = 0;
		}

		for( row=0; row<height; row++ )
		{
			sy = (ypos&0x1ff)-30+32;
			if( flipy )
			{
				sy += (height-1-row)*8;
			}
			else
			{
				sy += row*8;
			}
			sy = ((sy+8)&0x1ff)-8;

			for( col=0; col<width; col++ )
			{
				sx = (xpos&0x1ff)-10;
				if( flipx )
				{
					sx += (width-1-col)*8;
				}
				else
				{
					sx+=col*8;
				}
				sx = ((sx+16)&0x1ff)-8;

				pdraw_tile(machine,
					bitmap,
					cliprect,
					(tile & 0xfff) + row*64+col,
					palbase,
					sx,sy,flipx,flipy,
					priority,
					color & 0x4000, /* shadow */
					tile & 0x8000, /* opaque */
					bpp4 );

			} /* next col */
		} /* next row */
		source += 4;
	}
} /* draw_sprites */

static void draw_pixel_line( UINT16 *pDest, UINT8 *pPri, UINT16 *pSource, const pen_t *paldata )
{
	int x;
	for( x=0; x<38*8; x+=2 )
	{
		UINT16 data = *pSource++;
		pPri[x+0] = 0xff;
		pPri[x+1] = 0xff;
		pDest[x+0] = paldata[data>>8];
		pDest[x+1] = paldata[data&0xff];
	} /* next x */
} /* draw_pixel_line */

static void draw_background(running_machine *machine, bitmap_t *bitmap, const rectangle *cliprect, int which, int primask )
{
	/*          scrollx lineselect
     *  tmap0   ffe000  ffe200
     *  tmap1   ffe400  ffe600
     *  tmap2   ffe800  ffea00
     *  tmap3   ffec00  ffee00
     */
	int xadjust = 0x3a - which*2;
	const UINT16 *scroll = namcona1_scroll+0x200*which;
	int line;
	UINT16 xdata, ydata;
	int scrollx, scrolly;
	rectangle clip;
	const pen_t *paldata;
	gfx_element *pGfx;

	pGfx = machine->gfx[0];
	paldata = &machine->pens[pGfx->color_base + pGfx->color_granularity * tilemap_palette_bank[which]];

	/* draw one scanline at a time */
	clip.min_x = cliprect->min_x;
	clip.max_x = cliprect->max_x;
	scrollx = 0;
	scrolly = 0;
	for( line=0; line<256; line++ )
	{
		clip.min_y = line;
		clip.max_y = line;
		xdata = scroll[line];
		if( xdata )
		{
			/* screenwise linescroll */
			scrollx = xadjust+xdata;
		}
		ydata = scroll[line+0x100];
		if( ydata&0x4000 )
		{
			/* line select: dword offset from 0xff000 or tilemap source line */
			scrolly = (ydata - line)&0x1ff;
		}

		if (line >= cliprect->min_y && line <= cliprect->max_y)
		{
			if( xdata == 0xc001 )
			{
				/* This is a simplification, but produces the correct behavior for the only game that uses this
                 * feature, Numan Athletics.
                 */
				draw_pixel_line(
					BITMAP_ADDR16(bitmap, line, 0),
					BITMAP_ADDR8(priority_bitmap, line, 0),
					videoram16 + ydata + 25,
					paldata );
			}
			else
			{
				if(which == NAMCONA1_NUM_TILEMAPS )
				{
					int incxx = ((INT16)namcona1_vreg[0xc0/2])<<8;
					int incxy = ((INT16)namcona1_vreg[0xc2/2])<<8;
					int incyx = ((INT16)namcona1_vreg[0xc4/2])<<8;
					int incyy = ((INT16)namcona1_vreg[0xc6/2])<<8;
					INT16 xoffset = namcona1_vreg[0xc8/2];
					INT16 yoffset = namcona1_vreg[0xca/2];
					int dx = 46; /* horizontal adjust */
					int dy = -8; /* vertical adjust */
					UINT32 startx = (xoffset<<12)+incxx*dx+incyx*dy;
					UINT32 starty = (yoffset<<12)+incxy*dx+incyy*dy;
					tilemap_draw_roz_primask(bitmap, &clip, roz_tilemap,
						startx, starty, incxx, incxy, incyx, incyy, 0, 0, primask, 0);
				}
				else
				{
					tilemap_set_scrollx( bg_tilemap[which], 0, scrollx );
					tilemap_set_scrolly( bg_tilemap[which], 0, scrolly );
					tilemap_draw_primask( bitmap, &clip, bg_tilemap[which], 0, primask, 0 );
				}
			}
		}
	}
} /* draw_background */

VIDEO_UPDATE( namcona1 )
{
	int which;
	int priority;

	/* int flipscreen = namcona1_vreg[0x98/2]; (TBA) */

	if( namcona1_vreg[0x8e/2] )
	{ /* gfx enabled */
		if( palette_is_dirty )
		{
			/* palette updates are delayed when graphics are disabled */
			for( which=0; which<0x1000; which++ )
			{
				UpdatePalette(screen->machine, which );
			}
			palette_is_dirty = 0;
		}
		UpdateGfx(screen->machine);
		for( which=0; which<NAMCONA1_NUM_TILEMAPS; which++ )
		{
			int tilemap_color = namcona1_vreg[0xb0/2+(which&3)]&0xf;
			if( tilemap_color!=tilemap_palette_bank[which] )
			{
				tilemap_mark_all_tiles_dirty( bg_tilemap[which] );
				tilemap_palette_bank[which] = tilemap_color;
			}
		} /* next tilemap */

		{ /* ROZ tilemap */
			int color = namcona1_vreg[0xba/2]&0xf;
			if( color != roz_palette )
			{
				tilemap_mark_all_tiles_dirty( roz_tilemap );
				roz_palette = color;
			}
		}

		bitmap_fill( priority_bitmap,cliprect ,0);

		bitmap_fill( bitmap, cliprect , 0xff); /* background color? */

		for( priority = 0; priority<8; priority++ )
		{
			for( which=4; which>=0; which-- )
			{
				int pri;
				if( which==4 )
				{
					pri = namcona1_vreg[0xa0/2+5]&0x7;
				}
				else
				{
					pri = namcona1_vreg[0xa0/2+which]&0x7;
				}
				if( pri == priority )
				{
					draw_background(screen->machine,bitmap,cliprect,which,priority);
				}
			} /* next tilemap */
		} /* next priority level */

		draw_sprites(screen->machine,bitmap,cliprect);
	} /* gfx enabled */
	return 0;
}

/*
roz bad in emeraldaj - blit param?

$efff20: sprite control: 0x3a,0x3e,0x3f
            bit 0x01 selects spriteram bank

               0    2    4    6    8    a    c    e
$efff00:    src0 src1 src2 dst0 dst1 dst2 BANK [src
$efff10:    src] [dst dst] #BYT BLIT eINT 001f 0001
$efff20:    003f 003f IACK ---- ---- ---- ---- ----
...
$efff80:    0050 0170 0020 0100 0170 POS? 0000 GFXE
$efff90:    0000 0001 0002 0003 FLIP ---- ---- ----
$efffa0:    PRI  PRI  PRI  PRI  ???? PRI  00c0 ----     priority (0..7)
$efffb0:    CLR  CLR  CLR  CLR  0001 CLR  BPP  ----     color (0..f), bpp flag per layer
$efffc0:    RZXX RZXY RZYX RZYY RZX0 RZY0 0044 ----     ROZ


Emeralda:   0048 0177 0020 0100 0000 00f0 0000 0001     in-game
            0050 0170 0020 0100 0000 00f0 0000 0001     self test

NumanAth:   0050 0170 0020 0100 0170 00d8 0000 0001     in-game


*/
