#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;
					}
				}
			}
		}
	}
}
