/******************************************************************************
 *  Sega Master System / GameGear Emulator
 *  Copyright (C) 1998-2007  Charles MacDonald
 *
 *   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
 *
 *   TMS9918 and legacy video mode support.
 *
 ******************************************************************************/

#include "shared.h"

int text_counter;               /* Text offset counter */

static struct {
    uint8 mc_lookup[16][256][8];    /* Expand BD, PG data into 8-bit pixels (MC) */
    uint8 tms_lookup[16][256][2];   /* Expand BD, PG data into 8-bit pixels (G1,G2) */
    uint8 tms_obj_lut[16*256];      /* Look up priority between SG and display pixels */
    uint8 txt_lookup[256][2];       /* Expand BD, PG data into 8-bit pixels (TX) */
    uint8 bp_expand[256][8];        /* Expand PG data into 8-bit pixels */
} *tables;
#define mc_lookup tables->mc_lookup
#define tms_lookup tables->tms_lookup
#define tms_obj_lut tables->tms_obj_lut
#define txt_lookup tables->txt_lookup
#define bp_expand tables->bp_expand

static const uint8 diff_mask[]  = {0x07, 0x07, 0x0F, 0x0F};
static const uint8 name_mask[]  = {0xFF, 0xFF, 0xFC, 0xFC};
static const uint8 diff_shift[] = {0, 1, 0, 1};
static const uint8 size_tab[]   = {8, 16, 16, 32};

/* Internally latched sprite data in the VDP */
typedef struct {
    int xpos;
    uint8 attr;
    uint8 sg[2];
} tms_sprite;

static tms_sprite sprites[4];
static int sprites_found;

static void render_bg_m0(int line);
static void render_bg_m1(int line);
static void render_bg_m1x(int line);
static void render_bg_inv(int line);
static void render_bg_m3(int line);
static void render_bg_m3x(int line);
static void render_bg_m2(int line);

void parse_line(int line)
{
    int yp, i;
    int mode = vdp.reg[1] & 3;
    int size = size_tab[mode];
    int diff, name;
    uint8 *sa, *sg;
    tms_sprite *p;

    /* Reset # of sprites found */
    sprites_found = 0;

    /* Parse sprites */
    for(i = 0; i < 32; i++)
    {
        /* Point to current sprite in SA and our current sprite record */
        p = &sprites[sprites_found];
        sa = &vdp.vram[vdp.sa + (i << 2)];

        /* Fetch Y coordinate */
        yp = sa[0];

        /* Check for end marker */
        if(yp == 0xD0)
            goto parse_end;

        /* Wrap Y position */
        if(yp > 0xE0)
            yp -= 256;

        /* Check if sprite falls on following line */
        if(line >= yp && line < (yp + size))
        {
            /* Sprite overflow on this line */
            if(sprites_found == 4)
            {
                /* Set 5S and abort parsing */
                vdp.status |= 0x40;
                goto parse_end;
            }

            /* Fetch X position */
            p->xpos = sa[1];

            /* Fetch name */
            name = sa[2] & name_mask[mode];

            /* Load attribute into attribute storage */
            p->attr = sa[3];

            /* Apply early clock bit */
            if(p->attr & 0x80)
                p->xpos -= 32;

            /* Calculate offset in pattern */
            diff = ((line - yp) >> diff_shift[mode]) & diff_mask[mode];

            /* Insert additional name bit for 16-pixel tall sprites */
            if(diff & 8)
                name |= 1;

            /* Fetch SG data */
            sg = &vdp.vram[vdp.sg | (name << 3) | (diff & 7)];
            p->sg[0] = sg[0x00];
            p->sg[1] = sg[0x10];

            /* Bump found sprite count */
            ++sprites_found;
        }
    }
parse_end:

    /* Insert number of last sprite entry processed */
    vdp.status = (vdp.status & 0xE0) | (i & 0x1F);
}

