/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          3DFX Voodoo emulation.
 *
 * Authors: Sarah Walker, <https://pcem-emulator.co.uk/>
 *
 *          Copyright 2008-2020 Sarah Walker.
 */
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <wchar.h>
#include <math.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include "cpu.h"
#include <86box/machine.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/timer.h>
#include <86box/device.h>
#include <86box/plat.h>
#include <86box/thread.h>
#include <86box/video.h>
#include <86box/vid_svga.h>
#include <86box/vid_voodoo_common.h>
#include <86box/vid_voodoo_blitter.h>
#include <86box/vid_voodoo_dither.h>
#include <86box/vid_voodoo_regs.h>
#include <86box/vid_voodoo_render.h>

enum {
    BLIT_COMMAND_SCREEN_TO_SCREEN = 0,
    BLIT_COMMAND_CPU_TO_SCREEN    = 1,
    BLIT_COMMAND_RECT_FILL        = 2,
    BLIT_COMMAND_SGRAM_FILL       = 3
};

enum {
    BLIT_SRC_1BPP             = (0 << 3),
    BLIT_SRC_1BPP_BYTE_PACKED = (1 << 3),
    BLIT_SRC_16BPP            = (2 << 3),
    BLIT_SRC_24BPP            = (3 << 3),
    BLIT_SRC_24BPP_DITHER_2X2 = (4 << 3),
    BLIT_SRC_24BPP_DITHER_4X4 = (5 << 3)
};

enum {
    BLIT_SRC_RGB_ARGB = (0 << 6),
    BLIT_SRC_RGB_ABGR = (1 << 6),
    BLIT_SRC_RGB_RGBA = (2 << 6),
    BLIT_SRC_RGB_BGRA = (3 << 6)
};

enum {
    BLIT_COMMAND_MASK     = 7,
    BLIT_SRC_FORMAT       = (7 << 3),
    BLIT_SRC_RGB_FORMAT   = (3 << 6),
    BLIT_SRC_CHROMA       = (1 << 10),
    BLIT_DST_CHROMA       = (1 << 12),
    BLIT_CLIPPING_ENABLED = (1 << 16)
};

enum {
    BLIT_ROP_DST_PASS = (1 << 0),
    BLIT_ROP_SRC_PASS = (1 << 1)
};

#ifdef ENABLE_VOODOOBLT_LOG
int voodooblt_do_log = ENABLE_VOODOOBLT_LOG;

static void
voodooblt_log(const char *fmt, ...)
{
    va_list ap;

    if (voodooblt_do_log) {
        va_start(ap, fmt);
        pclog_ex(fmt, ap);
        va_end(ap);
    }
}
#else
#    define voodooblt_log(fmt, ...)
#endif

#define MIX(src_dat, dst_dat, rop)          \
    switch (rop) {                          \
        case 0x0:                           \
            dst_dat = 0;                    \
            break;                          \
        case 0x1:                           \
            dst_dat = ~(src_dat | dst_dat); \
            break;                          \
        case 0x2:                           \
            dst_dat = ~src_dat & dst_dat;   \
            break;                          \
        case 0x3:                           \
            dst_dat = ~src_dat;             \
            break;                          \
        case 0x4:                           \
            dst_dat = src_dat & ~dst_dat;   \
            break;                          \
        case 0x5:                           \
            dst_dat = ~dst_dat;             \
            break;                          \
        case 0x6:                           \
            dst_dat = src_dat ^ dst_dat;    \
            break;                          \
        case 0x7:                           \
            dst_dat = ~(src_dat & dst_dat); \
            break;                          \
        case 0x8:                           \
            dst_dat = src_dat & dst_dat;    \
            break;                          \
        case 0x9:                           \
            dst_dat = ~(src_dat ^ dst_dat); \
            break;                          \
        case 0xa:                           \
            dst_dat = dst_dat;              \
            break;                          \
        case 0xb:                           \
            dst_dat = ~src_dat | dst_dat;   \
            break;                          \
        case 0xc:                           \
            dst_dat = src_dat;              \
            break;                          \
        case 0xd:                           \
            dst_dat = src_dat | ~dst_dat;   \
            break;                          \
        case 0xe:                           \
            dst_dat = src_dat | dst_dat;    \
            break;                          \
        case 0xf:                           \
            dst_dat = 0xffff;               \
            break;                          \
    }

