
#include "PicoInt.h"

int (*PicoScan)(unsigned int num,unsigned short *pal,unsigned char *data)=NULL;

// Line colour indices - in the format 00ppcccc pp=palette, cccc=colour
static unsigned char LineCol[32+320+8]; // Gap for 32 column, and messy border on right
static int Scanline=0; // Scanline

static int DefaultCram(int cram)
{
  int high=0x18c3;
  // Convert 0000bbb0 ggg0rrr0
  // to      rrr11ggg 110bbb11
  high|=(cram&0x00e)<<12; // Red
  high|=(cram&0x0e0)<< 3; // Green
  high|=(cram&0xe00)>> 7; // Blue
  return high;
}

int (*PicoCram)(int cram)=DefaultCram;

static int TileNorm(unsigned char *pd,int addr,int pal)
{
  unsigned int pack=0; int t=0;

  pack=*(unsigned int *)(Pico.vram+addr); // Get 8 pixels
  if (pack==0) return 1;

  t=pack&0xf; pack>>=4; if (t) pd[3]=(unsigned char)(pal|t);
  t=pack&0xf; pack>>=4; if (t) pd[2]=(unsigned char)(pal|t);
  t=pack&0xf; pack>>=4; if (t) pd[1]=(unsigned char)(pal|t);
  t=pack&0xf; pack>>=4; if (t) pd[0]=(unsigned char)(pal|t);
  t=pack&0xf; pack>>=4; if (t) pd[7]=(unsigned char)(pal|t);
  t=pack&0xf; pack>>=4; if (t) pd[6]=(unsigned char)(pal|t);
  t=pack&0xf; pack>>=4; if (t) pd[5]=(unsigned char)(pal|t);
  t=pack    ;           if (t) pd[4]=(unsigned char)(pal|t);

  return 0;
}

static int TileFlip(unsigned char *pd,int addr,int pal)
{
  unsigned int pack=0; int t=0;

  pack=*(unsigned int *)(Pico.vram+addr); // Get 8 pixels
  if (pack==0) return 1;

  t=pack&0xf; pack>>=4; if (t) pd[4]=(unsigned char)(pal|t);
  t=pack&0xf; pack>>=4; if (t) pd[5]=(unsigned char)(pal|t);
  t=pack&0xf; pack>>=4; if (t) pd[6]=(unsigned char)(pal|t);
  t=pack&0xf; pack>>=4; if (t) pd[7]=(unsigned char)(pal|t);
  t=pack&0xf; pack>>=4; if (t) pd[0]=(unsigned char)(pal|t);
  t=pack&0xf; pack>>=4; if (t) pd[1]=(unsigned char)(pal|t);
  t=pack&0xf; pack>>=4; if (t) pd[2]=(unsigned char)(pal|t);
  t=pack    ;           if (t) pd[3]=(unsigned char)(pal|t);

  return 0;
}

struct TileStrip
{
  int nametab; // Position in VRAM of name table (for this tile line)
  int line;    // Line number in pixels 0x000-0x3ff within the virtual tilemap 
  int hscroll; // Horizontal scroll value in pixels for the line
  int xmask;   // X-Mask (0x1f - 0x7f) for horizontal wraparound in the tilemap
  int high;    // High or low tiles
};

static int DrawStrip(struct TileStrip ts)
{
  int tilex=0,dx=0,ty=0;
  int blank=-1; // The tile we know is blank

  // Draw tiles across screen:
  tilex=(-ts.hscroll)>>3;
  ty=(ts.line&7)<<1; // Y-Offset into tile
  for (dx=((ts.hscroll-1)&7)+1; dx<328; dx+=8,tilex++)
  {
    int code=0,addr=0,zero=0;
    int pal=0;

    code=Pico.vram[ts.nametab+(tilex&ts.xmask)];
    if (code==blank) continue;
    if ((code>>15)!=ts.high) continue;

    // Get tile address/2:
    addr=(code&0x7ff)<<4;
    if (code&0x1000) addr+=14-ty; else addr+=ty; // Y-flip

    pal=(code>>9)&0x30; // Get palette pointer

    if (code&0x0800) zero=TileFlip(LineCol+24+dx,addr,pal);
    else             zero=TileNorm(LineCol+24+dx,addr,pal);

    if (zero) blank=code; // We know this tile is blank now
  }
  
  return 0;
}

static int DrawLayer(int plane,int high)
{
  struct PicoVideo *pvid=&Pico.video;
  int vscroll=0,htab=0;
  int width=0,height=0;
  char shift[4]={5,6,6,7}; // 32,64 or 128 sized tilemaps
  int ymask=0;
  struct TileStrip ts={0,0,0,0,0};

  // Work out the TileStrip to draw

  // Get vertical scroll value:
  vscroll=Pico.vsram[plane];

  htab=pvid->reg[13]<<9; // Horizontal scroll table address
  if ( pvid->reg[11]&2)     htab+=Scanline<<1; // Offset by line
  if ((pvid->reg[11]&1)==0) htab&=~0xf; // Offset by tile
  htab+=plane; // A or B

  // Get horizontal scroll value
  ts.hscroll=Pico.vram[htab&0x7fff];

  // Work out the name table size: 32 64 or 128 tiles (0-3)
  width=pvid->reg[16]; height=(width>>4)&3; width&=3;

  ts.xmask=(1<<shift[width ])-1; // X Mask in tiles
     ymask=(8<<shift[height])-1; // Y Mask in pixels

  // Find name table:
  if (plane==0) ts.nametab=(pvid->reg[2]&0x38)<< 9; // A
  else          ts.nametab=(pvid->reg[4]&0x07)<<12; // B

  // Find the line in the name table
  ts.line=(vscroll+Scanline)&ymask;
  ts.nametab+=(ts.line>>3)<<shift[width];

  ts.high=high;

  DrawStrip(ts);
  return 0;
}

