/*
 * VARCem	Virtual ARchaeological Computer EMulator.
 *		An emulator of (mostly) x86-based PC systems and devices,
 *		using the ISA,EISA,VLB,MCA  and PCI system buses, roughly
 *		spanning the era between 1981 and 1995.
 *
 *		This file is part of the VARCem Project.
 *
 *		The below is (with some removals) a reasonable emulation
 *		of the ET4000/W32i blitter.  Unfortunately the Diamond
 *		Stealth 32 is actually an ET4000/W32p! Which has a different
 *		blitter. If only I'd dug out and looked at the card before
 *		trying to emulate it.
 *
 *		This might be of use for an attempt at an ET4000/W32i.
 *
 * Version:	@(#)vid_et4000w32i.c	1.0.3	2018/09/15
 *
 * Author:	Sarah Walker, <tommowalker@tommowalker.co.uk>
 *
 *		Copyright 2008-2018 Sarah Walker.
 *
 * 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 <stdio.h>
#include <stdint.h>
#include <string.h>
#include <wchar.h>
#include "../../emu.h"


int et4k_b8000;


struct
{
        struct
        {
                uint32_t pattern_addr,source_addr,dest_addr;
                uint16_t pattern_off,source_off,dest_off;
                uint8_t vbus,xy_dir;
                uint8_t pattern_wrap,source_wrap;
                uint16_t count_x,count_y;
                uint8_t ctrl_routing,ctrl_reload;
                uint8_t rop_fg,rop_bg;
                uint16_t pos_x,pos_y;
        } queued,internal;
        uint32_t pattern_addr,source_addr,dest_addr;
        uint32_t pattern_back,dest_back;
        int pattern_x,source_x;
        int pattern_x_back;
        int pattern_y,source_y;
        uint8_t status;
        uint32_t cpu_input;
        int cpu_input_num;
} acl;

#define ACL_WRST 1
#define ACL_RDST 2
#define ACL_XYST 4
#define ACL_SSO  8

struct
{
        uint32_t base[3];
        uint8_t ctrl;
} mmu;

void et4000w32_reset()
{
        acl.status=0;
        acl.cpu_input_num=0;
}

void et4000w32_blit_start();
void et4000w32_blit(int count, uint32_t mix, uint32_t sdat, int cpu_input);

int et4000w32_vbus[4]={1,2,4,4};

void et4000w32_mmu_write(uint32_t addr, uint8_t val)
{
        int bank;
        DEBUG("ET4K write %08X %02X %i %02X %02X %04X(%08X):%08X  %04X %04X %02X %08X\n",addr,val,acl.cpu_input_num,acl.status,acl.internal.ctrl_routing,CS,cs,pc,CS,DI,mmu.ctrl,mmu.base[2]);
        switch (addr&0x6000)
        {
                case 0x0000: /*MMU 0*/
                case 0x2000: /*MMU 1*/
                case 0x4000: /*MMU 2*/
                bank=(addr>>13)&3;
                if (mmu.ctrl&(1<<bank))
                {
                        if (!(acl.status&ACL_XYST))
                        {
//                                DEBUG("!ACL_XYST\n");
                                /*if ((acl.internal.ctrl_routing&0x30)==0x10) */acl.queued.dest_addr=(addr&0x1FFF)+mmu.base[bank];
                                acl.internal=acl.queued;
                                et4000w32_blit_start();
                                if (!(acl.internal.ctrl_routing&0x37)) et4000w32_blit(0xFFFFFF, ~0, 0, 0);
                                acl.cpu_input_num=0;
                        }
//                        else if (!(acl.internal.ctrl_routing&7)) DEBUG("ACL_XYST\n");
                        if (acl.internal.ctrl_routing&7)
                        {
                                acl.cpu_input=(acl.cpu_input&~(0xFF<<(acl.cpu_input_num*8)))|(val<<(acl.cpu_input_num*8));
                                acl.cpu_input_num++;
                                if (acl.cpu_input_num == et4000w32_vbus[acl.internal.vbus & 3])
                                {
                                        if ((acl.internal.ctrl_routing&7)==2)
                                           et4000w32_blit(acl.cpu_input_num << 3, acl.cpu_input, 0, 1);
                                        else if ((acl.internal.ctrl_routing&7)==1)
                                           et4000w32_blit(acl.cpu_input_num, ~0, acl.cpu_input, 2);
                                        else
                                           DEBUG("Bad ET4K routing %i\n",acl.internal.ctrl_routing&7);
                                        acl.cpu_input_num=0;
                                }
                        }
//                        else
//                           DEBUG("Not ctrl_routing\n");
                }
                else
                {
                        vram[(addr&0x1FFF)+mmu.base[bank]]=val;
                        changedvram[((addr&0x1FFF)+mmu.base[bank])>>12]=changeframecount;
                }
                break;
                case 0x6000:
                switch (addr&0x7FFF)
                {
                        case 0x7F00: mmu.base[0]=(mmu.base[0]&0xFFFFFF00)|val;       break;
                        case 0x7F01: mmu.base[0]=(mmu.base[0]&0xFFFF00FF)|(val<<8);  break;
                        case 0x7F02: mmu.base[0]=(mmu.base[0]&0xFF00FFFF)|(val<<16); break;
                        case 0x7F03: mmu.base[0]=(mmu.base[0]&0x00FFFFFF)|(val<<24); break;
                        case 0x7F04: mmu.base[1]=(mmu.base[1]&0xFFFFFF00)|val;       break;
                        case 0x7F05: mmu.base[1]=(mmu.base[1]&0xFFFF00FF)|(val<<8);  break;
                        case 0x7F06: mmu.base[1]=(mmu.base[1]&0xFF00FFFF)|(val<<16); break;
                        case 0x7F07: mmu.base[1]=(mmu.base[1]&0x00FFFFFF)|(val<<24); break;
                        case 0x7F08: mmu.base[2]=(mmu.base[2]&0xFFFFFF00)|val;       break;
                        case 0x7F09: mmu.base[2]=(mmu.base[2]&0xFFFF00FF)|(val<<8);  break;
                        case 0x7F0A: mmu.base[2]=(mmu.base[2]&0xFF00FFFF)|(val<<16); break;
                        case 0x7F0B: mmu.base[2]=(mmu.base[2]&0x00FFFFFF)|(val<<24); break;
                        case 0x7F13: mmu.ctrl=val; break;

                        case 0x7F80: acl.queued.pattern_addr=(acl.queued.pattern_addr&0xFFFFFF00)|val;       break;
                        case 0x7F81: acl.queued.pattern_addr=(acl.queued.pattern_addr&0xFFFF00FF)|(val<<8);  break;
                        case 0x7F82: acl.queued.pattern_addr=(acl.queued.pattern_addr&0xFF00FFFF)|(val<<16); break;
                        case 0x7F83: acl.queued.pattern_addr=(acl.queued.pattern_addr&0x00FFFFFF)|(val<<24); break;
                        case 0x7F84: acl.queued.source_addr =(acl.queued.source_addr &0xFFFFFF00)|val;       break;
                        case 0x7F85: acl.queued.source_addr =(acl.queued.source_addr &0xFFFF00FF)|(val<<8);  break;
                        case 0x7F86: acl.queued.source_addr =(acl.queued.source_addr &0xFF00FFFF)|(val<<16); break;
                        case 0x7F87: acl.queued.source_addr =(acl.queued.source_addr &0x00FFFFFF)|(val<<24); break;
                        case 0x7F88: acl.queued.pattern_off=(acl.queued.pattern_off&0xFF00)|val;      break;
                        case 0x7F89: acl.queued.pattern_off=(acl.queued.pattern_off&0x00FF)|(val<<8); break;
                        case 0x7F8A: acl.queued.source_off =(acl.queued.source_off &0xFF00)|val;      break;
                        case 0x7F8B: acl.queued.source_off =(acl.queued.source_off &0x00FF)|(val<<8); break;
                        case 0x7F8C: acl.queued.dest_off   =(acl.queued.dest_off   &0xFF00)|val;      break;
                        case 0x7F8D: acl.queued.dest_off   =(acl.queued.dest_off   &0x00FF)|(val<<8); break;
                        case 0x7F8E: acl.queued.vbus=val; break;
                        case 0x7F8F: acl.queued.xy_dir=val; break;
                        case 0x7F90: acl.queued.pattern_wrap=val; break;
                        case 0x7F92: acl.queued.source_wrap=val; break;
                        case 0x7F98: acl.queued.count_x    =(acl.queued.count_x    &0xFF00)|val;      break;
                        case 0x7F99: acl.queued.count_x    =(acl.queued.count_x    &0x00FF)|(val<<8); break;
                        case 0x7F9A: acl.queued.count_y    =(acl.queued.count_y    &0xFF00)|val;      break;
                        case 0x7F9B: acl.queued.count_y    =(acl.queued.count_y    &0x00FF)|(val<<8); break;
                        case 0x7F9C: acl.queued.ctrl_routing=val; break;
                        case 0x7F9D: acl.queued.ctrl_reload =val; break;
                        case 0x7F9E: acl.queued.rop_bg      =val; break;
                        case 0x7F9F: acl.queued.rop_fg      =val; break;
                        case 0x7FA0: acl.queued.dest_addr   =(acl.queued.dest_addr   &0xFFFFFF00)|val;       break;
                        case 0x7FA1: acl.queued.dest_addr   =(acl.queued.dest_addr   &0xFFFF00FF)|(val<<8);  break;
                        case 0x7FA2: acl.queued.dest_addr   =(acl.queued.dest_addr   &0xFF00FFFF)|(val<<16); break;
                        case 0x7FA3: acl.queued.dest_addr   =(acl.queued.dest_addr   &0x00FFFFFF)|(val<<24);
                        acl.internal=acl.queued;
                        et4000w32_blit_start();
                        acl.cpu_input_num=0;
                        if (!(acl.queued.ctrl_routing&0x37))
                        {
                                et4000w32_blit(0xFFFFFF, ~0, 0, 0);
                        }
                        break;
                }
                break;
        }
}

