// (C) 2018-2022 by Folkert van Heusden
// Released under Apache License v2.0
#include <errno.h>
#include <string.h>

#include "bus.h"
#include "cpu.h"
#include "error.h"
#if defined(ESP32)
#include "esp32.h"
#endif
#include "gen.h"
#include "rk05.h"
#include "utils.h"


const char * const regnames[] = { 
	"RK05_DS drivestatus",
	"RK05_ERROR        ",
	"RK05_CS ctrlstatus",
	"RK05_WC word count",
	"RK05_BA busaddress",
	"RK05_DA disk addrs",
	"RK05_DATABUF      "
	};

rk05::rk05(const std::vector<std::string> & files, bus *const b, std::atomic_bool *const disk_read_acitivity, std::atomic_bool *const disk_write_acitivity) :
	b(b),
	disk_read_acitivity(disk_read_acitivity),
	disk_write_acitivity(disk_write_acitivity)
{
	memset(registers, 0x00, sizeof registers);
	memset(xfer_buffer, 0x00, sizeof xfer_buffer);

#if defined(ESP32)
	Serial.print(F("MISO: "));
	Serial.println(int(MISO));
	Serial.print(F("MOSI: "));
	Serial.println(int(MOSI));
	Serial.print(F("SCK : "));
	Serial.println(int(SCK));
	Serial.print(F("SS  : "));
	Serial.println(int(SS));

	Serial.println(F("Files on SD-card:"));

	if (!sd.begin(SS, SD_SCK_MHZ(15)))
		sd.initErrorHalt();

	for(;;) {
		sd.ls("/", LS_DATE | LS_SIZE | LS_R);

		while(Serial.available())
			Serial.read();

		std::string selected_file = read_terminal_line("Enter filename: ");

		Serial.print(F("Opening file: "));
		Serial.println(selected_file.c_str());

		if (fh.open(selected_file.c_str(), O_RDWR))
			break;

		Serial.println(F("rk05: open failed"));
	}
#else
	for(auto file : files) {
		FILE *fh = fopen(file.c_str(), "rb");
		if (!fh)
			error_exit(true, "rk05: cannot open \"%s\"", file.c_str());

		fhs.push_back(fh);
	}
#endif
}

rk05::~rk05()
{
#if defined(ESP32)
	fh.close();
#else
	for(auto fh : fhs)
		fclose(fh);
#endif
}

uint8_t rk05::readByte(const uint16_t addr)
{
	uint16_t v = readWord(addr & ~1);

	if (addr & 1)
		return v >> 8;

	return v;
}

uint16_t rk05::readWord(const uint16_t addr)
{
	const int reg = (addr - RK05_BASE) / 2;

	if (addr == RK05_DS) {		// 0177400
		setBit(registers[reg], 11, true); // disk on-line
		setBit(registers[reg], 8, true); // sector ok
		setBit(registers[reg], 7, true); // drive ready
		setBit(registers[reg], 6, true); // seek ready
		setBit(registers[reg], 4, true); // heads in position
	}
	else if (addr == RK05_ERROR)	// 0177402
		registers[reg] = 0;
	else if (addr == RK05_CS) {	// 0177404
		setBit(registers[reg], 15, false); // clear error
		setBit(registers[reg], 14, false); // clear hard error
		setBit(registers[reg], 7, true); // controller ready
	}

	uint16_t vtemp = registers[reg];

	if (addr == RK05_CS)
		setBit(registers[reg], 0, false); // clear go

	D(fprintf(stderr, "RK05 read %s/%o: %06o\n", reg[regnames], addr, vtemp);)

	return vtemp;
}

void rk05::writeByte(const uint16_t addr, const uint8_t v)
{
	uint16_t vtemp = registers[(addr - RK05_BASE) / 2];

	if (addr & 1) {
		vtemp &= ~0xff00;
		vtemp |= v << 8;
	}
	else {
		vtemp &= ~0x00ff;
		vtemp |= v;
	}

	writeWord(addr, vtemp);
}

