#include <stdio.h>
#include <stdint.h>
#include "ibm.h"
#include "disc.h"
#include "disc_fdi.h"
#include "fdc.h"
#include "fdi2raw.h"

static struct
{
        FILE *f;
        FDI *h;
        uint8_t track_data[2][4][256*1024];
        
        int sides;
        int tracklen[2][4];
        int trackindex[2][4];
        
        int lasttrack;
} fdi[2];

static uint8_t fdi_timing[256*1024];
static int fdi_pos;
static int fdi_revs;

static int fdi_sector, fdi_track,   fdi_side,    fdi_drive, fdi_density, fdi_n;
static int fdi_inread, fdi_inwrite, fdi_readpos, fdi_inreadaddr;

static uint16_t CRCTable[256];

static int pollbytesleft=0,pollbitsleft=0;

static void fdi_setupcrc(uint16_t poly, uint16_t rvalue)
{
	int c = 256, bc;
	uint16_t crctemp;

	while(c--)
	{
		crctemp = c << 8;
		bc = 8;

		while(bc--)
		{
			if(crctemp & 0x8000)
			{
				crctemp = (crctemp << 1) ^ poly;
			}
			else
			{
				crctemp <<= 1;
			}
		}

		CRCTable[c] = crctemp;
	}
}

void fdi_init()
{
//        printf("FDI reset\n");
        memset(&fdi, 0, sizeof(fdi));
        fdi_setupcrc(0x1021, 0xcdb4);
}

int fdi_hole(int drive)
{
	switch (fdi2raw_get_bit_rate(fdi[drive].h))
	{
		case 1000:
			return 2;
		case 500:
			return 1;
		default:
			return 0;
	}
}

void fdi_load(int drive, char *fn)
{
        writeprot[drive] = fwriteprot[drive] = 1;
        fdi[drive].f = fopen(fn, "rb");
        if (!fdi[drive].f) return;
        fdi[drive].h = fdi2raw_header(fdi[drive].f);
//        if (!fdih[drive]) printf("Failed to load!\n");
        fdi[drive].lasttrack = fdi2raw_get_last_track(fdi[drive].h);
        fdi[drive].sides = fdi2raw_get_last_head(fdi[drive].h) + 1;
//        printf("Last track %i\n",fdilasttrack[drive]);
        drives[drive].seek        = fdi_seek;
        drives[drive].readsector  = fdi_readsector;
        drives[drive].writesector = fdi_writesector;
        drives[drive].readaddress = fdi_readaddress;
        drives[drive].hole        = fdi_hole;
        drives[drive].poll        = fdi_poll;
        drives[drive].format      = fdi_format;
        drives[drive].stop        = fdi_stop;
//        pclog("Loaded as FDI\n");
}

void fdi_close(int drive)
{
        if (fdi[drive].h)
                fdi2raw_header_free(fdi[drive].h);
        if (fdi[drive].f)
                fclose(fdi[drive].f);
        fdi[drive].f = NULL;
}

void fdi_seek(int drive, int track)
{
        int density;
        
        if (!fdi[drive].f)
                return;
//        printf("Track start %i\n",track);
        if (track < 0)
                track = 0;
        if (track > fdi[drive].lasttrack)
                track = fdi[drive].lasttrack - 1;
                
        for (density = 0; density < 4; density++)
        {
                int c = fdi2raw_loadtrack(fdi[drive].h, (uint16_t *)fdi[drive].track_data[0][density],
                              (uint16_t *)fdi_timing,
                              track * fdi[drive].sides,
                              &fdi[drive].tracklen[0][density],
                              &fdi[drive].trackindex[0][density], NULL, density);
                if (!c)
                        memset(fdi[drive].track_data[0][density], 0, fdi[drive].tracklen[0][density]);

                if (fdi[drive].sides == 2)
                {
                        c = fdi2raw_loadtrack(fdi[drive].h, (uint16_t *)fdi[drive].track_data[1][density],
                                      (uint16_t *)fdi_timing,
                                      (track * fdi[drive].sides) + 1,
                                      &fdi[drive].tracklen[1][density],
                                      &fdi[drive].trackindex[1][density], NULL, density);
                        if (!c)
                                memset(fdi[drive].track_data[1][density], 0, fdi[drive].tracklen[1][density]);
                }
                else
                {
                        memset(fdi[drive].track_data[1][density], 0, 65536);
                        fdi[drive].tracklen[1][density] = fdi[drive].trackindex[1][density] = 10000;
                }
        }
}