uint8_t et4000w32_mmu_read(uint32_t addr)
{
        int bank;
        DEBUG("ET4K read %08X %04X(%08X):%08X\n",addr,CS,cs,pc);
        switch (addr&0x6000)
        {
                case 0x0000: /*MMU 0*/
                case 0x2000: /*MMU 1*/
                case 0x4000: /*MMU 2*/
                bank=(addr>>13)&3;
                if (mmu.ctrl&(1<<bank))
                {
                        /*???*/
                        return 0xFF;
                }
                return vram[(addr&0x1FFF)+mmu.base[bank]];
                case 0x6000:
                switch (addr&0x7FFF)
                {
                        case 0x7F00: return mmu.base[0];
                        case 0x7F01: return mmu.base[0]>>8;
                        case 0x7F02: return mmu.base[0]>>16;
                        case 0x7F03: return mmu.base[0]>>24;
                        case 0x7F04: return mmu.base[1];
                        case 0x7F05: return mmu.base[1]>>8;
                        case 0x7F06: return mmu.base[1]>>16;
                        case 0x7F07: return mmu.base[1]>>24;
                        case 0x7F08: return mmu.base[2];
                        case 0x7F09: return mmu.base[2]>>8;
                        case 0x7F0A: return mmu.base[2]>>16;
                        case 0x7F0B: return mmu.base[2]>>24;
                        case 0x7F13: return mmu.ctrl;

                        case 0x7F36:
//                        if (acl.internal.pos_x!=acl.internal.count_x || acl.internal.pos_y!=acl.internal.count_y) return acl.status | ACL_XYST;
                        return acl.status & ~(ACL_XYST | ACL_SSO);
                        case 0x7F80: return acl.internal.pattern_addr;
                        case 0x7F81: return acl.internal.pattern_addr>>8;
                        case 0x7F82: return acl.internal.pattern_addr>>16;
                        case 0x7F83: return acl.internal.pattern_addr>>24;
                        case 0x7F84: return acl.internal.source_addr;
                        case 0x7F85: return acl.internal.source_addr>>8;
                        case 0x7F86: return acl.internal.source_addr>>16;
                        case 0x7F87: return acl.internal.source_addr>>24;
                        case 0x7F88: return acl.internal.pattern_off;
                        case 0x7F89: return acl.internal.pattern_off>>8;
                        case 0x7F8A: return acl.internal.source_off;
                        case 0x7F8B: return acl.internal.source_off>>8;
                        case 0x7F8C: return acl.internal.dest_off;
                        case 0x7F8D: return acl.internal.dest_off>>8;
                        case 0x7F8E: return acl.internal.vbus;
                        case 0x7F8F: return acl.internal.xy_dir;
                        case 0x7F90: return acl.internal.pattern_wrap;
                        case 0x7F92: return acl.internal.source_wrap;
                        case 0x7F98: return acl.internal.count_x;
                        case 0x7F99: return acl.internal.count_x>>8;
                        case 0x7F9A: return acl.internal.count_y;
                        case 0x7F9B: return acl.internal.count_y>>8;
                        case 0x7F9C: return acl.internal.ctrl_routing;
                        case 0x7F9D: return acl.internal.ctrl_reload;
                        case 0x7F9E: return acl.internal.rop_bg;
                        case 0x7F9F: return acl.internal.rop_fg;
                        case 0x7FA0: return acl.internal.dest_addr;
                        case 0x7FA1: return acl.internal.dest_addr>>8;
                        case 0x7FA2: return acl.internal.dest_addr>>16;
                        case 0x7FA3: return acl.internal.dest_addr>>24;
                }
                return 0xFF;
        }
}

