/*
 * Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
 *
 * (c) Copyright 1996 - 2001 Gary Henderson (gary.henderson@ntlworld.com) and
 *                           Jerremy Koot (jkoot@snes9x.com)
 *
 * Super FX C emulator code 
 * (c) Copyright 1997 - 1999 Ivar (ivar@snes9x.com) and
 *                           Gary Henderson.
 * Super FX assembler emulator code (c) Copyright 1998 zsKnight and _Demo_.
 *
 * DSP1 emulator code (c) Copyright 1998 Ivar, _Demo_ and Gary Henderson.
 * C4 asm and some C emulation code (c) Copyright 2000 zsKnight and _Demo_.
 * C4 C code (c) Copyright 2001 Gary Henderson (gary.henderson@ntlworld.com).
 *
 * DOS port code contains the works of other authors. See headers in
 * individual files.
 *
 * Snes9x homepage: http://www.snes9x.com
 *
 * Permission to use, copy, modify and distribute Snes9x in both binary and
 * source form, for non-commercial purposes, is hereby granted without fee,
 * providing that this license information and copyright notice appear with
 * all copies and any derived work.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event shall the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Snes9x is freeware for PERSONAL USE only. Commercial users should
 * seek permission of the copyright holders first. Commercial use includes
 * charging money for Snes9x or software derived from Snes9x.
 *
 * The copyright holders request that bug fixes and improvements to the code
 * should be forwarded to them so everyone can benefit from the modifications
 * in future versions.
 *
 * Super NES and Super Nintendo Entertainment System are trademarks of
 * Nintendo Co., Limited and its subsidiary companies.
 */
#ifndef _PPU_H_
#define _PPU_H_

#define FIRST_VISIBLE_LINE 1

extern uint8 GetBank;
extern uint16 SignExtend [2];
extern uint32 os9x_ColorsChanged;
extern int os9x_softrendering;
extern void tile_askforreset(s32 address);

#define TILE_2BIT 0
#define TILE_4BIT 1
#define TILE_8BIT 2

#define MAX_2BIT_TILES 4096
#define MAX_4BIT_TILES 2048
#define MAX_8BIT_TILES 1024

#define PPU_H_BEAM_IRQ_SOURCE	(1 << 0)
#define PPU_V_BEAM_IRQ_SOURCE	(1 << 1)
#define GSU_IRQ_SOURCE		(1 << 2)
#define SA1_IRQ_SOURCE		(1 << 7)
#define SA1_DMA_IRQ_SOURCE	(1 << 5)

struct ClipData {
    uint32  Count [6];
    uint32  Left [6][6];
    uint32  Right [6][6];
};

struct InternalPPU {
    bool8  ColorsChanged;
    uint8  HDMA;
    bool8  HDMAStarted;
    uint8  MaxBrightness;
    bool8  LatchedBlanking;
    bool8  OBJChanged;
    bool8  RenderThisFrame;
    bool8  DirectColourMapsNeedRebuild;
    uint32 FrameCount;
    uint32 RenderedFramesCount;
    uint32 DisplayedRenderedFrameCount;
    uint32 SkippedFrames;
    uint32 FrameSkip;
    uint8  *TileCache [3];
    	uint8  *TileCache8 [3];
    uint8  *TileCached [3];
    bool8  FirstVRAMRead;
    bool8  LatchedInterlace;
    bool8  DoubleWidthPixels;
    int    RenderedScreenHeight;
    int    RenderedScreenWidth;
    uint8 Red [256];
    uint8 Green [256];
    uint8 Blue [256];
    uint8  *XB;
    uint16 ScreenColors [256];
    int	   PreviousLine;
    int	   CurrentLine;
    int	   Controller;
    uint32 Joypads[5];
    uint32 SuperScope;
    uint32 Mouse[2];
    int    PrevMouseX[2];
    int    PrevMouseY[2];
    struct ClipData Clip [2];
};

struct SOBJ
{
    short  HPos;
    uint16 VPos;
    uint16 Name;
    uint8  VFlip;
    uint8  HFlip;
    uint8  Priority;
    uint8  Palette;
    uint8  Size;
};

struct SPPU {
    uint8  BGMode;
    uint8  BG3Priority;
    uint8  Brightness;