void render_obj_tms(int line)
{
    int i, x = 0;
    int size, start, end, mode;
    uint8 *lb, *lut, *ex[2];
    tms_sprite *p;

    mode = vdp.reg[1] & 3;
    size = size_tab[mode];

    /* Render sprites */
    for(i = 0; i < sprites_found; i++)
    {
        p = &sprites[i];
        lb = &linebuf[p->xpos];
        lut = &tms_obj_lut[(p->attr & 0x0F) << 8];

        /* Point to expanded PG data */
        ex[0] = bp_expand[p->sg[0]];
        ex[1] = bp_expand[p->sg[1]];

        /* Clip left edge */
        if(p->xpos < 0)
            start = 0 - p->xpos;
        else
            start = 0;

        /* Clip right edge */
        if(p->xpos > 256 - size)
            end = 256 - p->xpos;
        else
            end = size;

        /* Render sprite line */
        switch(mode)
        {
            case 0: /* 8x8 */
                for(x = start; x < end; x++) {
                    if(ex[0][x])
                    {
                        /* Check sprite collision */
                        if ((lb[x] & 0x40) && !(vdp.status & 0x20))
                        {
                            /* pixel-accurate SPR_COL flag */
                            vdp.status |= 0x20;
                            vdp.spr_col = (line << 8) | ((p->xpos + x + 13) >> 1);
                        }
                        lb[x] = lut[lb[x]];
                    }
                }
                break;

            case 1: /* 8x8 zoomed */
                for(x = start; x < end; x++) {
                    if(ex[0][x >> 1])
                    {
                        /* Check sprite collision */
                        if ((lb[x] & 0x40) && !(vdp.status & 0x20))
                        {
                            /* pixel-accurate SPR_COL flag */
                            vdp.status |= 0x20;
                            vdp.spr_col = (line << 8) | ((p->xpos + x + 13) >> 1);
                        }
                        lb[x] = lut[lb[x]];
                    }
                }
                break;

            case 2: /* 16x16 */
                for(x = start; x < end; x++) {
                    if(ex[(x >> 3) & 1][x & 7])
                    {
                        /* Check sprite collision */
                        if ((lb[x] & 0x40) && !(vdp.status & 0x20))
                        {
                            /* pixel-accurate SPR_COL flag */
                            vdp.status |= 0x20;
                            vdp.spr_col = (line << 8) | ((p->xpos + x + 13) >> 1);
                        }
                        lb[x] = lut[lb[x]];
                    }
                }
                break;

            case 3: /* 16x16 zoomed */
                for(x = start; x < end; x++) {
                    if(ex[(x >> 4) & 1][(x >> 1) & 7])
                    {
                        /* Check sprite collision */
                        if ((lb[x] & 0x40) && !(vdp.status & 0x20))
                        {
                            /* pixel-accurate SPR_COL flag */
                            vdp.status |= 0x20;
                            vdp.spr_col = (line << 8) | ((p->xpos + x + 13) >> 1);
                        }
                        lb[x] = lut[lb[x]];
                    }
                }
                break;
        }
    }
}

/****
1.) NOTE: xpos can be negative, but the 'start' value that is added
    to xpos will ensure it is positive.

    For an EC sprite that is offscreen, 'start' will be larger
    than 'end' and the for-loop used for rendering will abort
    on the first pass.
***/


#define RENDER_TX_LINE \
        *lb++ = 0x10 | clut[ *bpex++ ]; \
        *lb++ = 0x10 | clut[ *bpex++ ]; \
        *lb++ = 0x10 | clut[ *bpex++ ]; \
        *lb++ = 0x10 | clut[ *bpex++ ]; \
        *lb++ = 0x10 | clut[ *bpex++ ]; \
        *lb++ = 0x10 | clut[ *bpex++ ];

#define RENDER_TX_BORDER \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0]; \
        *lb++ = 0x10 | clut[0];

#define RENDER_GR_LINE \
        *lb++ = 0x10 | clut[ *bpex++ ]; \
        *lb++ = 0x10 | clut[ *bpex++ ]; \
        *lb++ = 0x10 | clut[ *bpex++ ]; \
        *lb++ = 0x10 | clut[ *bpex++ ]; \
        *lb++ = 0x10 | clut[ *bpex++ ]; \
        *lb++ = 0x10 | clut[ *bpex++ ]; \
        *lb++ = 0x10 | clut[ *bpex++ ]; \
        *lb++ = 0x10 | clut[ *bpex++ ];