void rk05::writeWord(const uint16_t addr, uint16_t v)
{
#if defined(ESP32)
	digitalWrite(LED_BUILTIN, LOW);
#endif

	const int reg = (addr - RK05_BASE) / 2;

	registers[reg] = v;

	if (addr == RK05_CS) {
		if (v & 1) { // GO
			const int    func   = (v >> 1) & 7; // FUNCTION
			int16_t      wc     = registers[(RK05_WC - RK05_BASE) / 2];
			const size_t reclen = wc < 0 ? (-wc * 2) : wc * 2;

			uint16_t temp     = registers[(RK05_DA - RK05_BASE) / 2];
			uint8_t  sector   = temp & 0b1111;
			uint8_t  surface  = (temp >> 4) & 1;
			int      track    = (temp >> 4) & 511;
			uint16_t cylinder = (temp >> 5) & 255;
			uint8_t  device   = temp >> 13;

			const int diskoff = track * 12 + sector;

			const int diskoffb = diskoff * 512; // RK05 is high density
			const uint16_t memoff = registers[(RK05_BA - RK05_BASE) / 2];

			registers[(RK05_CS - RK05_BASE) / 2] &= ~(1 << 13); // reset search complete

			if (func == 0) { // controller reset
				D(fprintf(stderr, "RK05 invoke %d (controller reset)\n", func);)

			}
			else if (func == 1) { // write
				*disk_write_acitivity = true;

				D(fprintf(stderr, "RK05 drive %d position sec %d surf %d cyl %d, reclen %zo, WRITE to %o, mem: %o\n", device, sector, surface, cylinder, reclen, diskoffb, memoff);)

				uint32_t p = reclen; // FIXME
				for(size_t i=0; i<reclen; i++)
					xfer_buffer[i] = b->readUnibusByte(memoff + i);

#if defined(ESP32)
				if (!fh.seek(diskoffb))
					fprintf(stderr, "RK05 seek error %s\n", strerror(errno));
				if (fh.write(xfer_buffer, reclen) != reclen)
                                        fprintf(stderr, "RK05 fwrite error %s\n", strerror(errno));
#else
				FILE *fh = fhs.at(device);
				if (fseek(fh, diskoffb, SEEK_SET) == -1)
					fprintf(stderr, "RK05 seek error %s\n", strerror(errno));
				if (fwrite(xfer_buffer, 1, reclen, fh) != reclen)
					fprintf(stderr, "RK05 fwrite error %s\n", strerror(errno));
#endif

				if (v & 2048)
					D(fprintf(stderr, "RK05 inhibit BA increase\n");)
				else
					registers[(RK05_BA - RK05_BASE) / 2] += p;

				if (++sector >= 12) {
					sector = 0;
					if (++surface >= 2) {
						surface = 0;
						cylinder++;
					}
				}

				registers[(RK05_DA - RK05_BASE) / 2] = sector | (surface << 4) | (cylinder << 5);

				*disk_write_acitivity = false;
			}
			else if (func == 2) { // read
				*disk_read_acitivity = true;

				D(fprintf(stderr, "RK05 drive %d position sec %d surf %d cyl %d, reclen %zo, READ from %o, mem: %o\n", device, sector, surface, cylinder, reclen, diskoffb, memoff);)

				bool proceed = true;

#if defined(ESP32)
				if (!fh.seek(diskoffb)) {
					fprintf(stderr, "RK05 seek error %s\n", strerror(errno));
					proceed = false;
				}
#else
				FILE *fh = nullptr;

				if (device >= fhs.size())
					proceed = false;
				else {
					fh = fhs.at(device);

					if (fseek(fh, diskoffb, SEEK_SET) == -1) {
						fprintf(stderr, "RK05 seek error %s\n", strerror(errno));
						proceed = false;
					}
				}
#endif

				uint32_t temp = reclen;
				uint32_t p = memoff;
				while(proceed && temp > 0) {
					uint32_t cur = std::min(uint32_t(sizeof xfer_buffer), temp);

#if defined(ESP32)
					yield();

					if (fh.read(xfer_buffer, cur) != size_t(cur))
						D(fprintf(stderr, "RK05 fread error: %s\n", strerror(errno));)
#else
					if (fread(xfer_buffer, 1, cur, fh) != size_t(cur))
						D(fprintf(stderr, "RK05 fread error: %s\n", strerror(errno));)
#endif

					for(uint32_t i=0; i<cur; i++) {
						if (p < 0160000)
							b -> writeUnibusByte(p, xfer_buffer[i]);
						p++;
					}

					temp -= cur;
				}

				if (v & 2048)
					D(fprintf(stderr, "RK05 inhibit BA increase\n");)
				else
					registers[(RK05_BA - RK05_BASE) / 2] += p;

				if (++sector >= 12) {
					sector = 0;
					if (++surface >= 2) {
						surface = 0;
						cylinder++;
					}
				}

				registers[(RK05_DA - RK05_BASE) / 2] = sector | (surface << 4) | (cylinder << 5);

				*disk_read_acitivity = false;
			}
			else if (func == 4) {
				D(fprintf(stderr, "RK05 invoke %d (seek) to %o\n", func, diskoffb);)

				registers[(RK05_CS - RK05_BASE) / 2] |= 1 << 13; // search complete
			}
			else if (func == 7) {
				D(fprintf(stderr, "RK05 invoke %d (write lock)\n", func);)
			}
			else {
				D(fprintf(stderr, "RK05 command %d UNHANDLED\n", func);)
			}

			registers[(RK05_WC - RK05_BASE) / 2] = 0;

			registers[(RK05_DS - RK05_BASE) / 2] |= 64;  // drive ready
			registers[(RK05_CS - RK05_BASE) / 2] |= 128;  // control ready

			// bit 6, invoke interrupt when done vector address 220, see http://www.pdp-11.nl/peripherals/disk/rk05-info.html
			if (v & 64) {
				registers[(RK05_DS - RK05_BASE) / 2] &= ~(7 << 13);  // store id of the device that caused the interrupt
				registers[(RK05_DS - RK05_BASE) / 2] |= device << 13;

				b->getCpu()->queue_interrupt(5, 0220);
			}
		}
	}

#if defined(ESP32)
	digitalWrite(LED_BUILTIN, HIGH);
#endif
}