    struct {
	bool8 High;
	uint8 Increment;
	uint16 Address;
	uint16 Mask1;
	uint16 FullGraphicCount;
	uint16 Shift;
    } VMA;

    struct {
	uint16 SCBase;
	union {
		uint16 VOffset;
		uint8 VOffset_Byte[2];
	};
	union {
		uint16 HOffset;
		uint8 HOffset_Byte[2];
	};
	uint8 BGSize;
	uint16 NameBase;
	uint16 SCSize;
    } BG [4];

    bool8  CGFLIP;
    uint16 CGDATA [256]; 
    uint8  FirstSprite;
    uint8  LastSprite;
    struct SOBJ OBJ [128];
    uint8  OAMPriorityRotation;
    uint16 OAMAddr;    

    uint8  OAMFlip;
    uint16 OAMTileAddress;
    uint16 IRQVBeamPos;
    uint16 IRQHBeamPos;
    uint16 VBeamPosLatched;
    uint16 HBeamPosLatched;

    uint8  HBeamFlip;
    uint8  VBeamFlip;
    uint8  HVBeamCounterLatched;

	union {
		short  MatrixA;
		uint8  MatrixA_Byte[2];
	};
	union {
		short  MatrixB;
		uint8  MatrixB_Byte[2];
	};
	union {
		short  MatrixC;
		uint8  MatrixC_Byte[2];
	};
	union {
		short  MatrixD;
		uint8  MatrixD_Byte[2];
	};
	union {
		short  CentreX;
		uint8  CentreX_Byte[2];
	};
	union {
		short  CentreY;
		uint8  CentreY_Byte[2];
	};
    uint8  Joypad1ButtonReadPos;
    uint8  Joypad2ButtonReadPos;

    uint8  CGADD;
    uint8  FixedColourRed;
    uint8  FixedColourGreen;
    uint8  FixedColourBlue;
    uint16 SavedOAMAddr;
    uint16 ScreenHeight;
    uint32 WRAM;
    uint8  BG_Forced;
    bool8  ForcedBlanking;
    bool8  OBJThroughMain;
    bool8  OBJThroughSub;
    uint8  OBJSizeSelect;
    uint16 OBJNameBase;
    bool8  OBJAddition;
    uint8  OAMReadFlip;
    uint8  OAMData [512 + 32];
    bool8  VTimerEnabled;
    bool8  HTimerEnabled;
    short  HTimerPosition;
    uint8  Mosaic;
    bool8  BGMosaic [4];
    bool8  Mode7HFlip;
    bool8  Mode7VFlip;
    uint8  Mode7Repeat;
    uint8  Window1Left;
    uint8  Window1Right;
    uint8  Window2Left;
    uint8  Window2Right;
    uint8  ClipCounts [6];
    uint8  ClipWindowOverlapLogic [6];
    uint8  ClipWindow1Enable [6];
    uint8  ClipWindow2Enable [6];
    bool8  ClipWindow1Inside [6];
    bool8  ClipWindow2Inside [6];
    bool8  RecomputeClipWindows;
    uint8  CGFLIPRead;
    uint16 OBJNameSelect;
    bool8  Need16x8Mulitply;
    uint8  Joypad3ButtonReadPos;
    uint8  MouseSpeed[2];
};

#define CLIP_OR 0
#define CLIP_AND 1
#define CLIP_XOR 2
#define CLIP_XNOR 3

#ifdef LSB_FIRST
 #define LOWBYTE 0
 #define HIGHBYTE 1
#else
 1#define LOWBYTE 1
 #define HIGHBYTE 0
#endif


struct SDMA {
    bool8  TransferDirection;
    bool8  AAddressFixed;
    bool8  AAddressDecrement;
    uint8  TransferMode;

	uint16 AAddress;
	uint16 Address;
    uint8  ABank;
    uint8  BAddress;

    // General DMA only:
	uint16 TransferBytes;

    // H-DMA only:
    bool8  HDMAIndirectAddressing;
	uint16 IndirectAddress;
    uint8  IndirectBank;
    uint8  Repeat;
    uint8  LineCount;
    uint8  FirstLine;
};