int et4000w32_wrap_x[8]={0,0,3,7,15,31,63,0xFFFFFFFF};
int et4000w32_wrap_y[8]={1,2,4,8,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF};

void et4000w32_blit_start()
{
        DEBUG("Blit - %08X %08X %08X (%i,%i) %i  %i %i  %02X %02X  %02X\n",acl.internal.pattern_addr,acl.internal.source_addr,acl.internal.dest_addr,acl.internal.dest_addr%640,acl.internal.dest_addr/640,acl.internal.xy_dir,acl.internal.count_x,acl.internal.count_y,acl.internal.rop_fg,acl.internal.rop_bg, acl.internal.ctrl_routing);
        acl.pattern_addr=acl.internal.pattern_addr;
        acl.source_addr =acl.internal.source_addr;
        acl.dest_addr   =acl.internal.dest_addr;
        acl.dest_back   =acl.dest_addr;
        acl.internal.pos_x=acl.internal.pos_y=0;
        acl.pattern_x=acl.source_x=acl.pattern_y=acl.source_y=0;
        acl.status = ACL_XYST;
        if (!(acl.internal.ctrl_routing&7) || (acl.internal.ctrl_routing&4)) acl.status |= ACL_SSO;
        if (et4000w32_wrap_x[acl.internal.pattern_wrap&7])
        {
                acl.pattern_x=acl.pattern_addr&et4000w32_wrap_x[acl.internal.pattern_wrap&7];
                acl.pattern_addr&=~et4000w32_wrap_x[acl.internal.pattern_wrap&7];
        }
        if (!(acl.internal.pattern_wrap&0x80))
        {
                acl.pattern_y=(acl.pattern_addr/(et4000w32_wrap_x[acl.internal.pattern_wrap&7]+1))&(et4000w32_wrap_y[(acl.internal.pattern_wrap>>4)&7]-1);
                acl.pattern_addr&=~(((et4000w32_wrap_x[acl.internal.pattern_wrap&7]+1)*et4000w32_wrap_y[(acl.internal.pattern_wrap>>4)&7])-1);
        }
        acl.pattern_x_back=acl.pattern_x;
        acl.pattern_back=acl.pattern_addr;
}

