/*Arculator 2.2 by Sarah Walker
  JFD disc image support
  Incomplete, and disabled by default*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <zlib.h>

#include "arc.h"
#include "disc.h"
#include "disc_jfd.h"

static disc_funcs_t jfd_disc_funcs;

static void jfd_seek(int drive, int track);

static struct
{
	struct
	{
		uint32_t id;
		uint32_t min_ver;
		uint32_t size;
		uint32_t seq_num;
		uint32_t game_id;
		uint32_t image_ver;
		uint32_t offset_track;
		uint32_t offset_sector;
		uint32_t offset_data;
		uint32_t offset_delta_track;
		uint32_t offset_delta_sector;
		uint32_t offset_delta_data;

		char title[256];

		/*2.04+*/
		uint32_t flags;
		uint8_t fps;
		uint8_t reserved[3];
		uint32_t obey_length;
	} header;

	int num_tracks;

	uint32_t track_offset[166]; /*Unlikely to be more than 83 tracks*/

	struct
	{
		uint32_t header;
		uint32_t offset;
	} sector_table[2][256];        /*Unlikely to be more than 256 sectors*/

	uint8_t sector_data[2][256][1024];
} jfd[4];

enum
{
	JFD_IDLE,
	JFD_FIND_SECTOR,
	JFD_READ_ID,
	JFD_READ_SECTOR
} jfd_state = JFD_IDLE;

enum
{
	CRC_ID_INVALID = 0x10,
	CRC_DATA_INVALID = 0x20
};

static gzFile jfd_f[4];

static int jfd_revs;

static int jfd_sector, jfd_track,   jfd_side,    jfd_drive, jfd_density;
static int jfd_inread, jfd_inwrite, jfd_readpos, jfd_inreadaddr;
static int jfd_notfound;

static int jfd_realsector;

static uint16_t CRCTable[256];

static void jfd_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 jfd_init()
{
//        rpclog("jfd reset\n");
	jfd_f[0]  = jfd_f[1]  = 0;
	jfd_notfound = 0;
	jfd_setupcrc(0x1021, 0xcdb4);
}

void jfd_load(int drive, char *fn)
{
	rpclog("jfd_load\n");

	writeprot[drive] = 1;
	jfd_f[drive] = gzopen(fn, "rb");
	if (!jfd_f[drive]) return;

	gzread(jfd_f[drive], &jfd[drive].header, sizeof(jfd[drive].header));

	rpclog("jfd_load : file ID %c%c%c%c\n", jfd[drive].header.id & 0xff, (jfd[drive].header.id >> 8) & 0xff, (jfd[drive].header.id >> 16) & 0xff, jfd[drive].header.id >> 24);
	rpclog("jfd_load : disc title %s\n", jfd[drive].header.title);

	jfd[drive].num_tracks = (jfd[drive].header.offset_sector - jfd[drive].header.offset_track) / 4;
	rpclog("jfd_load : num_tracks %i\n", jfd[drive].num_tracks);

	gzseek(jfd_f[drive], jfd[drive].header.offset_track, SEEK_SET);
	gzread(jfd_f[drive], jfd[drive].track_offset, jfd[drive].num_tracks * 4);

	drive_funcs[drive] = &jfd_disc_funcs;
	rpclog("Loaded as jfd\n");
}

static void jfd_close(int drive)
{
	if (jfd_f[drive]) gzclose(jfd_f[drive]);
	jfd_f[drive] = NULL;
}