struct SPPUPACK {
	struct SPPU PPU;
	struct InternalPPU IPPU;
};

START_EXTERN_C
void S9xUpdateScreen ();
void S9xResetPPU ();
void S9xFixColourBrightness ();
void S9xUpdateJoypads ();
void S9xProcessMouse(int which1);
void S9xSuperFXExec ();

void S9xSetPPU (uint8 Byte, uint16 Address);
uint8 S9xGetPPU (uint16 Address);
void S9xSetCPU (uint8 Byte, uint16 Address);
uint8 S9xGetCPU (uint16 Address);

//#ifndef __GP32__
void S9xInitC4 ();
void S9xSetC4 (uint8 Byte, uint16 Address);
uint8 S9xGetC4 (uint16 Address);
void S9xSetC4RAM (uint8 Byte, uint16 Address);
uint8 S9xGetC4RAM (uint16 Address);
//#endif

//extern struct SPPU PPU;
extern struct SDMA DMA [8];
//extern struct InternalPPU IPPU;

extern struct SPPUPACK PPUPack;

//yoyo
extern uint32 os9x_ColorsChanged;
END_EXTERN_C

#include "gfx.h"
#include "memmap.h"

STATIC inline uint8 REGISTER_4212()
{
    uint8 GetBank = 0;
    if (CPUPack.CPU.V_Counter >= PPUPack.PPU.ScreenHeight + FIRST_VISIBLE_LINE &&
	CPUPack.CPU.V_Counter < PPUPack.PPU.ScreenHeight + FIRST_VISIBLE_LINE + 3)
	GetBank = 1;

    GetBank |= CPUPack.CPU.Cycles >= Settings.HBlankStart ? 0x40 : 0;
    if (CPUPack.CPU.V_Counter >= PPUPack.PPU.ScreenHeight + FIRST_VISIBLE_LINE)
	GetBank |= 0x80; /* XXX: 0x80 or 0xc0 ? */

    return (GetBank);
}

STATIC inline void FLUSH_REDRAW ()
{
    //if (PPUPack.IPPU.PreviousLine != PPUPack.IPPU.CurrentLine) {    	    	
    if (PPUPack.IPPU.PreviousLine != PPUPack.IPPU.CurrentLine) {    	    	
			S9xUpdateScreen ();						
		}
}

STATIC inline void REGISTER_2104 (uint8 byte)
{
    if (PPUPack.PPU.OAMAddr >= 0x110)
	return;
	
    int addr = (PPUPack.PPU.OAMAddr << 1) + (PPUPack.PPU.OAMFlip & 1);
    
    if (byte != PPUPack.PPU.OAMData [addr])
    {
    	INFO_FLUSH_REDRAW("2104");
	FLUSH_REDRAW ();
	PPUPack.PPU.OAMData [addr] = byte;
	PPUPack.IPPU.OBJChanged = TRUE;
	if (addr & 0x200)
	{
	    // X position high bit, and sprite size (x4)
	    struct SOBJ *pObj = &PPUPack.PPU.OBJ [(addr & 0x1f) * 4];

	    pObj->HPos = (pObj->HPos & 0xFF) | SignExtend[(byte >> 0) & 1];
	    pObj++->Size = byte & 2;
	    pObj->HPos = (pObj->HPos & 0xFF) | SignExtend[(byte >> 2) & 1];
	    pObj++->Size = byte & 8;
	    pObj->HPos = (pObj->HPos & 0xFF) | SignExtend[(byte >> 4) & 1];
	    pObj++->Size = byte & 32;
	    pObj->HPos = (pObj->HPos & 0xFF) | SignExtend[(byte >> 6) & 1];
	    pObj->Size = byte & 128;
	}
	else
	{
	    if (addr & 1)
	    {
		if (addr & 2)
		{
		    addr = PPUPack.PPU.OAMAddr >> 1;
		    // Tile within group, priority, h and v flip.
		    PPUPack.PPU.OBJ[addr].Name &= 0xFF;
		    PPUPack.PPU.OBJ[addr].Name |= ((uint16) (byte & 1)) << 8;
		    PPUPack.PPU.OBJ[addr].Palette = (byte >> 1) & 7;
		    PPUPack.PPU.OBJ[addr].Priority = (byte >> 4) & 3;
		    PPUPack.PPU.OBJ[addr].HFlip = (byte >> 6) & 1;
		    PPUPack.PPU.OBJ[addr].VFlip = (byte >> 7) & 1;
		}
		else
		{
		    // Sprite Y position
		    PPUPack.PPU.OBJ[PPUPack.PPU.OAMAddr >> 1].VPos = byte;
		}
	    }
	    else
	    {
		if (addr & 2)
		{
		    // Tile group
		    
		    PPUPack.PPU.OBJ[addr = PPUPack.PPU.OAMAddr >> 1].Name &= 0x100;
		    PPUPack.PPU.OBJ[addr].Name |= byte;
		}
		else
		{
		    // X position (low)
		    PPUPack.PPU.OBJ[addr = PPUPack.PPU.OAMAddr >> 1].HPos &= 0xFF00;
		    PPUPack.PPU.OBJ[addr].HPos |= byte;
		}
	    }
	}
    }
    PPUPack.PPU.OAMFlip ^= 1;
    if (!(PPUPack.PPU.OAMFlip & 1))
	PPUPack.PPU.OAMAddr++;

    FillRAM [0x2104] = byte;
}

