 /*
  * UAE - The Un*x Amiga Emulator
  * 
  * Custom chip emulation
  * 
  * (c) 1995 Bernd Schmidt, Alessandro Bissacco
  */

#include "sysconfig.h"
#include "sysdeps.h"

#include "config.h"
#include "options.h"
#include "events.h"
#include "memory.h"
#include "custom.h"
#include "newcpu.h"
#include "blitter.h"
#include "blit.h"

UWORD bltsize, oldvblts;
UWORD bltcon0,bltcon1;
ULONG bltapt,bltbpt,bltcpt,bltdpt;

int blinea_shift;
static UWORD blitlpos, blinea, blineb;
static CPTR bltcnxlpt,bltdnxlpt;
static int blitline,blitfc,blitfill,blitife,blitdesc,blitsing;
static int blitonedot,blitsign;
static long int bltwait;

struct bltinfo blt_info;

static UBYTE blit_filltable[256][4][2];

enum blitter_states bltstate;

void build_blitfilltable(void)
{
    unsigned int d, fillmask;
    int i;
    for (d = 0; d < 256; d++) {
	for (i = 0; i < 4; i++) {
	    int fc = i & 1;
	    UBYTE data = d;
	    for (fillmask = 1; fillmask != 0x100; fillmask <<= 1) {
		UWORD tmp = data;
		if (fc) {
		    if (i & 2)
			data |= fillmask;
		    else
			data ^= fillmask;
		}
		if (tmp & fillmask) fc = !fc;
	    }
	    blit_filltable[d][i][0] = data;
	    blit_filltable[d][i][1] = fc;
	}
    }
}

static __inline__ UBYTE *blit_xlateptr(CPTR bltpt, int bytecount)
{
    if (!chipmem_bank.check(bltpt,bytecount)) return NULL;
    return chipmem_bank.xlateaddr(bltpt);
}

static __inline__ UBYTE *blit_xlateptr_desc(CPTR bltpt, int bytecount)
{
    if (!chipmem_bank.check(bltpt-bytecount, bytecount)) return NULL;
    return chipmem_bank.xlateaddr(bltpt);
}