static int DrawWindow(int high)
{
  struct PicoVideo *pvid=&Pico.video;
  struct TileStrip ts={0,0,0,0,0};

  // Work out the Window TileStrip to draw
  
  ts.line=Scanline;

  // Find name table line:
  if (Pico.video.reg[12]&1)
  {
    ts.nametab=(pvid->reg[3]&0x3c)<<9; // 40-cell mode
    ts.nametab+=(ts.line>>3)<<6;
  }
  else
  {
    ts.nametab=(pvid->reg[3]&0x3e)<<9; // 32-cell mode
    ts.nametab+=(ts.line>>3)<<5;
  }

  ts.xmask=0x3f;
  ts.high=high;

  DrawStrip(ts);
  return 0;
}

static int DrawSprite(int sy,unsigned short *sprite,int high)
{
  int sx=0,width=0,height=0;
  int row=0,code=0;
  int pal=0;
  int tile=0,delta=0;
  int i=0;

  code=sprite[2];
  if ((code>>15)!=high) return 0; // Wrong priority

  height=sprite[1]>>8;
  width=(height>>2)&3; height&=3;
  width++; height++; // Width and height in tiles
  if (Scanline>=sy+(height<<3)) return 0; // Not on this line after all

  row=Scanline-sy; // Row of the sprite we are on
  pal=(code>>9)&0x30; // Get palette pointer
  if (code&0x1000) row=(height<<3)-1-row; // Flip Y

  tile=code&0x7ff; // Tile number
  tile+=row>>3; // Tile number increases going down
  delta=height; // Delta to increase tile by going right
  if (code&0x0800) { tile+=delta*(width-1); delta=-delta; } // Flip X

  tile<<=4; tile+=(row&7)<<1; // Tile address
  delta<<=4; // Delta of address

  sx=(sprite[3]&0x3ff)-0x78; // Get X coordinate + 8

  for (i=0; i<width; i++,sx+=8,tile+=delta)
  {
    if (sx<=0 || sx>=328) continue; // Offscreen

    tile&=0x7fff; // Clip tile address
    if (code&0x0800) TileFlip(LineCol+24+sx,tile,pal);
    else             TileNorm(LineCol+24+sx,tile,pal);
  }

  return 0;
}

static int DrawAllSprites(int high)
{
  struct PicoVideo *pvid=&Pico.video;
  int table=0;
  int i=0,link=0;
  unsigned char spin[80]; // Sprite index

  table=pvid->reg[5]&0x7f;
  if (pvid->reg[12]&1) table&=0x7e; // Lowest bit 0 in 40-cell mode
  table<<=8; // Get sprite table address/2

  for (;;)
  {
    unsigned short *sprite=NULL;
    
    spin[i]=(unsigned char)link;
    sprite=Pico.vram+((table+(link<<2))&0x7ffc); // Find sprite

    // Find next sprite
    link=sprite[1]&0x7f;
    if (link==0 || i>=79) break; // End of sprites
    i++;
  }

  // Go through sprites backwards:
  for ( ;i>=0; i--)
  {
    unsigned short *sprite=NULL;
    int sy=0;
    
    sprite=Pico.vram+((table+(spin[i]<<2))&0x7ffc); // Find sprite
    sy=(sprite[0]&0x3ff)-0x80; // Get Y coordinate

    if (Scanline>=sy && Scanline<sy+32) DrawSprite(sy,sprite,high); // Possibly on this line
  }

  return 0;
}

static void BackFill()
{
  unsigned int back=0;
  unsigned int *pd=NULL,*end=NULL;

  // Start with a blank scanline (background colour):
  back=Pico.video.reg[7]&0x3f;
  back|=back<<8;
  back|=back<<16;

  pd=(unsigned int *)(LineCol+32); end=pd+80;

  do
  {
    pd[0]=back; pd[1]=back; pd[2]=back; pd[3]=back;
    pd+=4;
  }
  while (pd<end);
}

static int DrawDisplay()
{
  int win=0,edge=0,full=0;
  // Find out if the window is on this line:
  win=Pico.video.reg[0x12];
  edge=(win&0x1f)<<3;

  if (win&0x80) { if (Scanline>=edge) full=1; }
  else          { if (Scanline< edge) full=1; }

  DrawLayer(1,0);
  if (full) DrawWindow(0); else DrawLayer(0,0);
  DrawAllSprites(0);

  DrawLayer(1,1);
  if (full) DrawWindow(1); else DrawLayer(0,1);
  DrawAllSprites(1);

  return 0;
}

int PicoLine(int scan)
{
  int c=0;

  Scanline=scan;

  if (Pico.m.dirtyPal)
  {
    // Update palette:
    for (c=0;c<64;c++) Pico.highpal[c]=(unsigned short)PicoCram(Pico.cram[c]);
    Pico.m.dirtyPal=0;
  }

  // Draw screen:
  BackFill();

  if (Pico.video.reg[1]&0x40) DrawDisplay();

  if (Pico.video.reg[12]&1)
  {
    PicoScan(Scanline,Pico.highpal,LineCol+32); // 40-column mode
  }
  else
  {
    // Crop, centre and return 32-column mode
    memset(LineCol,    0,32); // Left border
    memset(LineCol+288,0,32); // Right border
    PicoScan(Scanline,Pico.highpal,LineCol);
  }


  return 0;
}