STATIC inline void REGISTER_2118 (uint8 Byte)
{
    uint32 address;
    if (PPUPack.PPU.VMA.FullGraphicCount)
    {
	uint32 rem = PPUPack.PPU.VMA.Address & PPUPack.PPU.VMA.Mask1;
	address = (((PPUPack.PPU.VMA.Address & ~PPUPack.PPU.VMA.Mask1) +
			 (rem >> PPUPack.PPU.VMA.Shift) +
			 ((rem & (PPUPack.PPU.VMA.FullGraphicCount - 1)) << 3)) << 1) & 0xffff;
	VRAM [address] = Byte;
    }
    else
    {
	VRAM[address = (PPUPack.PPU.VMA.Address << 1) & 0xFFFF] = Byte;
    }

		if (os9x_softrendering>=2) tile_askforreset(address);
		if (os9x_softrendering!=2) {
    	PPUPack.IPPU.TileCached [TILE_2BIT][(address >> 4)<<1] = FALSE;
    	PPUPack.IPPU.TileCached [TILE_4BIT][(address >> 5)<<1] = FALSE;
    	PPUPack.IPPU.TileCached [TILE_8BIT][(address >> 6)<<1] = FALSE;
    }
    

    if (!PPUPack.PPU.VMA.High)
    {
#ifdef DEBUGGER
	if (Settings.TraceVRAM && !CPUPack.CPU.InDMA)
	{
	    printf ("VRAM write byte: $%04X (%d,%d)\n", PPUPack.PPU.VMA.Address,
		    FillRAM[0x2115] & 3,
		    (FillRAM [0x2115] & 0x0c) >> 2);
	}
#endif	
	PPUPack.PPU.VMA.Address += PPUPack.PPU.VMA.Increment;
    }
//    FillRAM [0x2118] = Byte;
}

STATIC inline void REGISTER_2118_tile (uint8 Byte)
{
    uint32 address;
    uint32 rem = PPUPack.PPU.VMA.Address & PPUPack.PPU.VMA.Mask1;
    address = (((PPUPack.PPU.VMA.Address & ~PPUPack.PPU.VMA.Mask1) +
		 (rem >> PPUPack.PPU.VMA.Shift) +
		 ((rem & (PPUPack.PPU.VMA.FullGraphicCount - 1)) << 3)) << 1) & 0xffff;
    VRAM [address] = Byte;

		if (os9x_softrendering>=2) tile_askforreset(address);
		if (os9x_softrendering!=2) {
    	PPUPack.IPPU.TileCached [TILE_2BIT][(address >> 4)<<1] = FALSE;
    	PPUPack.IPPU.TileCached [TILE_4BIT][(address >> 5)<<1] = FALSE;
    	PPUPack.IPPU.TileCached [TILE_8BIT][(address >> 6)<<1] = FALSE;
    }
   
    if (!PPUPack.PPU.VMA.High)
	PPUPack.PPU.VMA.Address += PPUPack.PPU.VMA.Increment;
//    FillRAM [0x2118] = Byte;
}