static void blitter_dofast(void) 
{
    int i,j;
    UBYTE *bltadatpt = 0, *bltbdatpt = 0, *bltcdatpt = 0, *bltddatpt = 0;
    UWORD blitahold, blitbhold, bltaold;
    UBYTE mt = bltcon0 & 0xFF;
    
    if (bltcon0 & 0x800) {
	bltadatpt = blit_xlateptr(bltapt, (blt_info.hblitsize*2+blt_info.bltamod)*blt_info.vblitsize);
	bltapt += (blt_info.hblitsize*2+blt_info.bltamod)*blt_info.vblitsize;
    }
    if (bltcon0 & 0x400) {
	bltbdatpt = blit_xlateptr(bltbpt, (blt_info.hblitsize*2+blt_info.bltbmod)*blt_info.vblitsize);
	bltbpt += (blt_info.hblitsize*2+blt_info.bltbmod)*blt_info.vblitsize;
    }
    if (bltcon0 & 0x200) {
	bltcdatpt = blit_xlateptr(bltcpt, (blt_info.hblitsize*2+blt_info.bltcmod)*blt_info.vblitsize);
	bltcpt += (blt_info.hblitsize*2+blt_info.bltcmod)*blt_info.vblitsize;
    }
    if (bltcon0 & 0x100) {
	bltddatpt = blit_xlateptr(bltdpt, (blt_info.hblitsize*2+blt_info.bltdmod)*blt_info.vblitsize);
	bltdpt += (blt_info.hblitsize*2+blt_info.bltdmod)*blt_info.vblitsize;
    }
    
    if (blitfunc_dofast[mt] && !blitfill) 
        (*blitfunc_dofast[mt])(bltadatpt,bltbdatpt,bltcdatpt,bltddatpt,&blt_info);
    else {
	/*if (!blitfill) fprintf(stderr, "minterm %x not present\n",mt); */
        for (j = 0; j < blt_info.vblitsize; j++) {
	    blitfc = !!(bltcon1 & 0x4);
	    for (i = 0; i < blt_info.hblitsize; i++) {
	        if (bltadatpt) { blt_info.bltadat = (*bltadatpt << 8) | *(bltadatpt + 1); bltadatpt += 2; }
	        if (bltbdatpt) { blt_info.bltbdat = (*bltbdatpt << 8) | *(bltbdatpt + 1); bltbdatpt += 2; }
		if (bltcdatpt) { blt_info.bltcdat = (*bltcdatpt << 8) | *(bltcdatpt + 1); bltcdatpt += 2; }
	        bltaold = blt_info.bltadat;
	        if (i == 0) bltaold &= blt_info.bltafwm;
	        if (i== blt_info.hblitsize-1) bltaold &= blt_info.bltalwm;
	        blitahold = (((ULONG)blt_info.blitpreva << 16) | bltaold) >> blt_info.blitashift;
	        blitbhold = (((ULONG)blt_info.blitprevb << 16) | blt_info.bltbdat) >> blt_info.blitbshift;
	        blt_info.bltddat = blit_func(blitahold, blitbhold, blt_info.bltcdat, mt);
	        if (blitfill) {
		    UWORD d = blt_info.bltddat;
		    int ifemode = blitife ? 2 : 0;
		    int fc1 = blit_filltable[d & 255][ifemode + blitfc][1];
		    blt_info.bltddat = (blit_filltable[d & 255][ifemode + blitfc][0] 
					+ (blit_filltable[d >> 8][ifemode + fc1][0] << 8));
		    blitfc = blit_filltable[d >> 8][ifemode + fc1][1];
		}
	        blt_info.blitpreva = bltaold; blt_info.blitprevb = blt_info.bltbdat;
	        if (blt_info.bltddat) blt_info.blitzero = 0;
	        if (bltddatpt) { *bltddatpt = blt_info.bltddat >> 8; *(bltddatpt+1) = blt_info.bltddat; bltddatpt += 2; }
	    }
	    if (bltadatpt) bltadatpt += blt_info.bltamod;
	    if (bltbdatpt) bltbdatpt += blt_info.bltbmod;
	    if (bltcdatpt) bltcdatpt += blt_info.bltcmod;
	    if (bltddatpt) bltddatpt += blt_info.bltdmod;
        }
    }
    bltstate = BLT_done;
}