void fdi_writeback(int drive, int track)
{
        return;
}

void fdi_readsector(int drive, int sector, int track, int side, int rate, int sector_size)
{
        fdi_revs = 0;
        fdi_sector = sector;
        fdi_track  = track;
        fdi_side   = side;
	fdi_n = sector_size;
        fdi_drive  = drive;
        if (rate == 2)
                fdi_density = 1;
        if (rate == 0)
                fdi_density = 2;
        if (rate == 3)
                fdi_density = 3;

//        pclog("FDI Read sector %i %i %i %i %i\n",drive,side,track,sector, fdi_density);
//        if (pollbytesleft)
//                pclog("In the middle of a sector!\n");

        fdi_inread  = 1;
        fdi_inwrite = 0;
        fdi_inreadaddr = 0;
        fdi_readpos = 0;
}

void fdi_writesector(int drive, int sector, int track, int side, int rate, int sector_size)
{
        fdi_revs = 0;
        fdi_sector = sector;
        fdi_track  = track;
        fdi_side   = side;
	fdi_n = sector_size;
        fdi_drive  = drive;
        if (rate == 2)
                fdi_density = 1;
        if (rate == 0)
                fdi_density = 2;
        if (rate == 3)
                fdi_density = 3;
//        pclog("Write sector %i %i %i %i\n",drive,side,track,sector);

        fdi_inread  = 0;
        fdi_inwrite = 1;
        fdi_inreadaddr = 0;
        fdi_readpos = 0;
}

void fdi_readaddress(int drive, int track, int side, int rate)
{
        fdi_revs = 0;
        fdi_track = track;
        fdi_side  = side;
        fdi_drive = drive;
        if (rate == 2)
                fdi_density = 1;
        if (rate == 0)
                fdi_density = 2;
        if (rate == 3)
                fdi_density = 3;
//        pclog("Read address %i %i %i %i %i %p\n",drive,side,track, rate, fdi_density, &fdi_inreadaddr);

        fdi_inread  = 0;
        fdi_inwrite = 0;
        fdi_inreadaddr = 1;
        fdi_readpos    = 0;
}

void fdi_format(int drive, int track, int side, int rate, uint8_t fill)
{
        fdi_revs = 0;
        fdi_track = track;
        fdi_side  = side;
        fdi_drive = drive;
        if (rate == 2)
                fdi_density = 1;
        if (rate == 0)
                fdi_density = 2;
        if (rate == 3)
                fdi_density = 3;
//        pclog("Format %i %i %i\n",drive,side,track);

        fdi_inread  = 0;
        fdi_inwrite = 1;
        fdi_inreadaddr = 0;
        fdi_readpos = 0;
}

static uint16_t fdi_buffer;
static int readidpoll=0,readdatapoll=0,fdi_nextsector=0,inreadop=0;
static uint8_t fdi_sectordat[1026];
static int lastfdidat[2],sectorcrc[2];
static int sectorsize,fdc_sectorsize;
static int ddidbitsleft=0;

static uint8_t decodefm(uint16_t dat)
{
        uint8_t temp;
        temp = 0;
        if (dat & 0x0001) temp |= 1;
        if (dat & 0x0004) temp |= 2;
        if (dat & 0x0010) temp |= 4;
        if (dat & 0x0040) temp |= 8;
        if (dat & 0x0100) temp |= 16;
        if (dat & 0x0400) temp |= 32;
        if (dat & 0x1000) temp |= 64;
        if (dat & 0x4000) temp |= 128;
        return temp;
}

void fdi_stop()
{
//        pclog("fdi_stop\n");
        fdi_inread = fdi_inwrite = fdi_inreadaddr = 0;
        fdi_nextsector = ddidbitsleft = pollbitsleft = 0;
}

static uint16_t crc;