STATIC inline void REGISTER_2118_linear (uint8 Byte)
{
    uint32 address;
    VRAM[address = (PPUPack.PPU.VMA.Address << 1) & 0xFFFF] = Byte;

		if (os9x_softrendering>=2) tile_askforreset(address);
		if (os9x_softrendering!=2) {
    	PPUPack.IPPU.TileCached [TILE_2BIT][(address >> 4)<<1] = FALSE;
    	PPUPack.IPPU.TileCached [TILE_4BIT][(address >> 5)<<1] = FALSE;
    	PPUPack.IPPU.TileCached [TILE_8BIT][(address >> 6)<<1] = FALSE;
    }
   
    if (!PPUPack.PPU.VMA.High)
	PPUPack.PPU.VMA.Address += PPUPack.PPU.VMA.Increment;
//    FillRAM [0x2118] = Byte;
}

STATIC inline void REGISTER_2119 (uint8 Byte)
{
    uint32 address;
    if (PPUPack.PPU.VMA.FullGraphicCount)
    {
	uint32 rem = PPUPack.PPU.VMA.Address & PPUPack.PPU.VMA.Mask1;
	address = ((((PPUPack.PPU.VMA.Address & ~PPUPack.PPU.VMA.Mask1) +
		    (rem >> PPUPack.PPU.VMA.Shift) +
		    ((rem & (PPUPack.PPU.VMA.FullGraphicCount - 1)) << 3)) << 1) + 1) & 0xFFFF;
	VRAM [address] = Byte;
    }
    else
    {
	VRAM[address = ((PPUPack.PPU.VMA.Address << 1) + 1) & 0xFFFF] = Byte;
    }

		if (os9x_softrendering>=2) tile_askforreset(address);
		if (os9x_softrendering!=2) {
    	PPUPack.IPPU.TileCached [TILE_2BIT][(address >> 4)<<1] = FALSE;
    	PPUPack.IPPU.TileCached [TILE_4BIT][(address >> 5)<<1] = FALSE;
    	PPUPack.IPPU.TileCached [TILE_8BIT][(address >> 6)<<1] = FALSE;
    }
   
    if (PPUPack.PPU.VMA.High)
    {
#ifdef DEBUGGER
	if (Settings.TraceVRAM && !CPUPack.CPU.InDMA)
	{
	    printf ("VRAM write word: $%04X (%d,%d)\n", PPUPack.PPU.VMA.Address,
		    FillRAM[0x2115] & 3,
		    (FillRAM [0x2115] & 0x0c) >> 2);
	}
#endif	
	PPUPack.PPU.VMA.Address += PPUPack.PPU.VMA.Increment;
    }
//    FillRAM [0x2119] = Byte;
}

STATIC inline void REGISTER_2119_tile (uint8 Byte)
{
    uint32 rem = PPUPack.PPU.VMA.Address & PPUPack.PPU.VMA.Mask1;
    uint32 address = ((((PPUPack.PPU.VMA.Address & ~PPUPack.PPU.VMA.Mask1) +
		    (rem >> PPUPack.PPU.VMA.Shift) +
		    ((rem & (PPUPack.PPU.VMA.FullGraphicCount - 1)) << 3)) << 1) + 1) & 0xFFFF;
    VRAM [address] = Byte;

		if (os9x_softrendering>=2) tile_askforreset(address);
		if (os9x_softrendering!=2) {
    	PPUPack.IPPU.TileCached [TILE_2BIT][(address >> 4)<<1] = FALSE;
    	PPUPack.IPPU.TileCached [TILE_4BIT][(address >> 5)<<1] = FALSE;
    	PPUPack.IPPU.TileCached [TILE_8BIT][(address >> 6)<<1] = FALSE;
    }
 
    if (PPUPack.PPU.VMA.High)
	PPUPack.PPU.VMA.Address += PPUPack.PPU.VMA.Increment;
//    FillRAM [0x2119] = Byte;
}