static void blitter_dofast_desc(void) 
{
    int i,j;
    UBYTE *bltadatpt = 0, *bltbdatpt = 0, *bltcdatpt = 0, *bltddatpt = 0;
    UWORD blitahold, blitbhold, bltaold;
    UBYTE mt = bltcon0 & 0xFF;
    
    if (bltcon0 & 0x800) {
	bltadatpt = blit_xlateptr_desc(bltapt, (blt_info.hblitsize*2+blt_info.bltamod)*blt_info.vblitsize);
	bltapt -= (blt_info.hblitsize*2+blt_info.bltamod)*blt_info.vblitsize;
    }
    if (bltcon0 & 0x400) {
	bltbdatpt = blit_xlateptr_desc(bltbpt, (blt_info.hblitsize*2+blt_info.bltbmod)*blt_info.vblitsize);
	bltbpt -= (blt_info.hblitsize*2+blt_info.bltbmod)*blt_info.vblitsize;
    }
    if (bltcon0 & 0x200) {
	bltcdatpt = blit_xlateptr_desc(bltcpt, (blt_info.hblitsize*2+blt_info.bltcmod)*blt_info.vblitsize);
	bltcpt -= (blt_info.hblitsize*2+blt_info.bltcmod)*blt_info.vblitsize;
    }
    if (bltcon0 & 0x100) {
	bltddatpt = blit_xlateptr_desc(bltdpt, (blt_info.hblitsize*2+blt_info.bltdmod)*blt_info.vblitsize);
	bltdpt -= (blt_info.hblitsize*2+blt_info.bltdmod)*blt_info.vblitsize;
    }
    if (blitfunc_dofast_desc[mt] && !blitfill) 
	(*blitfunc_dofast_desc[mt])(bltadatpt,bltbdatpt,bltcdatpt,bltddatpt,&blt_info);
    else {
/*	if (!blitfill) fprintf(stderr, "minterm %x not present\n",mt);*/
        for (j = 0; j < blt_info.vblitsize; j++) {
	    blitfc = !!(bltcon1 & 0x4);
	    for (i = 0; i < blt_info.hblitsize; i++) {
		if (bltadatpt) { blt_info.bltadat = (*bltadatpt << 8) | *(bltadatpt + 1); bltadatpt -= 2; }
		if (bltbdatpt) { blt_info.bltbdat = (*bltbdatpt << 8) | *(bltbdatpt + 1); bltbdatpt -= 2; }
		if (bltcdatpt) { blt_info.bltcdat = (*bltcdatpt << 8) | *(bltcdatpt + 1); bltcdatpt -= 2; }
	        bltaold = blt_info.bltadat;
	        if (i == 0) bltaold &= blt_info.bltafwm;
	        if (i== blt_info.hblitsize-1) bltaold &= blt_info.bltalwm;
	        blitahold = (((ULONG)bltaold << 16) | blt_info.blitpreva) >> (16-blt_info.blitashift);
	        blitbhold = (((ULONG)blt_info.bltbdat << 16) | blt_info.blitprevb) >> (16-blt_info.blitbshift);
	        blt_info.bltddat = blit_func(blitahold, blitbhold, blt_info.bltcdat, mt);
	        if (blitfill) {
		    UWORD d = blt_info.bltddat;
		    int ifemode = blitife ? 2 : 0;
		    int fc1 = blit_filltable[d & 255][ifemode + blitfc][1];
		    blt_info.bltddat = (blit_filltable[d & 255][ifemode + blitfc][0]
					+ (blit_filltable[d >> 8][ifemode + fc1][0] << 8));
		    blitfc = blit_filltable[d >> 8][ifemode + fc1][1];
		}
	        blt_info.blitpreva = bltaold; blt_info.blitprevb = blt_info.bltbdat;
	        if (blt_info.bltddat) blt_info.blitzero = 0;
	        if (bltddatpt) { *bltddatpt = blt_info.bltddat >> 8; *(bltddatpt + 1) = blt_info.bltddat; bltddatpt -= 2; }
	    }
	    if (bltadatpt) bltadatpt -= blt_info.bltamod;
	    if (bltbdatpt) bltbdatpt -= blt_info.bltbmod;
	    if (bltcdatpt) bltcdatpt -= blt_info.bltcmod;
	    if (bltddatpt) bltddatpt -= blt_info.bltdmod;
        }
    }
    bltstate = BLT_done;
}

static __inline__ int blitter_read(void)
{
    if (bltcon0 & 0xe00){
	if (!dmaen(DMA_BLITTER)) return 1; /* blitter stopped */
	if (!blitline){
	    if (bltcon0 & 0x800) blt_info.bltadat = chipmem_bank.wget(bltapt);
	    if (bltcon0 & 0x400) blt_info.bltbdat = chipmem_bank.wget(bltbpt);
	}
	if (bltcon0 & 0x200) blt_info.bltcdat = chipmem_bank.wget(bltcpt);
    }
    bltstate = BLT_work;
    return (bltcon0 & 0xE00) != 0;
}

static __inline__ int blitter_write(void)
{
    if (blt_info.bltddat) blt_info.blitzero = 0;
    if ((bltcon0 & 0x100) || blitline){
	if (!dmaen(DMA_BLITTER)) return 1;
	chipmem_bank.wput(bltdpt, blt_info.bltddat);
    }
    bltstate = BLT_next;
    return (bltcon0 & 0x100) != 0;
}

