#include	"compiler.h"
#include	"strres.h"
#include	"dosio.h"
#include	"sysmng.h"
#include	"cpucore.h"
#include	"pccore.h"
#include	"sxsi.h"

const char sig_vhd[8] = "VHD1.00";
const char sig_nhd[15] = "T98HDDIMAGE.R0";

const SASIHDD sasihdd[7] = {
				{33, 4, 153},			// 5MB
				{33, 4, 310},			// 10MB
				{33, 6, 310},			// 15MB
				{33, 8, 310},			// 20MB
				{33, 4, 615},			// 20MB (not used!)
				{33, 6, 615},			// 30MB
				{33, 8, 615}};			// 40MB


// ----

static BRESULT hdd_reopen(SXSIDEV sxsi) {

	FILEH	fh;

	fh = file_open(sxsi->fname);
	if (fh != FILEH_INVALID) {
		sxsi->hdl = (INTPTR)fh;
		return(SUCCESS);
	}
	else {
		return(FAILURE);
	}
}

static REG8 hdd_read(SXSIDEV sxsi, long pos, UINT8 *buf, UINT size) {

	FILEH	fh;
	long	r;
	UINT	rsize;

	if (sxsi_prepare(sxsi) != SUCCESS) {
		return(0x60);
	}
	if ((pos < 0) || (pos >= sxsi->totals)) {
		return(0x40);
	}
	pos = pos * sxsi->size + sxsi->headersize;
	fh = (FILEH)sxsi->hdl;
	r = file_seek(fh, pos, FSEEK_SET);
	if (pos != r) {
		return(0xd0);
	}
	while(size) {
		rsize = min(size, sxsi->size);
		CPU_REMCLOCK -= rsize;
		if (file_read(fh, buf, rsize) != rsize) {
			return(0xd0);
		}
		buf += rsize;
		size -= rsize;
	}
	return(0x00);
}

static REG8 hdd_write(SXSIDEV sxsi, long pos, const UINT8 *buf, UINT size) {

	FILEH	fh;
	long	r;
	UINT	wsize;

	if (sxsi_prepare(sxsi) != SUCCESS) {
		return(0x60);
	}
	if ((pos < 0) || (pos >= sxsi->totals)) {
		return(0x40);
	}
	pos = pos * sxsi->size + sxsi->headersize;
	fh = (FILEH)sxsi->hdl;
	r = file_seek(fh, pos, FSEEK_SET);
	if (pos != r) {
		return(0xd0);
	}
	while(size) {
		wsize = min(size, sxsi->size);
		CPU_REMCLOCK -= wsize;
		if (file_write(fh, buf, wsize) != wsize) {
			return(0x70);
		}
		buf += wsize;
		size -= wsize;
	}
	return(0x00);
}

static REG8 hdd_format(SXSIDEV sxsi, long pos) {

	FILEH	fh;
	long	r;
	UINT16	i;
	UINT8	work[256];
	UINT	size;
	UINT	wsize;

	if (sxsi_prepare(sxsi) != SUCCESS) {
		return(0x60);
	}
	if ((pos < 0) || (pos >= sxsi->totals)) {
		return(0x40);
	}
	pos = pos * sxsi->size + sxsi->headersize;
	fh = (FILEH)sxsi->hdl;
	r = file_seek(fh, pos, FSEEK_SET);
	if (pos != r) {
		return(0xd0);
	}
	FillMemory(work, sizeof(work), 0xe5);
	for (i=0; i<sxsi->sectors; i++) {
		size = sxsi->size;
		while(size) {
			wsize = min(size, sizeof(work));
			size -= wsize;
			CPU_REMCLOCK -= wsize;
			if (file_write(fh, work, wsize) != wsize) {
				return(0x70);
			}
		}
	}
	return(0x00);
}

static void hdd_close(SXSIDEV sxsi) {

	file_close((FILEH)sxsi->hdl);
}


// ----

// SASI規格HDDかチェック
static UINT8 gethddtype(SXSIDEV sxsi) {

const SASIHDD	*sasi;
	UINT		i;

	if (sxsi->size == 256) {
		sasi = sasihdd;
		for (i=0; i<NELEMENTS(sasihdd); i++, sasi++) {
			if ((sxsi->sectors == sasi->sectors) &&
				(sxsi->surfaces == sasi->surfaces) &&
				(sxsi->cylinders == sasi->cylinders)) {
				return((UINT8)i);
			}
		}
	}
	return(SXSIMEDIA_INVSASI + 7);
}

