// license:BSD-3-Clause
// copyright-holders:Olivier Galibert
#include "emu.h"
#include "machine/nscsi_hd.h"
#include "imagedev/harddriv.h"

#define LOG_GENERAL (1U << 0)
#define LOG_COMMAND (1U << 1)

#define VERBOSE (LOG_GENERAL)

#include "logmacro.h"

DEFINE_DEVICE_TYPE(NSCSI_HARDDISK, nscsi_harddisk_device, "scsi_harddisk", "SCSI Hard Disk")

nscsi_harddisk_device::nscsi_harddisk_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
	nscsi_harddisk_device(mconfig, NSCSI_HARDDISK, tag, owner, clock)
{
}

nscsi_harddisk_device::nscsi_harddisk_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) :
	nscsi_full_device(mconfig, type, tag, owner, clock), harddisk(nullptr), lba(0), cur_lba(0), blocks(0), bytes_per_sector(0)
{
}

void nscsi_harddisk_device::device_start()
{
	nscsi_full_device::device_start();
	save_item(NAME(block));
	save_item(NAME(lba));
	save_item(NAME(cur_lba));
	save_item(NAME(blocks));
	save_item(NAME(bytes_per_sector));
}

void nscsi_harddisk_device::device_reset()
{
	nscsi_full_device::device_reset();
	harddisk_image_device *hd = subdevice<harddisk_image_device>("image");
	harddisk = hd->get_hard_disk_file();
	if(!harddisk) {
		scsi_id = -1;
		bytes_per_sector = 0;
	} else {
		const hard_disk_info *hdinfo = hard_disk_get_info(harddisk);
		bytes_per_sector = hdinfo->sectorbytes;

		chd_file *chd = hd->get_chd_file();
		if(chd != nullptr)
			chd->read_metadata(HARD_DISK_IDENT_METADATA_TAG, 0, m_inquiry_data);
	}
	cur_lba = -1;
}

MACHINE_CONFIG_START(nscsi_harddisk_device::device_add_mconfig)
	MCFG_HARDDISK_ADD("image")
	MCFG_HARDDISK_INTERFACE("scsi_hdd")
MACHINE_CONFIG_END


uint8_t nscsi_harddisk_device::scsi_get_data(int id, int pos)
{
	if(id != 2)
		return nscsi_full_device::scsi_get_data(id, pos);
	int clba = lba + pos / bytes_per_sector;
	if(clba != cur_lba) {
		cur_lba = clba;
		if(!hard_disk_read(harddisk, cur_lba, block)) {
			LOG("HD READ ERROR !\n");
			memset(block, 0, sizeof(block));
		}
	}
	return block[pos % bytes_per_sector];
}

void nscsi_harddisk_device::scsi_put_data(int id, int pos, uint8_t data)
{
	if(id != 2) {
		nscsi_full_device::scsi_put_data(id, pos, data);
		return;
	}

	int offset = pos % bytes_per_sector;
	block[offset] = data;
	int clba = lba + pos / bytes_per_sector;
	if(offset == bytes_per_sector-1) {
		if(!hard_disk_write(harddisk, clba, block))
			LOG("HD WRITE ERROR !\n");
	}
}