static void blitter_blit(void)
{
    UWORD blitahold,blitbhold,blitchold;
    UWORD bltaold;
    
    if (blitdesc) {
	UWORD bltamask = 0xffff;
	
	if (!blitlpos) { bltamask &= blt_info.bltafwm; }
	if (blitlpos == (blt_info.hblitsize - 1)) { bltamask &= blt_info.bltalwm; }
	bltaold = blt_info.bltadat & bltamask;

	blitahold = (((ULONG)bltaold << 16) | blt_info.blitpreva) >> (16-blt_info.blitashift);
	blitbhold = (((ULONG)blt_info.bltbdat << 16) | blt_info.blitprevb) >> (16-blt_info.blitbshift);
	blitchold = blt_info.bltcdat;
    } else {
	UWORD bltamask = 0xffff;
	
	if (!blitlpos) { bltamask &= blt_info.bltafwm; }
	if (blitlpos == (blt_info.hblitsize - 1)) { bltamask &= blt_info.bltalwm; }
	bltaold = blt_info.bltadat & bltamask;

	blitahold = (((ULONG)blt_info.blitpreva << 16) | bltaold) >> blt_info.blitashift;
	blitbhold = (((ULONG)blt_info.blitprevb << 16) | blt_info.bltbdat) >> blt_info.blitbshift;
	blitchold = blt_info.bltcdat;
    }
    blt_info.bltddat = 0;
    blt_info.bltddat = blit_func(blitahold, blitbhold, blitchold, bltcon0 & 0xFF);
    if (blitfill){
	UWORD fillmask;
	for (fillmask = 1; fillmask; fillmask <<= 1){
	    UWORD tmp = blt_info.bltddat;
	    if (blitfc) {
		if (blitife)
		    blt_info.bltddat |= fillmask;
		else
		    blt_info.bltddat ^= fillmask;
	    }
	    if (tmp & fillmask) blitfc = !blitfc;
	}
    }
    bltstate = BLT_write;
    blt_info.blitpreva = bltaold; blt_info.blitprevb = blt_info.bltbdat;
}

static void blitter_nxblit(void)
{
    bltstate = BLT_read;
    if (blitdesc){
	if (++blitlpos == blt_info.hblitsize) {
	    if (--blt_info.vblitsize == 0) {
		bltstate = BLT_done;
#if FAST_BLITTER == 0
		custom_bank.wput(0xDFF09C,0x8040);
#endif
	    }
	    blitfc = bltcon1 & 0x4;

	    blitlpos = 0;
	    if (bltcon0 & 0x800) bltapt -= 2+blt_info.bltamod; 
	    if (bltcon0 & 0x400) bltbpt -= 2+blt_info.bltbmod; 
	    if (bltcon0 & 0x200) bltcpt -= 2+blt_info.bltcmod; 
	    if (bltcon0 & 0x100) bltdpt -= 2+blt_info.bltdmod;
	} else {
	    if (bltcon0 & 0x800) bltapt -= 2; 
	    if (bltcon0 & 0x400) bltbpt -= 2; 
	    if (bltcon0 & 0x200) bltcpt -= 2; 
	    if (bltcon0 & 0x100) bltdpt -= 2;	    
	}
    } else {
	if (++blitlpos == blt_info.hblitsize) {
	    if (--blt_info.vblitsize == 0) { 
		bltstate = BLT_done;
#if FAST_BLITTER == 0
		custom_bank.wput(0xDFF09C,0x8040);
#endif
	    }
	    blitlpos = 0;
	    if (bltcon0 & 0x800) bltapt += 2+blt_info.bltamod; 
	    if (bltcon0 & 0x400) bltbpt += 2+blt_info.bltbmod; 
	    if (bltcon0 & 0x200) bltcpt += 2+blt_info.bltcmod; 
	    if (bltcon0 & 0x100) bltdpt += 2+blt_info.bltdmod;
	} else {
	    if (bltcon0 & 0x800) bltapt += 2; 
	    if (bltcon0 & 0x400) bltbpt += 2; 
	    if (bltcon0 & 0x200) bltcpt += 2; 
	    if (bltcon0 & 0x100) bltdpt += 2;
	}
    }
}