static void jfd_seek(int drive, int track)
{
	int head, sector;
	if (!jfd_f[drive]) return;

	rpclog("Seek drive %i to track %i\n", drive, track);
	if (jfd[drive].track_offset[track * 2] != 0xffffffff)
	{
		gzseek(jfd_f[drive], jfd[drive].track_offset[track * 2] + jfd[drive].header.offset_sector, SEEK_SET);
		gzread(jfd_f[drive], jfd[drive].sector_table[0], 256 * 8);
	}
	else
		jfd[drive].sector_table[0][0].header = 0xffffffff;  /*Track empty*/

	if (jfd[drive].track_offset[(track * 2) + 1] != 0xffffffff)
	{
		gzseek(jfd_f[drive], jfd[drive].track_offset[(track * 2) + 1] + jfd[drive].header.offset_sector, SEEK_SET);
		gzread(jfd_f[drive], jfd[drive].sector_table[1], 256 * 8);
	}
	else
		jfd[drive].sector_table[1][0].header = 0xffffffff;  /*Track empty*/

	/*Calculate timing*/
	/*Track is approx 6.5kb - 6656 bytes, which is 200 ms
	  Sector header is 6 bytes + sync + gap, say 16 bytes
	  Inter-sector gap, say 32 bytes*/
	for (head = 0; head < 2; head++)
	{
		int offset = 0;
		for (sector = 0; sector < 256; sector++)
		{
			if (jfd[drive].sector_table[head][sector].header == 0xffffffff)
				break;

			if ((jfd[drive].sector_table[head][sector].header >> 24) == 0xff)
			{
				int sector_size;

				jfd[drive].sector_table[head][sector].header &= ~0xff000000;
				jfd[drive].sector_table[head][sector].header |= offset << 24;

				sector_size = (128 << (jfd[drive].sector_table[head][sector].header & 0xf)) + 16 + 96;
				offset += ((sector_size * 200) / 6656);
			}
			rpclog("Track %02i Head %i Sector %02i : offset %i header %08X\n", track, head, sector, jfd[drive].sector_table[head][sector].header >> 24, jfd[drive].sector_table[head][sector].header);

			if (jfd[drive].header.offset_data != 0xffffffff)
			{
				gzseek(jfd_f[drive], jfd[drive].sector_table[head][sector].offset + jfd[drive].header.offset_data, SEEK_SET);
				gzread(jfd_f[drive], jfd[drive].sector_data[head][sector], 128 << (jfd[drive].sector_table[head][sector].header & 3));
			}
			else
				memset(jfd[drive].sector_data[head][sector], 0xff, 1024);
		}
	}
}

static void jfd_readsector(int drive, int sector, int track, int side, int density)
{
	jfd_revs = 0;
	jfd_sector  = sector;
	jfd_track   = track;
	jfd_side    = side;
	jfd_drive   = drive;
	jfd_density = 1 << density;
	rpclog("jfd Read sector %i %i %i %i %i\n",drive,side,track,sector, density);

	if (!jfd_f[drive])
	{
		jfd_notfound = 500;
		return;
	}

	jfd_inread  = 1;
	jfd_readpos = 0;
	jfd_state   = JFD_FIND_SECTOR;
}

static void jfd_writesector(int drive, int sector, int track, int side, int density)
{
	jfd_revs = 0;
	jfd_sector = sector;
	jfd_track  = track;
	jfd_side   = side;
	jfd_density = 1 << density;
	jfd_drive  = drive;
	rpclog("Write sector %i %i %i %i\n",drive,side,track,sector);

	if (!jfd_f[drive])
	{
		jfd_notfound = 500;
		return;
	}
	jfd_inwrite = 1;
	jfd_readpos = 0;
	jfd_state   = JFD_FIND_SECTOR;
}

static void jfd_readaddress(int drive, int track, int side, int density)
{
	jfd_revs = 0;
	jfd_track   = track;
	jfd_side    = side;
	jfd_density = 1 << density;
	jfd_drive   = drive;
	rpclog("Read address %i %i %i\n",drive,side,track);

	jfd_inreadaddr = 1;
	jfd_readpos    = 0;
	jfd_state      = JFD_FIND_SECTOR;
}

static void jfd_format(int drive, int track, int side, int density)
{
	jfd_revs = 0;
	jfd_track   = track;
	jfd_side    = side;
	jfd_density = 1 << density;
	jfd_drive   = drive;
	rpclog("Format %i %i %i\n",drive,side,track);

	jfd_inwrite = 1;
	jfd_readpos = 0;
}