STATIC inline void REGISTER_2119_linear (uint8 Byte)
{
    uint32 address;
    VRAM[address = ((PPUPack.PPU.VMA.Address << 1) + 1) & 0xFFFF] = Byte;

		if (os9x_softrendering>=2) tile_askforreset(address);
		if (os9x_softrendering!=2) {
    	PPUPack.IPPU.TileCached [TILE_2BIT][(address >> 4)<<1] = FALSE;
    	PPUPack.IPPU.TileCached [TILE_4BIT][(address >> 5)<<1] = FALSE;
    	PPUPack.IPPU.TileCached [TILE_8BIT][(address >> 6)<<1] = FALSE;
    }
    
    if (PPUPack.PPU.VMA.High)
	PPUPack.PPU.VMA.Address += PPUPack.PPU.VMA.Increment;
//    FillRAM [0x2119] = Byte;
}

STATIC inline void REGISTER_2122(uint8 Byte)
{
    // CG-RAM (palette) write
    if (PPUPack.PPU.CGFLIP)
    {    	
	if ((Byte & 0x7f) != (PPUPack.PPU.CGDATA[PPUPack.PPU.CGADD] >> 8))
	{
	    if (!(os9x_hack&PPU_IGNORE_PALWRITE)){
	    	INFO_FLUSH_REDRAW("2122flip");
				FLUSH_REDRAW ();
			}
	    PPUPack.PPU.CGDATA[PPUPack.PPU.CGADD] &= 0x00FF;
	    PPUPack.PPU.CGDATA[PPUPack.PPU.CGADD] |= (Byte & 0x7f) << 8;
	    PPUPack.IPPU.ColorsChanged = TRUE;
		PPUPack.IPPU.Blue [PPUPack.PPU.CGADD] = PPUPack.IPPU.XB [(Byte >> 2) & 0x1f];
		PPUPack.IPPU.Green [PPUPack.PPU.CGADD] = PPUPack.IPPU.XB [(PPUPack.PPU.CGDATA[PPUPack.PPU.CGADD] >> 5) & 0x1f];
		PPUPack.IPPU.ScreenColors [PPUPack.PPU.CGADD] = (uint16) BUILD_PIXEL (PPUPack.IPPU.Red [PPUPack.PPU.CGADD],
							     PPUPack.IPPU.Green [PPUPack.PPU.CGADD],
							     PPUPack.IPPU.Blue [PPUPack.PPU.CGADD]);
		os9x_ColorsChanged=1;							     
	}
	PPUPack.PPU.CGADD++;
    }
    else
    {
	if (Byte != (uint8) (PPUPack.PPU.CGDATA[PPUPack.PPU.CGADD] & 0xff))
	{
	    if (!(os9x_hack&PPU_IGNORE_PALWRITE)){
	    	INFO_FLUSH_REDRAW("2122dif");
				FLUSH_REDRAW ();
			}
	    PPUPack.PPU.CGDATA[PPUPack.PPU.CGADD] &= 0x7F00;
	    PPUPack.PPU.CGDATA[PPUPack.PPU.CGADD] |= Byte;
	    PPUPack.IPPU.ColorsChanged = TRUE;
		PPUPack.IPPU.Red [PPUPack.PPU.CGADD] = PPUPack.IPPU.XB [Byte & 0x1f];
		PPUPack.IPPU.Green [PPUPack.PPU.CGADD] = PPUPack.IPPU.XB [(PPUPack.PPU.CGDATA[PPUPack.PPU.CGADD] >> 5) & 0x1f];
		PPUPack.IPPU.ScreenColors [PPUPack.PPU.CGADD] = (uint16) BUILD_PIXEL (PPUPack.IPPU.Red [PPUPack.PPU.CGADD],
							     PPUPack.IPPU.Green [PPUPack.PPU.CGADD],
							     PPUPack.IPPU.Blue [PPUPack.PPU.CGADD]);
		os9x_ColorsChanged=1;							 
	}
    }
    PPUPack.PPU.CGFLIP ^= 1;
//    FillRAM [0x2122] = Byte;
}

STATIC inline void REGISTER_2180(uint8 Byte)
{
    RAM[PPUPack.PPU.WRAM++] = Byte;
    PPUPack.PPU.WRAM &= 0x1FFFF;
    FillRAM [0x2180] = Byte;
}
#endif