void et4000w32_blit(int count, uint32_t mix, uint32_t sdat, int cpu_input)
{
        int c,d;
        uint8_t pattern,source,dest,out;
        uint8_t rop;

//        if (count>400) DEBUG("New blit - %i,%i %06X (%i,%i) %06X %06X\n",acl.internal.count_x,acl.internal.count_y,acl.dest_addr,acl.dest_addr%640,acl.dest_addr/640,acl.source_addr,acl.pattern_addr);
//        DEBUG("Blit exec - %i %i %i\n",count,acl.internal.pos_x,acl.internal.pos_y);
        while (count--)
        {
                DEBUG("%i,%i : ",acl.internal.pos_x,acl.internal.pos_y);
                if (acl.internal.xy_dir&1)
                {
                        pattern=vram[(acl.pattern_addr-acl.pattern_x)&0x1FFFFF];
                        source =vram[(acl.source_addr -acl.source_x) &0x1FFFFF];
                        DEBUG("%06X %06X ",(acl.pattern_addr-acl.pattern_x)&0x1FFFFF,(acl.source_addr -acl.source_x) &0x1FFFFF);
                }
                else
                {
                        pattern=vram[(acl.pattern_addr+acl.pattern_x)&0x1FFFFF];
                        source =vram[(acl.source_addr +acl.source_x) &0x1FFFFF];
                        DEBUG("%06X %06X ",(acl.pattern_addr+acl.pattern_x)&0x1FFFFF,(acl.source_addr +acl.source_x) &0x1FFFFF);
                }
                if (cpu_input==2)
                {
                        source=sdat&0xFF;
                        sdat>>=8;
                }
                dest=vram[acl.dest_addr   &0x1FFFFF];
                out=0;
                DEBUG("%06X %i %08X   ",acl.dest_addr,mix&1,mix);
                rop = (mix & 1) ? acl.internal.rop_fg:acl.internal.rop_bg;
                mix>>=1; mix|=0x80000000;
                for (c=0;c<8;c++)
                {
                        d=(dest & (1<<c)) ? 1:0;
                        if (source & (1<<c))  d|=2;
                        if (pattern & (1<<c)) d|=4;
                        if (rop & (1<<d)) out|=(1<<c);
                }
                DEBUG("%06X = %02X\n",acl.dest_addr&0x1FFFFF,out);
                vram[acl.dest_addr&0x1FFFFF]=out;
                changedvram[(acl.dest_addr&0x1FFFFF)>>12]=changeframecount;

                acl.pattern_x++;
                acl.pattern_x&=et4000w32_wrap_x[acl.internal.pattern_wrap&7];
                acl.source_x++;
                acl.source_x &=et4000w32_wrap_x[acl.internal.source_wrap&7];
                if (acl.internal.xy_dir&1) acl.dest_addr--;
                else                       acl.dest_addr++;

                acl.internal.pos_x++;
                if (acl.internal.pos_x>acl.internal.count_x)
                {
                        if (acl.internal.xy_dir&2)
                        {
                                acl.pattern_addr-=(acl.internal.pattern_off+1);
                                acl.source_addr -=(acl.internal.source_off +1);
                                acl.dest_back=acl.dest_addr=acl.dest_back-(acl.internal.dest_off+1);
                        }
                        else
                        {
                                acl.pattern_addr+=acl.internal.pattern_off+1;
                                acl.source_addr +=acl.internal.source_off +1;
                                acl.dest_back=acl.dest_addr=acl.dest_back+acl.internal.dest_off+1;
                        }
                        acl.pattern_x = acl.pattern_x_back;
                        acl.source_x = 0;
                        acl.pattern_y++;
                        if (acl.pattern_y==et4000w32_wrap_y[(acl.internal.pattern_wrap>>4)&7])
                        {
                                acl.pattern_y=0;
                                acl.pattern_addr=acl.pattern_back;
                        }
                        acl.source_y++;
                        if (acl.source_y ==et4000w32_wrap_y[(acl.internal.source_wrap >>4)&7])
                        {
                                acl.source_y=0;
                                acl.source_addr=acl.internal.source_addr;
                        }

                        acl.internal.pos_y++;
                        if (acl.internal.pos_y>acl.internal.count_y)
                        {
                                acl.status = 0;
                                return;
                        }
                        acl.internal.pos_x=0;
                        if (cpu_input) return;
                }
        }
}