void nscsi_harddisk_device::scsi_command()
{
	if(scsi_cmdbuf[0] != SC_READ_6) {
		LOGMASKED(LOG_COMMAND, "%02x %02x %02x %02x %02x %02x\n",
			scsi_cmdbuf[0], scsi_cmdbuf[1], scsi_cmdbuf[2],
			scsi_cmdbuf[3], scsi_cmdbuf[4], scsi_cmdbuf[5]);
	}

	switch(scsi_cmdbuf[0]) {
	case SC_TEST_UNIT_READY:
		LOG("command TEST UNIT READY\n");
		scsi_status_complete(SS_GOOD);
		break;

	case SC_READ_6:
		lba = ((scsi_cmdbuf[1] & 0x1f)<<16) | (scsi_cmdbuf[2]<<8) | scsi_cmdbuf[3];
		blocks = scsi_cmdbuf[4];
		if(!blocks)
			blocks = 256;

		LOG("command READ start=%08x blocks=%04x\n", lba, blocks);

		scsi_data_in(2, blocks*bytes_per_sector);
		scsi_status_complete(SS_GOOD);
		break;

	case SC_WRITE_6:
		lba = ((scsi_cmdbuf[1] & 0x1f)<<16) | (scsi_cmdbuf[2]<<8) | scsi_cmdbuf[3];
		blocks = scsi_cmdbuf[4];
		if(!blocks)
			blocks = 256;

		LOG("command WRITE start=%08x blocks=%04x\n", lba, blocks);

		scsi_data_out(2, blocks*bytes_per_sector);
		scsi_status_complete(SS_GOOD);
		break;

	case SC_INQUIRY: {
		int lun = get_lun(scsi_cmdbuf[1] >> 5);
		LOG("command INQUIRY lun=%d EVPD=%d page=%d alloc=%02x link=%02x\n",
			lun, scsi_cmdbuf[1] & 1, scsi_cmdbuf[2], scsi_cmdbuf[4], scsi_cmdbuf[5]);

		int page = scsi_cmdbuf[2];
		int size = scsi_cmdbuf[4];
		switch(page) {
		case 0:
			memset(scsi_cmdbuf, 0, 148);
			// From Seagate SCSI Commands Reference Manual (http://www.seagate.com/staticfiles/support/disc/manuals/scsi/100293068a.pdf), page 73:
			// If the SCSI target device is not capable of supporting a peripheral device connected to this logical unit, the
			// device server shall set these fields to 7Fh (i.e., PERIPHERAL QUALIFIER field set to 011b and PERIPHERAL DEVICE
			// TYPE set to 1Fh).
			if (lun != 0)
				scsi_cmdbuf[0] = 0x7f;
			else
				scsi_cmdbuf[0] = 0x00; // device is direct-access (e.g. hard disk)
			scsi_cmdbuf[1] = 0x00; // media is not removable
			scsi_cmdbuf[2] = 0x05; // device complies with SPC-3 standard
			scsi_cmdbuf[3] = 0x01; // response data format = CCS
			if(m_inquiry_data.empty()) {
				LOG("IDNT tag not found in chd metadata, using default inquiry data\n");

				// Apple HD SC setup utility needs to see this
				strcpy((char *)&scsi_cmdbuf[8], " SEAGATE");
				strcpy((char *)&scsi_cmdbuf[15], "          ST225N");
				strcpy((char *)&scsi_cmdbuf[31], "1.00");
				scsi_cmdbuf[36] = 0x00; // # of extents high
				scsi_cmdbuf[37] = 0x08; // # of extents low
				scsi_cmdbuf[38] = 0x00; // group 0 commands 0-1f
				scsi_cmdbuf[39] = 0x99; // commands 0,3,4,7
				scsi_cmdbuf[40] = 0xa0; // commands 8, a
				scsi_cmdbuf[41] = 0x27; // commands 12,15,16,17
				scsi_cmdbuf[42] = 0x34; // commands 1a,1b,1d
				scsi_cmdbuf[43] = 0x01; // group 1 commands 20-3f
				scsi_cmdbuf[44] = 0x04;
				scsi_cmdbuf[45] = 0xa0;
				scsi_cmdbuf[46] = 0x01;
				scsi_cmdbuf[47] = 0x18;
				scsi_cmdbuf[48] = 0x07; // group 7 commands e0-ff
				scsi_cmdbuf[49] = 0x00;
				scsi_cmdbuf[50] = 0xa0; // commands 8, a
				scsi_cmdbuf[51] = 0x00;
				scsi_cmdbuf[52] = 0x00;
				scsi_cmdbuf[53] = 0xff; // end of list
			}
			else
				std::copy_n(m_inquiry_data.begin(), std::min(m_inquiry_data.size(), size_t(48)), &scsi_cmdbuf[8]);

			if(size > 56)
				size = 56;
			scsi_data_in(0, size);
			break;
		}
		scsi_status_complete(SS_GOOD);
		break;
	}

	case SC_MODE_SENSE_6: {
		int lun = get_lun(scsi_cmdbuf[1] >> 5);
		LOG("command MODE SENSE 6 lun=%d page=%02x alloc=%02x link=%02x\n",
			lun, scsi_cmdbuf[2] & 0x3f, scsi_cmdbuf[4], scsi_cmdbuf[5]);
		if(lun) {
			bad_lun();
			return;
		}

		int page = scsi_cmdbuf[2] & 0x3f;
		int size = scsi_cmdbuf[4];
		int pos = 1;
		scsi_cmdbuf[pos++] = 0x00; // medium type
		scsi_cmdbuf[pos++] = 0x00; // WP, cache

		hard_disk_info *info = hard_disk_get_info(harddisk);
		uint32_t dsize = info->cylinders * info->heads * info->sectors - 1;
		scsi_cmdbuf[pos++] = 0x08; // Block descriptor length
		scsi_cmdbuf[pos++] = 0x00;
		scsi_cmdbuf[pos++] = (dsize>>16) & 0xff;
		scsi_cmdbuf[pos++] = (dsize>>8) & 0xff;
		scsi_cmdbuf[pos++] = (dsize & 0xff);
		scsi_cmdbuf[pos++] = 0x00;
		scsi_cmdbuf[pos++] = (info->sectorbytes>>16)&0xff;
		scsi_cmdbuf[pos++] = (info->sectorbytes>>8)&0xff;
		scsi_cmdbuf[pos++] = (info->sectorbytes & 0xff);

		int pmax = page == 0x3f ? 0x3e : page;
		int pmin = page == 0x3f ? 0x00 : page;
		for(int page=pmax; page >= pmin; page--) {
			switch(page) {
			case 0x00: // Unit attention parameters page (weird)
				scsi_cmdbuf[pos++] = 0x80; // PS, page id
				scsi_cmdbuf[pos++] = 0x02; // Page length
				scsi_cmdbuf[pos++] = 0x00; // Meh
				scsi_cmdbuf[pos++] = 0x00; // Double meh
				break;

			case 0x03:  { // Format parameters page
				scsi_cmdbuf[pos++] = 0x83; // PS, page id
				scsi_cmdbuf[pos++] = 0x16; // Page length
				scsi_cmdbuf[pos++] = (info->cylinders * info->heads) >> 8; // Track/zone
				scsi_cmdbuf[pos++] = info->cylinders * info->heads;        // Track/zone
				scsi_cmdbuf[pos++] = 0x00; // Alt sect/zone
				scsi_cmdbuf[pos++] = 0x00; // Alt sect/zone
				scsi_cmdbuf[pos++] = 0x00; // Alt track/zone
				scsi_cmdbuf[pos++] = 0x00; // Alt track/zone
				scsi_cmdbuf[pos++] = 0x00; // Alt track/volume
				scsi_cmdbuf[pos++] = 0x00; // Alt track/volume
				scsi_cmdbuf[pos++] = info->sectors >> 8; // Sectors/track
				scsi_cmdbuf[pos++] = info->sectors;      // Sectors/track
				scsi_cmdbuf[pos++] = info->sectorbytes >> 8; // Bytes/sector
				scsi_cmdbuf[pos++] = info->sectorbytes;      // Bytes/sector
				scsi_cmdbuf[pos++] = 0x00; // Interleave
				scsi_cmdbuf[pos++] = 0x00; // Interleave
				scsi_cmdbuf[pos++] = 0x00; // Track skew
				scsi_cmdbuf[pos++] = 0x00; // Track skew
				scsi_cmdbuf[pos++] = 0x00; // Cylinder skew
				scsi_cmdbuf[pos++] = 0x00; // Cylinder skew
				scsi_cmdbuf[pos++] = 0x00; // Sectoring type
				scsi_cmdbuf[pos++] = 0x00; // Reserved
				scsi_cmdbuf[pos++] = 0x00; // Reserved
				scsi_cmdbuf[pos++] = 0x00; // Reserved
				break;
			}

			case 0x04: { // Rigid drive geometry page
				scsi_cmdbuf[pos++] = 0x84; // PS, page id
				scsi_cmdbuf[pos++] = 0x16; // Page length
				scsi_cmdbuf[pos++] = info->cylinders >> 16; // Cylinders
				scsi_cmdbuf[pos++] = info->cylinders >> 8;  // Cylinders
				scsi_cmdbuf[pos++] = info->cylinders;       // Cylinders
				scsi_cmdbuf[pos++] = info->heads;           // Heads
				scsi_cmdbuf[pos++] = 0x00;                  // Starting cylinder - write precomp
				scsi_cmdbuf[pos++] = 0x00;                  // Starting cylinder - write precomp
				scsi_cmdbuf[pos++] = 0x00;                  // Starting cylinder - write precomp
				scsi_cmdbuf[pos++] = 0x00;                  // Starting cylinder - reduced write current
				scsi_cmdbuf[pos++] = 0x00;                  // Starting cylinder - reduced write current
				scsi_cmdbuf[pos++] = 0x00;                  // Starting cylinder - reduced write current
				scsi_cmdbuf[pos++] = 0x00;                  // Drive step rate
				scsi_cmdbuf[pos++] = 0x00;                  // Drive step rate
				scsi_cmdbuf[pos++] = 0x00;                  // Landing zone cylinder
				scsi_cmdbuf[pos++] = 0x00;                  // Landing zone cylinder
				scsi_cmdbuf[pos++] = 0x00;                  // Landing zone cylinder
				scsi_cmdbuf[pos++] = 0x00;                  // RPL
				scsi_cmdbuf[pos++] = 0x00;                  // Rotational offset
				scsi_cmdbuf[pos++] = 0x00;                  // Reserved
				scsi_cmdbuf[pos++] = uint8_t(10000 >> 8);     // Medium rotation rate
				scsi_cmdbuf[pos++] = uint8_t(10000);          // Medium rotation rate
				scsi_cmdbuf[pos++] = 0x00;                  // Reserved
				scsi_cmdbuf[pos++] = 0x00;                  // Reserved
				break;
			}

			case 0x30: { // Apple firmware ID page
				scsi_cmdbuf[pos++] = 0xb0; // cPS, page id
				scsi_cmdbuf[pos++] = 0x16; // Page length
				scsi_cmdbuf[pos++] = 'A';
				scsi_cmdbuf[pos++] = 'P';
				scsi_cmdbuf[pos++] = 'P';
				scsi_cmdbuf[pos++] = 'L';
				scsi_cmdbuf[pos++] = 'E';
				scsi_cmdbuf[pos++] = ' ';
				scsi_cmdbuf[pos++] = 'C';
				scsi_cmdbuf[pos++] = 'O';
				scsi_cmdbuf[pos++] = 'M';
				scsi_cmdbuf[pos++] = 'P';
				scsi_cmdbuf[pos++] = 'U';
				scsi_cmdbuf[pos++] = 'T';
				scsi_cmdbuf[pos++] = 'E';
				scsi_cmdbuf[pos++] = 'R';
				scsi_cmdbuf[pos++] = ',';
				scsi_cmdbuf[pos++] = ' ';
				scsi_cmdbuf[pos++] = 'I';
				scsi_cmdbuf[pos++] = 'N';
				scsi_cmdbuf[pos++] = 'C';
				scsi_cmdbuf[pos++] = ' ';
				scsi_cmdbuf[pos++] = ' ';
				scsi_cmdbuf[pos++] = ' ';
				break;
			}

			default:
				LOG("mode sense page %02x unhandled\n", page);
				break;
			}
		}
		scsi_cmdbuf[0] = pos;
		if(pos > size)
			pos = size;

		scsi_data_in(0, pos);
		scsi_status_complete(SS_GOOD);
		break;
	}

	case SC_START_STOP_UNIT:
		LOG("command START STOP UNIT\n");
		scsi_status_complete(SS_GOOD);
		break;

	case SC_READ_CAPACITY: {
		LOG("command READ CAPACITY\n");

		hard_disk_info *info = hard_disk_get_info(harddisk);
		uint32_t size = info->cylinders * info->heads * info->sectors - 1;

		scsi_cmdbuf[0] = (size>>24) & 0xff;
		scsi_cmdbuf[1] = (size>>16) & 0xff;
		scsi_cmdbuf[2] = (size>>8) & 0xff;
		scsi_cmdbuf[3] = (size & 0xff);
		scsi_cmdbuf[4] = (info->sectorbytes>>24)&0xff;
		scsi_cmdbuf[5] = (info->sectorbytes>>16)&0xff;
		scsi_cmdbuf[6] = (info->sectorbytes>>8)&0xff;
		scsi_cmdbuf[7] = (info->sectorbytes & 0xff);

		scsi_data_in(0, 8);
		scsi_status_complete(SS_GOOD);
		break;
	}

	case SC_READ_10:
		lba = (scsi_cmdbuf[2]<<24) | (scsi_cmdbuf[3]<<16) | (scsi_cmdbuf[4]<<8) | scsi_cmdbuf[5];
		blocks = (scsi_cmdbuf[7] << 8) | scsi_cmdbuf[8];

		LOG("command READ EXTENDED start=%08x blocks=%04x\n",lba, blocks);

		scsi_data_in(2, blocks*bytes_per_sector);
		scsi_status_complete(SS_GOOD);
		break;

	case SC_WRITE_10:
		lba = (scsi_cmdbuf[2]<<24) | (scsi_cmdbuf[3]<<16) | (scsi_cmdbuf[4]<<8) | scsi_cmdbuf[5];
		blocks = (scsi_cmdbuf[7] << 8) | scsi_cmdbuf[8];

		LOG("command WRITE EXTENDED start=%08x blocks=%04x\n", lba, blocks);

		scsi_data_out(2, blocks*bytes_per_sector);
		scsi_status_complete(SS_GOOD);
		break;

	default:
		LOG("command %02x ***UNKNOWN***\n", scsi_cmdbuf[0]);
		nscsi_full_device::scsi_command();
		break;
	}
}