void
voodoo_v2_blit_start(voodoo_t *voodoo)
{
    uint64_t dat64;
    int      size_x = ABS(voodoo->bltSizeX);
    int      size_y = ABS(voodoo->bltSizeY);
    int      x_dir = (voodoo->bltSizeX > 0) ? 1 : -1;
    int      y_dir = (voodoo->bltSizeY > 0) ? 1 : -1;
    int      dst_x;
    int      src_y = voodoo->bltSrcY & 0x7ff;
    int      dst_y = voodoo->bltDstY & 0x7ff;
    int      src_stride    = (voodoo->bltCommand & BLTCMD_SRC_TILED) ? ((voodoo->bltSrcXYStride & 0x3f) * 32 * 2) : (voodoo->bltSrcXYStride & 0xff8);
    int      dst_stride    = (voodoo->bltCommand & BLTCMD_DST_TILED) ? ((voodoo->bltDstXYStride & 0x3f) * 32 * 2) : (voodoo->bltDstXYStride & 0xff8);
    uint32_t src_base_addr = (voodoo->bltCommand & BLTCMD_SRC_TILED) ? ((voodoo->bltSrcBaseAddr & 0x3ff) << 12) : (voodoo->bltSrcBaseAddr & 0x3ffff8);
    uint32_t dst_base_addr = (voodoo->bltCommand & BLTCMD_DST_TILED) ? ((voodoo->bltDstBaseAddr & 0x3ff) << 12) : (voodoo->bltDstBaseAddr & 0x3ffff8);

#if 0
    voodooblt_log("blit_start: command=%08x srcX=%i srcY=%i dstX=%i dstY=%i sizeX=%i sizeY=%i color=%04x,%04x\n",
                  voodoo->bltCommand, voodoo->bltSrcX, voodoo->bltSrcY, voodoo->bltDstX, voodoo->bltDstY, voodoo->bltSizeX, voodoo->bltSizeY, voodoo->bltColorFg, voodoo->bltColorBg);
#endif

    voodoo_wait_for_render_thread_idle(voodoo);

    switch (voodoo->bltCommand & BLIT_COMMAND_MASK) {
        case BLIT_COMMAND_SCREEN_TO_SCREEN:
            for (int y = 0; y <= size_y; y++) {
                const uint16_t *src   = (uint16_t *) &voodoo->fb_mem[src_base_addr + src_y * src_stride];
                uint16_t       *dst   = (uint16_t *) &voodoo->fb_mem[dst_base_addr + dst_y * dst_stride];
                int             src_x = voodoo->bltSrcX;
                int             dst_x = voodoo->bltDstX;

                for (int x = 0; x <= size_x; x++) {
                    uint16_t src_dat = src[src_x];
                    uint16_t dst_dat = dst[dst_x];
                    int      rop     = 0;

                    if (voodoo->bltCommand & BLIT_CLIPPING_ENABLED) {
                        if (dst_x < voodoo->bltClipLeft || dst_x >= voodoo->bltClipRight || dst_y < voodoo->bltClipLowY || dst_y >= voodoo->bltClipHighY)
                            goto skip_pixel_blit;
                    }

                    if (voodoo->bltCommand & BLIT_SRC_CHROMA) {
                        int r = (src_dat >> 11);
                        int g = (src_dat >> 5) & 0x3f;
                        int b = src_dat & 0x1f;

                        if (r >= voodoo->bltSrcChromaMinR && r <= voodoo->bltSrcChromaMaxR && g >= voodoo->bltSrcChromaMinG && g <= voodoo->bltSrcChromaMaxG && b >= voodoo->bltSrcChromaMinB && b <= voodoo->bltSrcChromaMaxB)
                            rop |= BLIT_ROP_SRC_PASS;
                    }
                    if (voodoo->bltCommand & BLIT_DST_CHROMA) {
                        int r = (dst_dat >> 11);
                        int g = (dst_dat >> 5) & 0x3f;
                        int b = dst_dat & 0x1f;

                        if (r >= voodoo->bltDstChromaMinR && r <= voodoo->bltDstChromaMaxR && g >= voodoo->bltDstChromaMinG && g <= voodoo->bltDstChromaMaxG && b >= voodoo->bltDstChromaMinB && b <= voodoo->bltDstChromaMaxB)
                            rop |= BLIT_ROP_DST_PASS;
                    }

                    MIX(src_dat, dst_dat, voodoo->bltRop[rop]);

                    dst[dst_x] = dst_dat;
skip_pixel_blit:
                    src_x += x_dir;
                    dst_x += x_dir;
                }

                src_y += y_dir;
                dst_y += y_dir;
            }
            break;

        case BLIT_COMMAND_CPU_TO_SCREEN:
            voodoo->launch_pending = 1;
            break;

        case BLIT_COMMAND_RECT_FILL:
            for (int y = 0; y <= size_y; y++) {
                uint16_t *dst;
                int       dst_x = voodoo->bltDstX;

                if (SLI_ENABLED) {
                    if ((!(voodoo->initEnable & INITENABLE_SLI_MASTER_SLAVE) && (dst_y & 1)) || ((voodoo->initEnable & INITENABLE_SLI_MASTER_SLAVE) && !(dst_y & 1)))
                        goto skip_line_fill;
                    dst = (uint16_t *) &voodoo->fb_mem[dst_base_addr + (dst_y >> 1) * dst_stride];
                } else
                    dst = (uint16_t *) &voodoo->fb_mem[dst_base_addr + dst_y * dst_stride];

                for (int x = 0; x < size_x; x++) {
                    if (voodoo->bltCommand & BLIT_CLIPPING_ENABLED) {
                        if (dst_x < voodoo->bltClipLeft || dst_x >= voodoo->bltClipRight || dst_y < voodoo->bltClipLowY || dst_y >= voodoo->bltClipHighY)
                            goto skip_pixel_fill;
                    }

                    dst[dst_x] = voodoo->bltColorFg;
skip_pixel_fill:
                    dst_x += x_dir;
                }
skip_line_fill:
                dst_y += y_dir;
            }
            break;

        case BLIT_COMMAND_SGRAM_FILL:
            /*32x32 tiles - 2kb*/
            dst_y  = voodoo->bltDstY & 0x3ff;
            size_x = voodoo->bltSizeX & 0x1ff; // 512*8 = 4kb
            size_y = voodoo->bltSizeY & 0x3ff;

            dat64 = voodoo->bltColorFg | ((uint64_t) voodoo->bltColorFg << 16) | ((uint64_t) voodoo->bltColorFg << 32) | ((uint64_t) voodoo->bltColorFg << 48);

            for (int y = 0; y <= size_y; y++) {
                uint64_t *dst;

                /*This may be wrong*/
                if (!y) {
                    dst_x  = voodoo->bltDstX & 0x1ff;
                    size_x = 511 - dst_x;
                } else if (y < size_y) {
                    dst_x  = 0;
                    size_x = 511;
                } else {
                    dst_x  = 0;
                    size_x = voodoo->bltSizeX & 0x1ff;
                }

                dst = (uint64_t *) &voodoo->fb_mem[(dst_y * 512 * 8 + dst_x * 8) & voodoo->fb_mask];

                for (int x = 0; x <= size_x; x++)
                    dst[x] = dat64;

                dst_y++;
            }
            break;

        default:
            fatal("bad blit command %08x\n", voodoo->bltCommand);
    }
}