#define RENDER_MC_LINE \
        *lb++ = 0x10 | *mcex++; \
        *lb++ = 0x10 | *mcex++; \
        *lb++ = 0x10 | *mcex++; \
        *lb++ = 0x10 | *mcex++; \
        *lb++ = 0x10 | *mcex++; \
        *lb++ = 0x10 | *mcex++; \
        *lb++ = 0x10 | *mcex++; \
        *lb++ = 0x10 | *mcex++;

void make_tms_tables(void)
{
    int i, j, x;
    int bd, pg, ct;
    int sx, bx;

    if (!tables)
        tables = calloc(1, sizeof(*tables));

    assert(tables != NULL);

    for(sx = 0; sx < 16; sx++)
    {
        for(bx = 0; bx < 256; bx++)
        {
//          uint8 bd = (bx & 0x0F);
            uint8 bs = (bx & 0x40);
//          uint8 bt = (bd == 0) ? 1 : 0;
            uint8 sd = (sx & 0x0F);
//          uint8 st = (sd == 0) ? 1 : 0;

            // opaque sprite pixel, choose 2nd pal and set sprite marker
            if(sd && !bs)
            {
                tms_obj_lut[(sx<<8)|(bx)] = sd | 0x10 | 0x40;
            }
            else
            if(sd && bs)
            {
                // writing over a sprite
                tms_obj_lut[(sx<<8)|(bx)] = bx;
            }
            else
            {
                tms_obj_lut[(sx<<8)|(bx)] = bx;
            }
        }
    }

    /* Text lookup table */
    for(bd = 0; bd < 256; bd++)
    {
        uint8 bg = (bd >> 0) & 0x0F;
        uint8 fg = (bd >> 4) & 0x0F;

        /* If foreground is transparent, use background color */
        if(fg == 0) fg = bg;

        txt_lookup[bd][0] = bg;
        txt_lookup[bd][1] = fg;
    }

    /* Multicolor lookup table */
    for(bd = 0; bd < 16; bd++)
    {
        for(pg = 0; pg < 256; pg++)
        {
            int l = (pg >> 4) & 0x0F;
            int r = (pg >> 0) & 0x0F;

            /* If foreground is transparent, use background color */
            if(l == 0) l = bd;
            if(r == 0) r = bd;

            /* Unpack 2 nibbles across eight pixels */
            for(x = 0; x < 8; x++)
            {
                int c = (x & 4) ? r : l;

                mc_lookup[bd][pg][x] = c;
            }
        }
    }

    /* Make bitmap data expansion table */
    for(i = 0; i < 256; i++)
    {
        for(j = 0; j < 8; j++)
        {
            int c = (i >> (j ^ 7)) & 1;
            bp_expand[i][j] = c;
        }
    }

    /* Graphics I/II lookup table */
    for(bd = 0; bd < 0x10; bd++)
    {
        for(ct = 0; ct < 0x100; ct++)
        {
            int backdrop = (bd & 0x0F);
            int background = (ct >> 0) & 0x0F;
            int foreground = (ct >> 4) & 0x0F;

            /* If foreground is transparent, use background color */
            if(background == 0) background = backdrop;
            if(foreground == 0) foreground = backdrop;

            tms_lookup[bd][ct][0] = background;
            tms_lookup[bd][ct][1] = foreground;
        }
    }
}


void render_bg_tms(int line)
{
    switch(vdp.mode & 7)
    {
        case 0: /* Graphics I */
            render_bg_m0(line);
            break;

        case 1: /* Text */
            render_bg_m1(line);
            break;

        case 2: /* Graphics II */
            render_bg_m2(line);
            break;

        case 3: /* Text (Extended PG) */
            render_bg_m1x(line);
            break;

        case 4: /* Multicolor */
            render_bg_m3(line);
            break;

        case 5: /* Invalid (1+3) */
            render_bg_inv(line);
            break;

        case 6: /* Multicolor (Extended PG) */
            render_bg_m3x(line);
            break;

        case 7: /* Invalid (1+2+3) */
            render_bg_inv(line);
            break;
    }
}

/* Graphics I */
static void render_bg_m0(int line)
{
    int v_row  = (line & 7);
    int column;
    int name;

    uint8 *clut;
    uint8 *bpex;
    uint8 *lb = &linebuf[0];
    uint8 *pn = &vdp.vram[vdp.pn + ((line >> 3) << 5)];
    uint8 *ct = &vdp.vram[vdp.ct];
    uint8 *pg = &vdp.vram[vdp.pg | (v_row)];

    for(column = 0; column < 32; column++)
    {
        name = pn[column];
        clut = &tms_lookup[vdp.bd][ct[name >> 3]][0];
        bpex = &bp_expand[pg[name << 3]][0];
        RENDER_GR_LINE
    }
}