/*        for (y=0;y<=acl.internal.count_y;y++)
        {
                dest_back=acl.dest_addr;
                for (x=0;x<=acl.internal.count_x;x++)
                {
                        if (acl.internal.xy_dir&1)
                        {
                                pattern=vram[(acl.pattern_addr-pattern_x)&0x1FFFFF];
                                source =vram[(acl.source_addr -source_x) &0x1FFFFF];
                        }
                        else
                        {
                                pattern=vram[(acl.pattern_addr+pattern_x)&0x1FFFFF];
                                source =vram[(acl.source_addr +source_x) &0x1FFFFF];
                        }
                        dest=vram[acl.dest_addr   &0x1FFFFF];
                        out=0;
                        for (c=0;c<8;c++)
                        {
                                d=(dest&(1<<c))?1:0;
                                if (source&(1<<c))  d|=2;
                                if (pattern&(1<<c)) d|=4;
                                if (acl.internal.rop_bg&(1<<d)) out|=(1<<c);
                        }
                        vram[acl.dest_addr&0x1FFFFF]=out;
                        changedvram[(acl.dest_addr&0x1FFFFF)>>12]=changeframecount;

                        pattern_x++;
                        pattern_x&=et4000w32_wrap_x[acl.internal.pattern_wrap&7];
                        source_x++;
                        source_x &=et4000w32_wrap_x[acl.internal.source_wrap&7];
                        if (acl.internal.xy_dir&1) acl.dest_addr--;
                        else                       acl.dest_addr++;
                }
                acl.pattern_addr+=acl.internal.pattern_off+1;
                acl.source_addr +=acl.internal.source_off+1;
                acl.dest_addr=dest_back+acl.internal.dest_off+1;
                pattern_y++;
                if (pattern_y==et4000w32_wrap_y[(acl.internal.pattern_wrap>>4)&7])
                {
                        pattern_y=0;
                        acl.pattern_addr=acl.internal.pattern_addr;
                }
                source_y++;
                if (source_y ==et4000w32_wrap_y[(acl.internal.source_wrap >>4)&7])
                {
                        source_y=0;
                        acl.source_addr=acl.internal.source_addr;
                }
        }*/