void
voodoo_v2_blit_data(voodoo_t *voodoo, uint32_t data)
{
    int       src_bits  = 32;
    uint32_t  base_addr = (voodoo->bltCommand & BLTCMD_DST_TILED) ? ((voodoo->bltDstBaseAddr & 0x3ff) << 12) : (voodoo->bltDstBaseAddr & 0x3ffff8);
    uint32_t  addr;
    uint16_t *dst;

    if ((voodoo->bltCommand & BLIT_COMMAND_MASK) != BLIT_COMMAND_CPU_TO_SCREEN)
        return;

    if (voodoo->launch_pending) {
        voodoo->blt.dst_x      = voodoo->bltDstX;
        voodoo->blt.dst_y      = voodoo->bltDstY;
        voodoo->blt.cur_x      = 0;
        voodoo->blt.size_x     = voodoo->bltSizeX;
        voodoo->blt.size_y     = voodoo->bltSizeY;
        voodoo->blt.x_dir      = (voodoo->bltSizeX > 0) ? 1 : -1;
        voodoo->blt.y_dir      = (voodoo->bltSizeY > 0) ? 1 : -1;
        voodoo->blt.dst_stride = (voodoo->bltCommand & BLTCMD_DST_TILED) ? ((voodoo->bltDstXYStride & 0x3f) * 32 * 2) : (voodoo->bltDstXYStride & 0xff8);
        voodoo->launch_pending = 0;
    }

    if (SLI_ENABLED) {
        addr = base_addr + (voodoo->blt.dst_y >> 1) * voodoo->blt.dst_stride;
        dst  = (uint16_t *) &voodoo->fb_mem[addr];
    } else {
        addr = base_addr + voodoo->blt.dst_y * voodoo->blt.dst_stride;
        dst  = (uint16_t *) &voodoo->fb_mem[addr];
    }

    if (addr >= voodoo->front_offset && voodoo->row_width) {
        int y = (addr - voodoo->front_offset) / voodoo->row_width;
        if (y < voodoo->v_disp)
            voodoo->dirty_line[y] = 2;
    }

    while (src_bits && voodoo->blt.cur_x <= voodoo->blt.size_x) {
        int      r       = 0;
        int      g       = 0;
        int      b       = 0;
        uint16_t src_dat = 0;
        uint16_t dst_dat;
        int      x       = (voodoo->blt.x_dir > 0) ? (voodoo->blt.dst_x + voodoo->blt.cur_x) : (voodoo->blt.dst_x - voodoo->blt.cur_x);
        int      rop     = 0;

        switch (voodoo->bltCommand & BLIT_SRC_FORMAT) {
            case BLIT_SRC_1BPP:
            case BLIT_SRC_1BPP_BYTE_PACKED:
                src_dat = (data & 1) ? voodoo->bltColorFg : voodoo->bltColorBg;
                data >>= 1;
                src_bits--;
                break;
            case BLIT_SRC_16BPP:
                switch (voodoo->bltCommand & BLIT_SRC_RGB_FORMAT) {
                    case BLIT_SRC_RGB_ARGB:
                    case BLIT_SRC_RGB_RGBA:
                        src_dat = data & 0xffff;
                        break;
                    case BLIT_SRC_RGB_ABGR:
                    case BLIT_SRC_RGB_BGRA:
                        src_dat = ((data & 0xf800) >> 11) | (data & 0x07c0) | ((data & 0x0038) << 11);
                        break;

                    default:
                        break;
                }
                data >>= 16;
                src_bits -= 16;
                break;
            case BLIT_SRC_24BPP:
            case BLIT_SRC_24BPP_DITHER_2X2:
            case BLIT_SRC_24BPP_DITHER_4X4:
                switch (voodoo->bltCommand & BLIT_SRC_RGB_FORMAT) {
                    case BLIT_SRC_RGB_ARGB:
                        r = (data >> 16) & 0xff;
                        g = (data >> 8) & 0xff;
                        b = data & 0xff;
                        break;
                    case BLIT_SRC_RGB_ABGR:
                        r = data & 0xff;
                        g = (data >> 8) & 0xff;
                        b = (data >> 16) & 0xff;
                        break;
                    case BLIT_SRC_RGB_RGBA:
                        r = (data >> 24) & 0xff;
                        g = (data >> 16) & 0xff;
                        b = (data >> 8) & 0xff;
                        break;
                    case BLIT_SRC_RGB_BGRA:
                        r = (data >> 8) & 0xff;
                        g = (data >> 16) & 0xff;
                        b = (data >> 24) & 0xff;
                        break;

                    default:
                        break;
                }
                switch (voodoo->bltCommand & BLIT_SRC_FORMAT) {
                    case BLIT_SRC_24BPP:
                        src_dat = (b >> 3) | ((g & 0xfc) << 3) | ((r & 0xf8) << 8);
                        break;
                    case BLIT_SRC_24BPP_DITHER_2X2:
                        r       = dither_rb2x2[r][voodoo->blt.dst_y & 1][x & 1];
                        g       = dither_g2x2[g][voodoo->blt.dst_y & 1][x & 1];
                        b       = dither_rb2x2[b][voodoo->blt.dst_y & 1][x & 1];
                        src_dat = (b >> 3) | ((g & 0xfc) << 3) | ((r & 0xf8) << 8);
                        break;
                    case BLIT_SRC_24BPP_DITHER_4X4:
                        r       = dither_rb[r][voodoo->blt.dst_y & 3][x & 3];
                        g       = dither_g[g][voodoo->blt.dst_y & 3][x & 3];
                        b       = dither_rb[b][voodoo->blt.dst_y & 3][x & 3];
                        src_dat = (b >> 3) | ((g & 0xfc) << 3) | ((r & 0xf8) << 8);
                        break;

                    default:
                        break;
                }
                src_bits = 0;
                break;

            default:
                break;
        }

        if (SLI_ENABLED) {
            if ((!(voodoo->initEnable & INITENABLE_SLI_MASTER_SLAVE) && (voodoo->blt.dst_y & 1)) || ((voodoo->initEnable & INITENABLE_SLI_MASTER_SLAVE) && !(voodoo->blt.dst_y & 1)))
                goto skip_pixel;
        }

        if (voodoo->bltCommand & BLIT_CLIPPING_ENABLED) {
            if (x < voodoo->bltClipLeft || x >= voodoo->bltClipRight || voodoo->blt.dst_y < voodoo->bltClipLowY || voodoo->blt.dst_y >= voodoo->bltClipHighY)
                goto skip_pixel;
        }

        dst_dat = dst[x];

        if (voodoo->bltCommand & BLIT_SRC_CHROMA) {
            r = (src_dat >> 11);
            g = (src_dat >> 5) & 0x3f;
            b = src_dat & 0x1f;

            if (r >= voodoo->bltSrcChromaMinR && r <= voodoo->bltSrcChromaMaxR && g >= voodoo->bltSrcChromaMinG && g <= voodoo->bltSrcChromaMaxG && b >= voodoo->bltSrcChromaMinB && b <= voodoo->bltSrcChromaMaxB)
                rop |= BLIT_ROP_SRC_PASS;
        }
        if (voodoo->bltCommand & BLIT_DST_CHROMA) {
            r = (dst_dat >> 11);
            g = (dst_dat >> 5) & 0x3f;
            b = dst_dat & 0x1f;

            if (r >= voodoo->bltDstChromaMinR && r <= voodoo->bltDstChromaMaxR && g >= voodoo->bltDstChromaMinG && g <= voodoo->bltDstChromaMaxG && b >= voodoo->bltDstChromaMinB && b <= voodoo->bltDstChromaMaxB)
                rop |= BLIT_ROP_DST_PASS;
        }

        MIX(src_dat, dst_dat, voodoo->bltRop[rop]);

        dst[x] = dst_dat;

skip_pixel:
        voodoo->blt.cur_x++;
    }

    if (voodoo->blt.cur_x > voodoo->blt.size_x) {
        voodoo->blt.size_y--;
        if (voodoo->blt.size_y >= 0) {
            voodoo->blt.cur_x = 0;
            voodoo->blt.dst_y += voodoo->blt.y_dir;
        }
    }
}