static __inline__ void blitter_line_incx(void)
{
    if (++blinea_shift == 16) {
	blinea_shift = 0;
	bltcnxlpt += 2;
	bltdnxlpt += 2;
    }
}

static __inline__ void blitter_line_decx(void)
{
    if (blinea_shift-- == 0) {
	blinea_shift = 15;
	bltcnxlpt -= 2;
	bltdnxlpt -= 2;
    }
}

static __inline__ void blitter_line_decy(void)
{
    bltcnxlpt -= blt_info.bltcmod;
    bltdnxlpt -= blt_info.bltcmod; /* ??? am I wrong or doesn't KS1.3 set bltdmod? */
    blitonedot = 0;
}

static __inline__ void blitter_line_incy(void)
{
    bltcnxlpt += blt_info.bltcmod;
    bltdnxlpt += blt_info.bltcmod; /* ??? */
    blitonedot = 0;
}

static void blitter_line(void)
{
    UWORD blitahold = blinea >> blinea_shift, blitbhold = blineb & 1 ? 0xFFFF : 0, blitchold = blt_info.bltcdat;
    blt_info.bltddat = 0;
    
    if (blitsing && blitonedot) blitahold = 0;
    blitonedot = 1;
    blt_info.bltddat = blit_func(blitahold, blitbhold, blitchold, bltcon0 & 0xFF);
    if (!blitsign){
	bltapt += (WORD)blt_info.bltamod;
	if (bltcon1 & 0x10){
	    if (bltcon1 & 0x8)
	    	blitter_line_decy();
	    else
	    	blitter_line_incy();
	} else {
	    if (bltcon1 & 0x8)
	    	blitter_line_decx();
	    else 
	    	blitter_line_incx();
	}
    } else {
	bltapt += (WORD)blt_info.bltbmod;
    }
    if (bltcon1 & 0x10){
	if (bltcon1 & 0x4)
	    blitter_line_decx();
	else
	    blitter_line_incx();
    } else {
	if (bltcon1 & 0x4)
	    blitter_line_decy();
	else
	    blitter_line_incy();
    }
    blitsign = 0 > (WORD)bltapt;
    bltstate = BLT_write;
}

static __inline__ void blitter_nxline(void)
{
    bltcpt = bltcnxlpt;
    bltdpt = bltdnxlpt;
    blineb = (blineb << 1) | (blineb >> 15);
    if (--blt_info.vblitsize == 0) {
	bltstate = BLT_done;
#if FAST_BLITTER == 0
	custom_bank.wput(0xDFF09C,0x8040);
#endif
    } else {
	bltstate = BLT_read;
    }
}

static void blit_init(void)
{
    blitlpos = 0;
    blt_info.blitzero = 1; blt_info.blitpreva = blt_info.blitprevb = 0;
    blitline = bltcon1 & 1;
    blt_info.blitashift = bltcon0 >> 12; blt_info.blitbshift = bltcon1 >> 12;
    
    if (blitline) {
	if (blt_info.hblitsize != 2)
	    fprintf(stderr, "weird hblitsize in linemode: %d\n", blt_info.hblitsize);
	bltcnxlpt = bltcpt;
	bltdnxlpt = bltdpt;
	blitsing = bltcon1 & 0x2;
	blinea = blt_info.bltadat;
	blineb = (blt_info.bltbdat >> blt_info.blitbshift) | (blt_info.bltbdat << (16-blt_info.blitbshift));
#if 0
	if (blineb != 0xFFFF && blineb != 0)
	    fprintf(stderr, "%x %x %d %x\n", blineb, blt_info.bltbdat, blt_info.blitbshift, bltcon1);
#endif
	blitsign = bltcon1 & 0x40; 
	blitonedot = 0;
    } else {
	blitfc = !!(bltcon1 & 0x4);
	blitife = bltcon1 & 0x8;
	blitfill = bltcon1 & 0x18;
	if ((bltcon1 & 0x18) == 0x18) {
	    /* Digital "Trash" demo does this; others too. Apparently, no
	     * negative effects. */
	    static int warn = 1;
	    if (warn)
	    	fprintf(stderr, "warning: weird fill mode (further messages suppressed)\n");
	    warn = 0;
	}
	blitdesc = bltcon1 & 0x2;
	if (blitfill && !blitdesc)
	    fprintf(stderr, "warning: blitter fill without desc\n");
    }
}