/* Text */
static void render_bg_m1(int line)
{
    int v_row  = (line & 7);
    int column;

    uint8 *clut;
    uint8 *bpex;
    uint8 *lb = &linebuf[0];
//  uint8 *pn = &vdp.vram[vdp.pn + ((line >> 3) * 40)];

    uint8 *pn = &vdp.vram[vdp.pn + text_counter];

    uint8 *pg = &vdp.vram[vdp.pg | (v_row)];
    uint8 bk = vdp.reg[7];

    clut = &txt_lookup[bk][0];

    for(column = 0; column < 40; column++)
    {
        bpex = &bp_expand[pg[pn[column] << 3]][0];
        RENDER_TX_LINE
    }

    /* V3 */
    if((vdp.line & 7) == 7)
        text_counter += 40;

    RENDER_TX_BORDER
}

/* Text + extended PG */
static void render_bg_m1x(int line)
{
    int v_row  = (line & 7);
    int column;

    uint8 *clut;
    uint8 *bpex;
    uint8 *lb = &linebuf[0];
    uint8 *pn = &vdp.vram[vdp.pn + ((line >> 3) * 40)];
    uint8 *pg = &vdp.vram[vdp.pg + (v_row) + ((line & 0xC0) << 5)];
    uint8 bk = vdp.reg[7];

    clut = &tms_lookup[0][bk][0];

    for(column = 0; column < 40; column++)
    {
        bpex = &bp_expand[pg[pn[column] << 3]][0];
        RENDER_TX_LINE
    }
    RENDER_TX_BORDER
}

/* Invalid (2+3/1+2+3) */
static void render_bg_inv(int line)
{
    int column;
    uint8 *clut;
    uint8 *bpex;
    uint8 *lb = &linebuf[0];
    uint8 bk = vdp.reg[7];

    clut = &txt_lookup[bk][0];

    for(column = 0; column < 40; column++)
    {
        bpex = &bp_expand[0xF0][0];
        RENDER_TX_LINE
    }
}

/* Multicolor */
static void render_bg_m3(int line)
{
    int column;
    uint8 *mcex;
    uint8 *lb = &linebuf[0];

    uint8 *pn = &vdp.vram[vdp.pn + ((line >> 3) << 5)];
    uint8 *pg = &vdp.vram[vdp.pg + ((line >> 2) & 7)];

    for(column = 0; column < 32; column++)
    {
        mcex = &mc_lookup[vdp.bd][pg[pn[column]<<3]][0];
        RENDER_MC_LINE
    }
}

/* Multicolor + extended PG */
static void render_bg_m3x(int line)
{
    int column;
    uint8 *mcex;
    uint8 *lb = &linebuf[0];
    uint8 *pn = &vdp.vram[vdp.pn + ((line >> 3) << 5)];
    uint8 *pg = &vdp.vram[vdp.pg + ((line >> 2) & 7) + ((line & 0xC0) << 5)];

    for(column = 0; column < 32; column++)
    {
        mcex = &mc_lookup[vdp.bd][pg[pn[column]<<3]][0];
        RENDER_MC_LINE
    }
}

/* Graphics II */
static void render_bg_m2(int line)
{
    int v_row  = (line & 7);
    int column;
    int name;

    uint8 *clut;
    uint8 *bpex;
    uint8 *lb = &linebuf[0];
    uint8 *pn = &vdp.vram[vdp.pn | ((line & 0xF8) << 2)];
    uint8 *ct = &vdp.vram[(vdp.ct & 0x2000) | (v_row) | ((line & 0xC0) << 5)];
    uint8 *pg = &vdp.vram[(vdp.pg & 0x2000) | (v_row) | ((line & 0xC0) << 5)];

    for(column = 0; column < 32; column++)
    {
        name = pn[column] << 3;
        clut = &tms_lookup[vdp.bd][ct[name]][0];
        bpex = &bp_expand[pg[name]][0];
        RENDER_GR_LINE
    }
}