void
voodoo_fastfill(voodoo_t *voodoo, voodoo_params_t *params)
{
    int y;
    int low_y;
    int high_y;

    if (params->fbzMode & (1 << 17)) {
        int y_origin = (voodoo->type >= VOODOO_BANSHEE) ? (voodoo->y_origin_swap + 1) : voodoo->v_disp;

        high_y = y_origin - params->clipLowY;
        low_y  = y_origin - params->clipHighY;
    } else {
        low_y  = params->clipLowY;
        high_y = params->clipHighY;
    }

    if (params->fbzMode & FBZ_RGB_WMASK) {
        int      r;
        int      g;
        int      b;
        uint16_t col;

        r   = ((params->color1 >> 16) >> 3) & 0x1f;
        g   = ((params->color1 >> 8) >> 2) & 0x3f;
        b   = (params->color1 >> 3) & 0x1f;
        col = b | (g << 5) | (r << 11);

        if (SLI_ENABLED) {
            for (y = low_y; y < high_y; y += 2) {
                uint16_t *cbuf = (uint16_t *) &voodoo->fb_mem[(params->draw_offset + (y >> 1) * voodoo->row_width) & voodoo->fb_mask];

                for (int x = params->clipLeft; x < params->clipRight; x++)
                    cbuf[x] = col;
            }
        } else {
            for (y = low_y; y < high_y; y++) {
                if (voodoo->col_tiled) {
                    uint16_t *cbuf = (uint16_t *) &voodoo->fb_mem[(params->draw_offset + (y >> 5) * voodoo->row_width + (y & 31) * 128) & voodoo->fb_mask];

                    for (int x = params->clipLeft; x < params->clipRight; x++) {
                        int x2   = (x & 63) | ((x >> 6) * 128 * 32 / 2);
                        cbuf[x2] = col;
                    }
                } else {
                    uint16_t *cbuf = (uint16_t *) &voodoo->fb_mem[(params->draw_offset + y * voodoo->row_width) & voodoo->fb_mask];

                    for (int x = params->clipLeft; x < params->clipRight; x++)
                        cbuf[x] = col;
                }
                /* Mark line dirty for single buffer mode */
                if (params->draw_offset == params->front_offset && y < 2048)
                    voodoo->dirty_line[y] = 1;
            }
        }
    }
    if (params->fbzMode & FBZ_DEPTH_WMASK) {
        if (SLI_ENABLED) {
            for (y = low_y; y < high_y; y += 2) {
                uint16_t *abuf = (uint16_t *) &voodoo->fb_mem[(params->aux_offset + (y >> 1) * voodoo->row_width) & voodoo->fb_mask];

                for (int x = params->clipLeft; x < params->clipRight; x++)
                    abuf[x] = params->zaColor & 0xffff;
            }
        } else {
            for (y = low_y; y < high_y; y++) {
                if (voodoo->aux_tiled) {
                    uint16_t *abuf = (uint16_t *) &voodoo->fb_mem[(params->aux_offset + (y >> 5) * voodoo->aux_row_width + (y & 31) * 128) & voodoo->fb_mask];

                    for (int x = params->clipLeft; x < params->clipRight; x++) {
                        int x2   = (x & 63) | ((x >> 6) * 128 * 32 / 2);
                        abuf[x2] = params->zaColor & 0xffff;
                    }
                } else {
                    uint16_t *abuf = (uint16_t *) &voodoo->fb_mem[(params->aux_offset + y * voodoo->aux_row_width) & voodoo->fb_mask];

                    for (int x = params->clipLeft; x < params->clipRight; x++)
                        abuf[x] = params->zaColor & 0xffff;
                }
            }
        }
    }
}