static void actually_do_blit(void)
{
    if (blitline) {
	do {
	    blitter_read();
	    blitter_line();
	    blitter_write();
	    blitter_nxline();
	} while (bltstate != BLT_done);
    } else {
	/*blitcount[bltcon0 & 0xff]++;  blitter debug */
	if (blitdesc) blitter_dofast_desc();
	else blitter_dofast();
    }
}

void blitter_handler(void)
{
    if (!dmaen(DMA_BLITTER)) {
	eventtab[ev_blitter].active = 1;
	eventtab[ev_blitter].oldcycles = cycles;
	eventtab[ev_blitter].evtime = 10 + cycles; /* wait a little */
	return; /* gotta come back later. */
    }
    actually_do_blit();
    
    INTREQ(0x8040);
    eventtab[ev_blitter].active = 0;
    regs.spcflags &= ~SPCFLAG_BLTNASTY;
}

void do_blitter(void)
{
#if FAST_BLITTER == 0
    /* I'm not sure all this bltstate stuff is really necessary.
     * Most programs should be OK if the blit is done as soon as BLTSIZE is
     * written to, and the BLTFINISH bit is set some time after that.
     * This code here is nowhere near exact.
     */
    do {	
	switch(bltstate) {
	 case BLT_init:
	    blit_init();
	    bltstate = BLT_read;
	    /* fall through */
	 case BLT_read:
	    if (blitter_read())
	    	break;
	    /* fall through */
	 case BLT_work:
	    if (blitline)
	    	blitter_line(); 
	    else 
	    	blitter_blit();
	    /* fall through */
	 case BLT_write:
	    if (blitter_write())
	    	break;
	    /* fall through */
	 case BLT_next:
	    if (blitline)
	    	blitter_nxline();
	    else 
	    	blitter_nxblit();
	    break;
	 case BLT_done:
	    regs.spcflags &= ~SPCFLAG_BLIT;
	    break;
	}
    } while(bltstate != BLT_done && dmaen(DMA_BLITTER)
	    && dmaen(DMA_BLITPRI));  /* blitter nasty -> no time for CPU */
#else
    {
#if FAST_BLITTER < 4
	long int blit_cycles = 2;

	if (!blitline) {
	    if (bltcon0 & 0x400)
		blit_cycles++;
	    if ((bltcon0 & 0x300) == 0x300)
		blit_cycles++;
	    blit_cycles *= blt_info.vblitsize * blt_info.hblitsize;
	}
#else
	long int blit_cycles = 1;
#endif
	blit_init();
	
	eventtab[ev_blitter].active = 1;
	eventtab[ev_blitter].oldcycles = cycles;
	eventtab[ev_blitter].evtime = blit_cycles + cycles;
	events_schedule();

	regs.spcflags &= ~SPCFLAG_BLIT;
	if (dmaen(DMA_BLITPRI))
	    regs.spcflags |= SPCFLAG_BLTNASTY;
    }
#endif
}

void maybe_blit(void)
{
    static int warned = 0;
    if (bltstate == BLT_done)
	return;
    
    if (!warned) {
	warned = 1;
	fprintf(stderr, "warning: Program does not wait for blitter (no further messages)\n");
    }
    if (!eventtab[ev_blitter].active)
	printf("FOO!!?\n");
    actually_do_blit();
    eventtab[ev_blitter].active = 0;
    regs.spcflags &= ~SPCFLAG_BLTNASTY;
}