static int jfd_nextsector=0;
static int ddidbitsleft=0;

static void jfd_stop()
{
	jfd_inread = jfd_inwrite = jfd_inreadaddr = 0;
	jfd_nextsector = ddidbitsleft = 0;
	jfd_state = JFD_IDLE;
}

static int jfd_pos_us = 0;

static void jfd_poll()
{
	int c;
	int old_pos_us = jfd_pos_us;

	/*32 us*/
	jfd_pos_us += 32;
	if (jfd_pos_us >= 200000)
		jfd_pos_us -= 200000;

	if (jfd_pos_us == 191808)
	{
//                rpclog("index pulse\n");
		fdc_funcs->indexpulse(fdc_p);
	}

	switch (jfd_state)
	{
		case JFD_IDLE:
		break;

		case JFD_FIND_SECTOR:
//                rpclog("JFD_FIND_SECTOR %06i\n", jfd_pos_us);
		c = 0;
		while (jfd[jfd_drive].sector_table[jfd_side][c].header != 0xffffffff)
		{
			uint32_t header = jfd[jfd_drive].sector_table[jfd_side][c].header;
			int time_us = (header >> 24) * 1000;
			if (old_pos_us <= time_us && time_us < jfd_pos_us)
			{
				rpclog("%06i : Found sector %i %08X %i %i\n", jfd_pos_us, c, header, old_pos_us, time_us);
				if (jfd_inreadaddr)
				{
					rpclog("jfd_inreadaddr %i %i %i\n", fdc_funcs->sectorid, (header >> 16) & 0xf, jfd_density);
					if (fdc_funcs->sectorid && ((header >> 16) & 0xf) == jfd_density)
					{
						fdc_funcs->sectorid(jfd_track, jfd_side, header >> 8 & 0xff, header & 3, 0, 0, fdc_p);
						jfd_inreadaddr = 0;
						jfd_state = JFD_IDLE;
					}
				}
				else if (jfd_inread)
				{
					if (((header >> 8) & 0xff) == jfd_sector &&
					    ((header >> 16) & 0xf) == jfd_density)
					{
						if (header & CRC_ID_INVALID)
						{
							fdc_funcs->headercrcerror(fdc_p);
							jfd_state = JFD_IDLE;
						}
						else
						{
							jfd_state = JFD_READ_SECTOR;
							jfd_realsector = c;
						}
					}
				}
				break;
			}
			c++;
		}
		break;

		case JFD_READ_SECTOR:
//                rpclog("JFD_READ_SECTOR : %04i %02X\n", jfd_readpos, jfd[jfd_drive].sector_data[jfd_side][jfd_realsector][jfd_readpos]);
		fdc_funcs->data(jfd[jfd_drive].sector_data[jfd_side][jfd_realsector][jfd_readpos], fdc_p);
		jfd_readpos++;
		if (jfd_readpos == (128 << (jfd[jfd_drive].sector_table[jfd_side][jfd_realsector].header & 3)))
		{
//                        rpclog("Read %i bytes\n", jfd_readpos);
			fdc_funcs->finishread(fdc_p);
			if (jfd[jfd_drive].sector_table[jfd_side][jfd_realsector].header & CRC_DATA_INVALID)
				fdc_funcs->datacrcerror(fdc_p);
			jfd_inread = 0;
			jfd_state = JFD_IDLE;
		}
		break;

		case JFD_READ_ID:
		break;
	}
}

static disc_funcs_t jfd_disc_funcs =
{
	.seek        = jfd_seek,
	.readsector  = jfd_readsector,
	.writesector = jfd_writesector,
	.readaddress = jfd_readaddress,
	.poll        = jfd_poll,
	.format      = jfd_format,
	.stop        = jfd_stop,
	.close       = jfd_close
};