static void calccrc(uint8_t byte)
{
	crc = (crc << 8) ^ CRCTable[(crc >> 8)^byte];
}

static int fdi_indextime_blank = 6250 * 8;
void fdi_poll()
{
        int tempi, c;
        int bitcount;
        
        for (bitcount = 0; bitcount < 16; bitcount++)
        {
        if (fdi_pos >= fdi[fdi_drive].tracklen[fdi_side][fdi_density])
        {
                fdi_pos = 0;
                if (fdi[fdi_drive].tracklen[fdi_side][fdi_density]) 
                        fdc_indexpulse();
                else
                {
                        fdi_indextime_blank--;
                        if (!fdi_indextime_blank)
                        {
                                fdi_indextime_blank = 6250 * 8;
                                fdc_indexpulse();
                        }
                }
        }
        tempi = fdi[fdi_drive].track_data[fdi_side][fdi_density][((fdi_pos >> 3) & 0xFFFF) ^ 1] & (1 << (7 - (fdi_pos & 7)));
        fdi_pos++;
        fdi_buffer <<= 1;
        fdi_buffer |= (tempi ? 1 : 0);
        if (fdi_inwrite)
        {
                fdi_inwrite = 0;
                fdc_writeprotect();
                return;
        }
        if (!fdi_inread && !fdi_inreadaddr)
                return;
        if (fdi_pos == fdi[fdi_drive].trackindex[fdi_side][fdi_density])
        {
                fdi_revs++;
                if (fdi_revs == 3)
                {
//                        pclog("Not found!\n");
                        fdc_notfound(FDC_STATUS_NOT_FOUND);
                        fdi_inread = fdi_inreadaddr = 0;
                        return;
                }
                if (fdi_sector == SECTOR_FIRST)
                        fdi_sector = SECTOR_NEXT;
        }
        if (pollbitsleft)
        {
                pollbitsleft--;
                if (!pollbitsleft)
                {
                        pollbytesleft--;
                        if (pollbytesleft) pollbitsleft = 16; /*Set up another word if we need it*/
                        if (readidpoll)
                        {
                                fdi_sectordat[5 - pollbytesleft] = decodefm(fdi_buffer);
                                if (!pollbytesleft)
                                {
//                                        pclog("Header over %i,%i %i,%i\n", fdi_sectordat[0], fdi_sectordat[2], fdi_track, fdi_sector);
                                        if ((fdi_sectordat[0] == fdi_track && (fdi_sectordat[3] == fdi_n) && (fdi_sectordat[2] == fdi_sector || fdi_sector == SECTOR_NEXT)) || fdi_inreadaddr)
                                        {
                                                crc = (fdi_density) ? 0xcdb4 : 0xffff;
                                                calccrc(0xFE);
                                                for (c = 0; c < 4; c++) 
                                                        calccrc(fdi_sectordat[c]);

                                                if ((crc >> 8) != fdi_sectordat[4] || (crc & 0xFF) != fdi_sectordat[5])
                                                {
//                                                        pclog("Header CRC error : %02X %02X %02X %02X\n",crc>>8,crc&0xFF,fdi_sectordat[4],fdi_sectordat[5]);
//                                                        dumpregs();
//                                                        exit(-1);
                                                        inreadop = 0;
                                                        if (fdi_inreadaddr)
                                                                fdc_sectorid(fdi_sectordat[0], fdi_sectordat[1], fdi_sectordat[2], fdi_sectordat[3], fdi_sectordat[4], fdi_sectordat[5]);
                                                        else
                                                                fdc_headercrcerror();
                                                        return;
                                                }
//                                                pclog("Sector %i,%i %i,%i\n", fdi_sectordat[0], fdi_sectordat[2], fdi_track, fdi_sector);
                                                if (fdi_sectordat[0] == fdi_track && (fdi_sectordat[2] == fdi_sector || fdi_sector == SECTOR_NEXT) && fdi_inread && !fdi_inreadaddr)
                                                {
                                                        fdi_nextsector = 1;
                                                        readidpoll = 0;
                                                        sectorsize = (1 << (fdi_sectordat[3] + 7)) + 2;
                                                        fdc_sectorsize = fdi_sectordat[3];
                                                }
                                                if (fdi_inreadaddr)
                                                {
                                                        fdc_sectorid(fdi_sectordat[0], fdi_sectordat[1], fdi_sectordat[2], fdi_sectordat[3], fdi_sectordat[4], fdi_sectordat[5]);
                                                        fdi_inreadaddr = 0;
                                                }
                                        }
                                }
                        }
                        if (readdatapoll)
                        {
//                                pclog("readdatapoll %i %02x\n", pollbytesleft, decodefm(fdi_buffer));
                                if (pollbytesleft > 1)
                                {
                                        calccrc(decodefm(fdi_buffer));
                                }
                                else
                                   sectorcrc[1 - pollbytesleft] = decodefm(fdi_buffer);
                                if (!pollbytesleft)
                                {
                                        fdi_inread = 0;
//#if 0
                                        if ((crc >> 8) != sectorcrc[0] || (crc & 0xFF) != sectorcrc[1])// || (fditrack==79 && fdisect==4 && fdc_side&1))
                                        {
//                                                pclog("Data CRC error : %02X %02X %02X %02X %i %04X %02X%02X\n",crc>>8,crc&0xFF,sectorcrc[0],sectorcrc[1],fdi_pos,crc,sectorcrc[0],sectorcrc[1]);
                                                inreadop = 0;
                                                fdc_data(decodefm(lastfdidat[1]));
                                                fdc_finishread();
                                                fdc_datacrcerror();
                                                readdatapoll = 0;
                                                return;
                                        }
//#endif
//                                        pclog("End of FDI read %02X %02X %02X %02X\n",crc>>8,crc&0xFF,sectorcrc[0],sectorcrc[1]);
                                        fdc_data(decodefm(lastfdidat[1]));
                                        fdc_finishread();
                                }
                                else if (lastfdidat[1] != 0)
                                   fdc_data(decodefm(lastfdidat[1]));
                                lastfdidat[1] = lastfdidat[0];
                                lastfdidat[0] = fdi_buffer;
                                if (!pollbytesleft)
                                   readdatapoll = 0;
                        }
                }
        }
        if (fdi_buffer == 0x4489 && fdi_density)
        {
//                rpclog("Found sync\n");
                ddidbitsleft = 17;
        }

        if (fdi_buffer == 0xF57E && !fdi_density)
        {
                pollbytesleft = 6;
                pollbitsleft  = 16;
                readidpoll    = 1;
        }
        if ((fdi_buffer == 0xF56F || fdi_buffer == 0xF56A) && !fdi_density)
        {
                if (fdi_nextsector)
                {
                        pollbytesleft  = sectorsize;
                        pollbitsleft   = 16;
                        readdatapoll   = 1;
                        fdi_nextsector = 0;
                        crc = 0xffff;
                        if (fdi_buffer == 0xF56A) calccrc(0xF8);
                        else                      calccrc(0xFB);
                        lastfdidat[0] = lastfdidat[1] = 0;
                }
        }
        if (ddidbitsleft)
        {
                ddidbitsleft--;
                if (!ddidbitsleft && !readdatapoll)
                {
//                        printf("ID bits over %04X %02X %i\n",fdibuffer,decodefm(fdibuffer),fdipos);
                        if (decodefm(fdi_buffer) == 0xFE)
                        {
//                                printf("Sector header %i %i\n", fdi_inread, fdi_inreadaddr);
                                pollbytesleft = 6;
                                pollbitsleft  = 16;
                                readidpoll    = 1;
                        }
                        else if (decodefm(fdi_buffer) == 0xFB)
                        {
//                                printf("Data header %i %i\n", fdi_inread, fdi_inreadaddr);
                                if (fdi_nextsector)
                                {
                                        pollbytesleft  = sectorsize;
                                        pollbitsleft   = 16;
                                        readdatapoll   = 1;
                                        fdi_nextsector = 0;
                                        crc = 0xcdb4;
                                        if (fdi_buffer == 0xF56A) calccrc(0xF8);
                                        else                      calccrc(0xFB);
                                        lastfdidat[0] = lastfdidat[1] = 0;
                                }
                        }
                }
        }
        }
}
