/* Cygne
*
* Copyright notice for this file:
*  Copyright (C) 2002 Dox dox@space.pl
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "system.h"
#include <cstring>

namespace MDFN_IEN_WSWAN
{
	GFX::GFX()
		:LayerEnabled(7) // 1 = bg, 2 = fg, 4 = sprite
	{
		SetPixelFormat();
	}


	void GFX::PaletteRAMWrite(uint32 ws_offset, uint8 data)
	{
		ws_offset=(ws_offset&0xfffe)-0xfe00;
		wsCols[(ws_offset>>1)>>4][(ws_offset>>1)&15] = sys->memory.wsRAM[ws_offset+0xfe00] | ((sys->memory.wsRAM[ws_offset+0xfe01]&0x0f) << 8);
	}

	void GFX::Write(uint32 A, uint8 V)
	{
		if(A >= 0x1C && A <= 0x1F)
		{
			wsColors[(A - 0x1C) * 2 + 0] = 0xF - (V & 0xf);
			wsColors[(A - 0x1C) * 2 + 1] = 0xF - (V >> 4);
		}
		else if(A >= 0x20 && A <= 0x3F)
		{
			wsMonoPal[(A - 0x20) >> 1][((A & 0x1) << 1) + 0] = V&7;
			wsMonoPal[(A - 0x20) >> 1][((A & 0x1) << 1) | 1] = (V>>4)&7;
		}
		else switch(A)
		{
		case 0x00: DispControl = V; break;
		case 0x01: BGColor = V; break;
		case 0x03: LineCompare = V; break;
		case 0x04: SPRBase = V & 0x3F; break;
		case 0x05: SpriteStart = V; break;
		case 0x06: SpriteCount = V; break;
		case 0x07: FGBGLoc = V; break;
		case 0x08: FGx0 = V; break;
		case 0x09: FGy0 = V; break;
		case 0x0A: FGx1 = V; break;
		case 0x0B: FGy1 = V; break;
		case 0x0C: SPRx0 = V; break;
		case 0x0D: SPRy0 = V; break;
		case 0x0E: SPRx1 = V; break;
		case 0x0F: SPRy1 = V; break;
		case 0x10: BGXScroll = V; break;
		case 0x11: BGYScroll = V; break;
		case 0x12: FGXScroll = V; break;
		case 0x13: FGYScroll = V; break;

		case 0x14: LCDControl = V; break; //    if((!(wsIO[0x14]&1))&&(data&1)) { wsLine=0; }break; /* LCD off ??*/
		case 0x15: LCDIcons = V; break;

		case 0x60: VideoMode = V; 
			SetVideo(V>>5, false); 
			//printf("VideoMode: %02x, %02x\n", V, V >> 5);
			break;

		case 0xa2: if((V & 0x01) && !(BTimerControl & 0x01))
					   HBCounter = HBTimerPeriod;
			if((V & 0x04) && !(BTimerControl & 0x04))
				VBCounter = VBTimerPeriod;
			BTimerControl = V; 
			//printf("%04x:%02x\n", A, V);
			break;
		case 0xa4: HBTimerPeriod &= 0xFF00; HBTimerPeriod |= (V << 0); /*printf("%04x:%02x, %d\n", A, V, wsLine);*/ break;
		case 0xa5: HBTimerPeriod &= 0x00FF; HBTimerPeriod |= (V << 8); HBCounter = HBTimerPeriod; /*printf("%04x:%02x, %d\n", A, V, wsLine);*/ break;
		case 0xa6: VBTimerPeriod &= 0xFF00; VBTimerPeriod |= (V << 0); /*printf("%04x:%02x, %d\n", A, V, wsLine);*/ break;
		case 0xa7: VBTimerPeriod &= 0x00FF; VBTimerPeriod |= (V << 8); VBCounter = VBTimerPeriod; /*printf("%04x:%02x, %d\n", A, V, wsLine);*/ break;
			//default: printf("%04x:%02x\n", A, V); break;
		}
	}

	uint8 GFX::Read(uint32 A)
	{
		if(A >= 0x1C && A <= 0x1F)
		{
			uint8 ret = 0;

			ret |= 0xF - wsColors[(A - 0x1C) * 2 + 0];
			ret |= (0xF - wsColors[(A - 0x1C) * 2 + 1]) << 4;

			return(ret);
		}
		else if(A >= 0x20 && A <= 0x3F)
		{
			uint8 ret = wsMonoPal[(A - 0x20) >> 1][((A & 0x1) << 1) + 0] | (wsMonoPal[(A - 0x20) >> 1][((A & 0x1) << 1) | 1] << 4);

			return(ret);
		}
		else switch(A)
		{
		case 0x00: return(DispControl);
		case 0x01: return(BGColor);
		case 0x02: return(wsLine);
		case 0x03: return(LineCompare);
		case 0x04: return(SPRBase);
		case 0x05: return(SpriteStart);
		case 0x06: return(SpriteCount);
		case 0x07: return(FGBGLoc);
		case 0x08: return(FGx0); break;
		case 0x09: return(FGy0); break;
		case 0x0A: return(FGx1); break;
		case 0x0B: return(FGy1); break;
		case 0x0C: return(SPRx0); break;
		case 0x0D: return(SPRy0); break;
		case 0x0E: return(SPRx1); break;
		case 0x0F: return(SPRy1); break;
		case 0x10: return(BGXScroll);
		case 0x11: return(BGYScroll);
		case 0x12: return(FGXScroll);
		case 0x13: return(FGYScroll);
		case 0x14: return(LCDControl);
		case 0x15: return(LCDIcons);
		case 0x60: return(VideoMode);
		case 0xa0: return(wsc ? 0x87 : 0x86);
		case 0xa2: return(BTimerControl);
		case 0xa4: return((HBTimerPeriod >> 0) & 0xFF);
		case 0xa5: return((HBTimerPeriod >> 8) & 0xFF);
		case 0xa6: return((VBTimerPeriod >> 0) & 0xFF);
		case 0xa7: return((VBTimerPeriod >> 8) & 0xFF);
		case 0xa8: /*printf("%04x\n", A);*/ return((HBCounter >> 0) & 0xFF);
		case 0xa9: /*printf("%04x\n", A);*/ return((HBCounter >> 8) & 0xFF);
		case 0xaa: /*printf("%04x\n", A);*/ return((VBCounter >> 0) & 0xFF);
		case 0xab: /*printf("%04x\n", A);*/ return((VBCounter >> 8) & 0xFF);
		default: return(0);
			//default: printf("GfxRead:  %04x\n", A); return(0);
		}
	}

	bool GFX::ExecuteLine(uint32 *surface, bool skip)
	{
		bool ret = false; // true if we finish frame here

		if(wsLine < 144)
		{
			if(!skip)
			{
				if (sys->rotate)
					Scanline(surface + 223 * 144 + wsLine);
				else
					Scanline(surface + wsLine * 224);
			}
		}

		sys->memory.CheckSoundDMA();

		// Update sprite data table
		if(wsLine == 142)
		{
			SpriteCountCache = SpriteCount;

			if(SpriteCountCache > 0x80)
				SpriteCountCache = 0x80;

			memcpy(SpriteTable, &sys->memory.wsRAM[(SPRBase << 9) + (SpriteStart << 2)], SpriteCountCache << 2);
		}

		if(wsLine == 144)
		{
			ret = true;
			sys->interrupt.DoInterrupt(WSINT_VBLANK);
			//printf("VBlank: %d\n", wsLine);
		}


		if(HBCounter && (BTimerControl & 0x01))
		{
			HBCounter--;
			if(!HBCounter)
			{
				// Loop mode?
				if(BTimerControl & 0x02)
					HBCounter = HBTimerPeriod;
				sys->interrupt.DoInterrupt(WSINT_HBLANK_TIMER);
			}
		}

		// CPU ==========================
		sys->cpu.execute(224);
		// CPU ==========================

		wsLine = (wsLine + 1) % 159;
		if(wsLine == LineCompare)
		{
			sys->interrupt.DoInterrupt(WSINT_LINE_HIT);
			//printf("Line hit: %d\n", wsLine);
		}

		// CPU ==========================
		sys->cpu.execute(32);
		// CPU ==========================

		sys->rtc.Clock(256);

		if(!wsLine)
		{
			if(VBCounter && (BTimerControl & 0x04))
			{
				VBCounter--;
				if(!VBCounter)
				{
					if(BTimerControl & 0x08) // Loop mode?
						VBCounter = VBTimerPeriod;

					sys->interrupt.DoInterrupt(WSINT_VBLANK_TIMER);
				}
			}
			wsLine = 0;
		}

		return ret;
	}

	void GFX::SetLayerEnableMask(uint32 mask)
	{
		LayerEnabled = mask;
	}

	void GFX::SetPixelFormat()
	{
		for(int r = 0; r < 16; r++)
		{
			for(int g = 0; g < 16; g++)
			{
				for(int b = 0; b < 16; b++)
				{
					uint32 neo_r, neo_g, neo_b;

					neo_r = r * 17;
					neo_g = g * 17;
					neo_b = b * 17;

					ColorMap[(r << 8) | (g << 4) | (b << 0)] = 0xff000000 | neo_r << 16 | neo_g << 8 | neo_b << 0;
				}

				for(int i = 0; i < 16; i++)
				{
					uint32 neo_r, neo_g, neo_b;

					neo_r = (i) * 17;
					neo_g = (i) * 17;
					neo_b = (i) * 17;

					ColorMapG[i] = 0xff000000 | neo_r << 16 | neo_g << 8 | neo_b << 0;
				}
			}
		}
	}

	void GFX::Scanline(uint32 *target)
	{
		uint32		start_tile_n,map_a,startindex,adrbuf,b1,b2,j,t,l;
		char		ys2;
		uint8		b_bg[256];
		uint8		b_bg_pal[256];
		const uint8 *ram = sys->memory.wsRAM;

		if(!wsVMode)
			memset(b_bg, wsColors[BGColor&0xF]&0xF, 256);
		else
		{
			memset(&b_bg[0], BGColor & 0xF, 256);
			memset(&b_bg_pal[0], (BGColor>>4)  & 0xF, 256);
		}
		start_tile_n=(wsLine+BGYScroll)&0xff;/*First line*/
		map_a=(((uint32)(FGBGLoc&0xF))<<11)+((start_tile_n&0xfff8)<<3);
		startindex = BGXScroll >> 3; /*First tile in row*/
		adrbuf = 7-(BGXScroll&7); /*Pixel in tile*/

		if((DispControl & 0x01) && (LayerEnabled & 0x01)) /*BG layer*/
		{
			for(t=0;t<29;t++)
			{
				b1=ram[map_a+(startindex<<1)];
				b2=ram[map_a+(startindex<<1)+1];
				uint32 palette=(b2>>1)&15;
				b2=(b2<<8)|b1;
				GetTile(b2&0x1ff,start_tile_n&7,b2&0x8000,b2&0x4000,b2&0x2000);

				if(wsVMode)
				{
					if(wsVMode & 0x2)
					{
						for(int x = 0; x < 8; x++)
							if(wsTileRow[x])
							{
								b_bg[adrbuf + x] = wsTileRow[x];
								b_bg_pal[adrbuf + x] = palette;
							}
					}
					else
					{
						for(int x = 0; x < 8; x++)
							if(wsTileRow[x] || !(palette & 0x4))
							{
								b_bg[adrbuf + x] = wsTileRow[x];
								b_bg_pal[adrbuf + x] = palette;
							}
					}
				}
				else
				{
					for(int x = 0; x < 8; x++)
						if(wsTileRow[x] || !(palette & 4))
						{
							b_bg[adrbuf + x] = wsColors[wsMonoPal[palette][wsTileRow[x]]];
						}
				}
				adrbuf += 8;
				startindex=(startindex + 1)&31;
			} // end for(t = 0 ...
		} // End BG layer drawing

		if((DispControl & 0x02) && (LayerEnabled & 0x02))/*FG layer*/
		{
			uint8 windowtype = DispControl&0x30;
			bool in_window[256 + 8*2];

			if(windowtype)
			{
				memset(in_window, 0, sizeof(in_window));

				if(windowtype == 0x20) // Display FG only inside window
				{
					if((wsLine >= FGy0) && (wsLine < FGy1))
						for(j = FGx0; j <= FGx1 && j < 224; j++)
							in_window[7 + j] = 1;
				}
				else if(windowtype == 0x30) // Display FG only outside window
				{
					for(j = 0; j < 224; j++)
					{
						if(!(j >= FGx0 && j < FGx1) || !((wsLine >= FGy0) && (wsLine < FGy1)))
							in_window[7 + j] = 1;
					}
				}
				else
				{
					Debug::puts("Bad windowtype??");
				}
			}
			else
				memset(in_window, 1, sizeof(in_window));

			start_tile_n=(wsLine+FGYScroll)&0xff;
			map_a=(((uint32)((FGBGLoc>>4)&0xF))<<11)+((start_tile_n>>3)<<6);
			startindex = FGXScroll >> 3;
			adrbuf = 7-(FGXScroll&7);

			for(t=0; t<29; t++)
			{
				b1=ram[map_a+(startindex<<1)];
				b2=ram[map_a+(startindex<<1)+1];
				uint32 palette=(b2>>1)&15;
				b2=(b2<<8)|b1;
				GetTile(b2&0x1ff,start_tile_n&7,b2&0x8000,b2&0x4000,b2&0x2000);

				if(wsVMode)
				{
					if(wsVMode & 0x2)
						for(int x = 0; x < 8; x++)
						{
							if(wsTileRow[x] && in_window[adrbuf + x])
							{
								b_bg[adrbuf + x] = wsTileRow[x] | 0x10;
								b_bg_pal[adrbuf + x] = palette;
							}
						}
					else
						for(int x = 0; x < 8; x++)
						{
							if((wsTileRow[x] || !(palette & 0x4)) && in_window[adrbuf + x])
							{
								b_bg[adrbuf + x] = wsTileRow[x] | 0x10;
								b_bg_pal[adrbuf + x] = palette;
							}
						}
				}
				else
				{
					for(int x = 0; x < 8; x++)
						if((wsTileRow[x] || !(palette & 4)) && in_window[adrbuf + x])
						{
							b_bg[adrbuf + x] = wsColors[wsMonoPal[palette][wsTileRow[x]]] | 0x10;
						}
				}
				adrbuf += 8;
				startindex=(startindex + 1)&31;
			} // end for(t = 0 ...

		} // end FG drawing

		if((DispControl & 0x04) && SpriteCountCache && (LayerEnabled & 0x04))/*Sprites*/
		{
			int xs,ts,as,ys,ysx,h;
			bool in_window[256 + 8*2];

			if(DispControl & 0x08)
			{
				memset(in_window, 0, sizeof(in_window));
				if((wsLine >= SPRy0) && (wsLine < SPRy1))
					for(j = SPRx0; j < SPRx1 && j < 256; j++)
						in_window[7 + j] = 1;
			}
			else
				memset(in_window, 1, sizeof(in_window));

			for(h = SpriteCountCache - 1; h >= 0; h--)
			{
				ts = SpriteTable[h][0];
				as = SpriteTable[h][1];
				ysx = SpriteTable[h][2];
				ys2 = (int8)SpriteTable[h][2];
				xs = SpriteTable[h][3];

				if(xs >= 249) xs -= 256;

				if(ysx > 150) 
					ys = ys2;
				else 
					ys = ysx;

				ys = wsLine - ys;

				if(ys >= 0 && ys < 8 && xs < 224)
				{
					uint32 palette = ((as >> 1) & 0x7);

					ts |= (as&1) << 8;
					GetTile(ts, ys, as & 0x80, as & 0x40, 0);

					if(wsVMode)
					{
						if(wsVMode & 0x2)
						{
							for(int x = 0; x < 8; x++)
								if(wsTileRow[x])
								{
									if((as & 0x20) || !(b_bg[xs + x + 7] & 0x10))
									{
										bool drawthis = 0;

										if(!(DispControl & 0x08)) 
											drawthis = TRUE;
										else if((as & 0x10) && !in_window[7 + xs + x])
											drawthis = TRUE;
										else if(!(as & 0x10) && in_window[7 + xs + x])
											drawthis = TRUE;

										if(drawthis)
										{
											b_bg[xs + x + 7] = wsTileRow[x] | (b_bg[xs + x + 7] & 0x10);
											b_bg_pal[xs + x + 7] = 8 + palette;
										}
									}
								}
						}
						else
						{
							for(int x = 0; x < 8; x++)
								if(wsTileRow[x] || !(palette & 0x4))
								{
									if((as & 0x20) || !(b_bg[xs + x + 7] & 0x10))
									{
										bool drawthis = 0;

										if(!(DispControl & 0x08))
											drawthis = TRUE;
										else if((as & 0x10) && !in_window[7 + xs + x])
											drawthis = TRUE;
										else if(!(as & 0x10) && in_window[7 + xs + x])
											drawthis = TRUE;

										if(drawthis)
										{
											b_bg[xs + x + 7] = wsTileRow[x] | (b_bg[xs + x + 7] & 0x10);
											b_bg_pal[xs + x + 7] = 8 + palette;
										}
									}
								}

						}

					}
					else
					{
						for(int x = 0; x < 8; x++)
							if(wsTileRow[x] || !(palette & 4))
							{
								if((as & 0x20) || !(b_bg[xs + x + 7] & 0x10))
								{
									bool drawthis = 0;

									if(!(DispControl & 0x08))
										drawthis = TRUE;
									else if((as & 0x10) && !in_window[7 + xs + x])
										drawthis = TRUE;
									else if(!(as & 0x10) && in_window[7 + xs + x])
										drawthis = TRUE;

									if(drawthis)
										//if((as & 0x10) || in_window[7 + xs + x])
									{
										b_bg[xs + x + 7] = wsColors[wsMonoPal[8 + palette][wsTileRow[x]]] | (b_bg[xs + x + 7] & 0x10);
									}
								}
							}

					}
				}
			}

		}	// End sprite drawing

		const int hinc = sys->rotate ? -144 : 1;

		if(wsVMode)
		{
			for(l=0;l<224;l++)
			{
				target[0] = ColorMap[wsCols[b_bg_pal[l+7]][b_bg[(l+7)]&0xf]];
				target += hinc;
			}
		}
		else
		{
			for(l=0;l<224;l++)
			{
				target[0] = ColorMapG[(b_bg[l+7])&15];
				target += hinc;
			}
		}
	}

	void GFX::Init(bool color)
	{
		wsc = color;
	}

	void GFX::Reset()
	{
		wsLine = 145; // all frames same length
		SetVideo(0, true);

		std::memset(SpriteTable, 0, sizeof(SpriteTable));
		SpriteCountCache = 0;
		DispControl = 0;
		BGColor = 0;
		LineCompare = 0xBB;
		SPRBase = 0;

		SpriteStart = 0;
		SpriteCount = 0;
		FGBGLoc = 0;

		FGx0 = 0;
		FGy0 = 0;
		FGx1 = 0;
		FGy1 = 0;
		SPRx0 = 0;
		SPRy0 = 0;
		SPRx1 = 0;
		SPRy1 = 0;

		BGXScroll = BGYScroll = 0;
		FGXScroll = FGYScroll = 0;
		LCDControl = 0;
		LCDIcons = 0;

		BTimerControl = 0;
		HBTimerPeriod = 0;
		VBTimerPeriod = 0;

		HBCounter = 0;
		VBCounter = 0;

		std::memset(wsCols, 0, sizeof(wsCols));
	}

	SYNCFUNC(GFX)
	{
		
		// TCACHE stuff.  we invalidate the cache instead of saving it
		if (isReader)
		{
			std::memset(wsTCacheUpdate, 0, sizeof(wsTCacheUpdate));
			std::memset(wsTCacheUpdate2, 0, sizeof(wsTCacheUpdate2));
		}
		/*
		NSS(tiles);
		NSS(wsTCache);			
		NSS(wsTCache2);			
		NSS(wsTCacheFlipped);
		NSS(wsTCacheFlipped2);
		NSS(wsTCacheUpdate);		
		NSS(wsTCacheUpdate2);		  
		NSS(wsTileRow);
		*/

		NSS(wsVMode);

		NSS(wsMonoPal);
		NSS(wsColors);
		NSS(wsCols);

		NSS(ColorMapG);
		NSS(ColorMap);
		NSS(LayerEnabled);

		NSS(wsLine);

		NSS(SpriteTable);
		NSS(SpriteCountCache);
		NSS(DispControl);
		NSS(BGColor);
		NSS(LineCompare);
		NSS(SPRBase);
		NSS(SpriteStart);
		NSS(SpriteCount);
		NSS(FGBGLoc);
		NSS(FGx0);
		NSS(FGy0);
		NSS(FGx1);
		NSS(FGy1);
		NSS(SPRx0);
		NSS(SPRy0);
		NSS(SPRx1);
		NSS(SPRy1);

		NSS(BGXScroll);
		NSS(BGYScroll);
		NSS(FGXScroll);
		NSS(FGYScroll);
		NSS(LCDControl);
		NSS(LCDIcons);

		NSS(BTimerControl);
		NSS(HBTimerPeriod);
		NSS(VBTimerPeriod);

		NSS(HBCounter);
		NSS(VBCounter);
		NSS(VideoMode);

		NSS(wsc); // mono / color
	}
}