BRESULT sxsihdd_open(SXSIDEV sxsi, const OEMCHAR *fname) {

	FILEH		fh;
const OEMCHAR	*ext;
	REG8		iftype;
	long		totals;
	UINT32		headersize;
	UINT32		surfaces;
	UINT32		cylinders;
	UINT32		sectors;
	UINT32		size;

	fh = file_open(fname);
	if (fh == FILEH_INVALID) {
		goto sxsiope_err1;
	}
	ext = file_getext(fname);
	iftype = sxsi->drv & SXSIDRV_IFMASK;
#ifndef GEKKO
	if ((iftype == SXSIDRV_SASI) && (!file_cmpname(ext, str_thd))) {
#else
	if ((iftype == SXSIDRV_SASI) && (!strcasecmp(ext, str_thd))) {
#endif
		THDHDR thd;						// T98 HDD (IDE)
		if (file_read(fh, &thd, sizeof(thd)) != sizeof(thd)) {
			goto sxsiope_err2;
		}
		headersize = 256;
		surfaces = 8;
		cylinders = LOADINTELWORD(thd.cylinders);
		sectors = 33;
		size = 256;
		totals = cylinders * sectors * surfaces;
	}
#ifndef GEKKO
	else if ((iftype == SXSIDRV_SASI) && (!file_cmpname(ext, str_nhd))) {
#else
	else if ((iftype == SXSIDRV_SASI) && (!strcasecmp(ext, str_nhd))) {
#endif
		NHDHDR nhd;						// T98Next HDD (IDE)
		if ((file_read(fh, &nhd, sizeof(nhd)) != sizeof(nhd)) ||
			(memcmp(nhd.sig, sig_nhd, 15))) {
			goto sxsiope_err2;
		}
		headersize = LOADINTELDWORD(nhd.headersize);
		surfaces = LOADINTELWORD(nhd.surfaces);
		cylinders = LOADINTELDWORD(nhd.cylinders);
		sectors = LOADINTELWORD(nhd.sectors);
		size = LOADINTELWORD(nhd.sectorsize);
		totals = cylinders * sectors * surfaces;
	}
#ifndef GEKKO
	else if ((iftype == SXSIDRV_SASI) && (!file_cmpname(ext, str_hdi))) {
#else
	else if ((iftype == SXSIDRV_SASI) && (!strcasecmp(ext, str_hdi))) {
#endif
		HDIHDR hdi;						// ANEX86 HDD (SASI) thanx Mamiya
		if (file_read(fh, &hdi, sizeof(hdi)) != sizeof(hdi)) {
			goto sxsiope_err2;
		}
		headersize = LOADINTELDWORD(hdi.headersize);
		surfaces = LOADINTELDWORD(hdi.surfaces);
		cylinders = LOADINTELDWORD(hdi.cylinders);
		sectors = LOADINTELDWORD(hdi.sectors);
		size = LOADINTELDWORD(hdi.sectorsize);
		totals = cylinders * sectors * surfaces;
	}
#ifndef GEKKO
	else if ((iftype == SXSIDRV_SCSI) && (!file_cmpname(ext, str_hdd))) {
#else
	else if ((iftype == SXSIDRV_SCSI) && (!strcasecmp(ext, str_hdd))) {
#endif
		VHDHDR vhd;						// Virtual98 HDD (SCSI)
		if ((file_read(fh, &vhd, sizeof(vhd)) != sizeof(vhd)) ||
			(memcmp(vhd.sig, sig_vhd, 5))) {
			goto sxsiope_err2;
		}
		headersize = sizeof(vhd);
		surfaces = vhd.surfaces;
		cylinders = LOADINTELWORD(vhd.cylinders);
		sectors = vhd.sectors;
		size = LOADINTELWORD(vhd.sectorsize);
		totals = (SINT32)LOADINTELDWORD(vhd.totals);
	}
	else {
		goto sxsiope_err2;
	}

	// フォーマット確認〜
	if ((surfaces == 0) || (surfaces >= 256) ||
		(cylinders == 0) || (cylinders >= 65536) ||
		(sectors == 0) || (sectors >= 256) ||
		(size == 0) || ((size & (size - 1)) != 0)) {
		goto sxsiope_err2;
	}
	if (iftype == SXSIDRV_SCSI) {
		if (!(size & 0x700)) {			// not 256,512,1024
			goto sxsiope_err2;
		}
	}
	sxsi->reopen = hdd_reopen;
	sxsi->read = hdd_read;
	sxsi->write = hdd_write;
	sxsi->format = hdd_format;
	sxsi->close = hdd_close;

	sxsi->hdl = (INTPTR)fh;
	sxsi->totals = totals;
	sxsi->cylinders = (UINT16)cylinders;
	sxsi->size = (UINT16)size;
	sxsi->sectors = (UINT8)sectors;
	sxsi->surfaces = (UINT8)surfaces;
	sxsi->headersize = headersize;
	sxsi->mediatype = gethddtype(sxsi);
	return(SUCCESS);

sxsiope_err2:
	file_close(fh);

sxsiope_err1:
	return(FAILURE);
}

