/*
	Dreamcast serial port.
*/
#include <cerrno>
#include <cstdlib>
#include <fcntl.h>
#ifndef _WIN32
#include <unistd.h>
#include <sys/ioctl.h>
#else
#include <windows.h>
#include <io.h>
#endif
#include "types.h"
#include "hw/sh4/sh4_mmr.h"
#include "hw/sh4/sh4_interrupts.h"
#include "cfg/option.h"
#include "modules.h"
#include "hw/sh4/sh4_sched.h"
#include "serialize.h"

//#define DEBUG_SCIF

#ifdef DEBUG_SCIF
#define SCIF_LOG(...) INFO_LOG(SH4, __VA_ARGS__)
#else
#define SCIF_LOG(...)
#endif

SCIRegisters sci;
SCIFRegisters scif;

static void updateInterrupts()
{
    InterruptPend(sh4_SCIF_TXI, SCIF_SCFSR2.TDFE);
    InterruptMask(sh4_SCIF_TXI, SCIF_SCSCR2.TIE);

    InterruptPend(sh4_SCIF_RXI, SCIF_SCFSR2.RDF || SCIF_SCFSR2.DR);
    InterruptMask(sh4_SCIF_RXI, SCIF_SCSCR2.RIE);

    InterruptPend(sh4_SCIF_BRI, SCIF_SCFSR2.BRK);
    InterruptMask(sh4_SCIF_BRI, SCIF_SCSCR2.RIE || SCIF_SCSCR2.REIE);

    InterruptPend(sh4_SCIF_ERI, SCIF_SCFSR2.ER || SCIF_SCFSR2.FER || SCIF_SCFSR2.PER);
    InterruptMask(sh4_SCIF_ERI, SCIF_SCSCR2.RIE || SCIF_SCSCR2.REIE);
}

int SCIFSerialPort::schedCallback(int tag, int cycles, int lag, void *arg)
{
	SCIFSerialPort& scif = *(SCIFSerialPort *)arg;
	if (tag == 0)
	{
		bool reschedule = scif.txDone();
		scif.rxSched();
		if (reschedule || scif.pipe != nullptr)
			return scif.frameSize * scif.cyclesPerBit;
		else
			return 0;
	}
	else
	{
		scif.sendBreak();
		return 0;
	}
}

bool SCIFSerialPort::isTDFE() const {
	return (int)txFifo.size() <= 1 << (3 - SCIF_SCFCR2.TTRG);
}

bool SCIFSerialPort::isRDF() const {
	constexpr u32 trigLevels[] { 1, 4, 8, 14 };
	return rxFifo.size() >= trigLevels[SCIF_SCFCR2.RTRG];
}

bool SCIFSerialPort::txDone()
{
	if (!transmitting || SCIF_SCFCR2.TFRST == 1)
		return false;
	if (txFifo.empty())
	{
		setStatusBit(TEND);
		transmitting = false;
		return false; // don't reschedule
	}
	u8 v = txFifo.front();
	txFifo.pop_front();
	if (pipe != nullptr)
		pipe->write(v);
	if (isTDFE()) {
		setStatusBit(TDFE);
		updateInterrupts();
	}
	return true;
}

void SCIFSerialPort::rxSched()
{
	if (pipe == nullptr)
		return;

	if (pipe->available() > 0)
	{
		u8 v = pipe->read();
		if (SCIF_SCSCR2.RE == 0 || SCIF_SCFCR2.RFRST == 1)
			return;
		if (rxFifo.size() == 16)
		{
			// rx overrun
			SCIF_SCLSR2.ORER = 1;
			updateInterrupts();
			INFO_LOG(SH4, "scif: Receive overrun");
		}
		else
		{
			rxFifo.push_back(v);
			if (isRDF()) {
				setStatusBit(RDF);
				updateInterrupts();
			}
		}
	}
	else if (!rxFifo.empty())
	{
		setStatusBit(DR);
		updateInterrupts();
	}
}

void SCIFSerialPort::updateBaudRate()
{
	// 1 start bit, 7 or 8 data bits, optional parity bit, 1 or 2 stop bits
	frameSize = 1 + 8 - SCIF_SCSMR2.CHR + SCIF_SCSMR2.PE + 1 + SCIF_SCSMR2.STOP;
	int bauds = SH4_MAIN_CLOCK / 4 / (SCIF_SCBRR2 + 1) / 32 / (1 << (SCIF_SCSMR2.CKS * 2));
	cyclesPerBit = SH4_MAIN_CLOCK / bauds;
	INFO_LOG(SH4, "SCIF: Frame size %d cycles/bit %d (%d bauds) pipe %p", frameSize, cyclesPerBit, bauds, pipe);
	sh4_sched_request(schedId, frameSize * cyclesPerBit);
}

// SCIF SCFTDR2 - Transmit FIFO Data Register
void SCIFSerialPort::SCFTDR2_write(u8 data)
{
	SCIF_LOG("serial out %02x %c fifo_sz %d", data, data == '\0' ? ' ' : data, (int)txFifo.size());
	if (SCIF_SCFCR2.TFRST == 1)
		return;
	if (SCIF_SCSMR2.CHR == 1)
		data &= 0x7f;
	if (txFifo.empty() && !transmitting && SCIF_SCSCR2.TE == 1)
	{
		if (pipe != nullptr)
			pipe->write(data);
		transmitting = true;
		// Need to reschedule so it doesn't happen too early (f355)
		sh4_sched_request(schedId, frameSize * cyclesPerBit);
		setStatusBit(TDFE); // immediately transfer SCFTDR2 into the shift register
		updateInterrupts();
	}
	else if (txFifo.size() < 16) {
		txFifo.push_back(data);
	}
}

// SCIF_SCFSR2 read - Serial Status Register
u16 SCIFSerialPort::readStatus()
{
//	SCIF_LOG("SCIF_SCFSR2.read %s%s%s%s%s%s%s%s",
//			SCIF_SCFSR2.ER ? "ER " : "",
//			SCIF_SCFSR2.TEND ? "TEND " : "",
//			SCIF_SCFSR2.TDFE ? "TDFE " : "",
//			SCIF_SCFSR2.BRK ? "BRK " : "",
//			SCIF_SCFSR2.FER ? "FER " : "",
//			SCIF_SCFSR2.PER ? "PER " : "",
//			SCIF_SCFSR2.RDF ? "RDF " : "",
//			SCIF_SCFSR2.DR ? "DR" : "");
	statusLastRead = SCIF_SCFSR2.full;
	return SCIF_SCFSR2.full;
}

void SCIFSerialPort::setStatusBit(StatusBit bit)
{
	statusLastRead &= ~bit;
	SCIF_SCFSR2.full |= bit;
}

// SCIF_SCFSR2 write - Serial Status Register
void SCIFSerialPort::writeStatus(u16 data)
{
	data = data | ~0x00f3 | ~statusLastRead;
	// RDF and TDFE cannot be reset until the trigger level is reached
	if (isRDF())
		data |= RDF;
	if (isTDFE())
		data |= TDFE;
	if (!rxFifo.empty())
		data |= DR;
	SCIF_LOG("SCIF_SCFSR2.reset %s%s%s%s%s%s%s%s",
			(data & ER)   ? "" : "ER ",
			(data & TEND) ? "" : "TEND ",
			(data & TDFE) ? "" : "TDFE ",
			(data & BRK)  ? "" : "BRK ",
			(data & FER)  ? "" : "FER ",
			(data & PER)  ? "" : "PER ",
			(data & RDF)  ? "" : "RDF ",
			(data & DR)   ? "" : "DR");

	SCIF_SCFSR2.full &= data;
	statusLastRead &= data;

	updateInterrupts();
}

//SCIF_SCFDR2 - FIFO Data Count Register
u16 SCIFSerialPort::SCFDR2_read()
{
	u16 rv = rxFifo.size() | (txFifo.size() << 8);
	SCIF_LOG("SCIF: fifo count rx %d tx %d", rv & 0xff, rv >> 8);

	return rv;
}

//SCIF_SCFRDR2 - Receive FIFO Data Register
u8 SCIFSerialPort::SCFRDR2_read()
{
	if (rxFifo.empty()) {
		INFO_LOG(SH4, "Empty rx fifo read");
		return 0;
	}
	u8 data = rxFifo.front();
	rxFifo.pop_front();
	SCIF_LOG("serial in %02x %c", data, data);
	return data;
}

SCIFSerialPort& SCIFSerialPort::Instance()
{
	static SCIFSerialPort instance;

	return instance;
}

//SCSCR2 - Serial Control Register
static u16 SCSCR2_read(u32 addr)
{
	return SCIF_SCSCR2.full;
}

void SCIFSerialPort::SCSCR2_write(u16 data)
{
	SCIF_SCSCR2.full = data & 0x00fa;
	if (SCIF_SCSCR2.TE == 0)
	{
		setStatusBit(TEND);
		// TE must be cleared to send a break
		setBreak(SCIF_SCSPTR2.SPB2IO == 1 && SCIF_SCSPTR2.SPB2DT == 0);
	}
	else {
		setBreak(false);
	}
	updateInterrupts();
	SCIF_LOG("SCIF_SCSCR2= %s%s%s%s%s",
			SCIF_SCSCR2.TIE ? "TIE " : "",
			SCIF_SCSCR2.RIE ? "RIE " : "",
			SCIF_SCSCR2.TE ? "TE " : "",
			SCIF_SCSCR2.RE ? "RE " : "",
			SCIF_SCSCR2.REIE ? "REIE" : "");
}

// SCSPTR2 - Serial Port Register
static u16 SCSPTR2_read(u32 addr)
{
	SCIF_LOG("SCIF_SCSPTR2.read %x", SCIF_SCSPTR2.full);
	return SCIF_SCSPTR2.full & ~0x10; // CTS active/low
}

void SCIFSerialPort::setBreak(bool on)
{
	if (on) {
		// tetris needs to send/receive breaks
		if (!sh4_sched_is_scheduled(brkSchedId))
			sh4_sched_request(brkSchedId, cyclesPerBit * frameSize);
	}
	else {
		if (sh4_sched_is_scheduled(brkSchedId))
			sh4_sched_request(brkSchedId, -1);
	}
}

void SCIFSerialPort::SCSPTR2_write(u16 data)
{
	SCIF_SCSPTR2.full = data & 0x00f3;
	if (SCIF_SCSPTR2.SPB2IO == 1)
		setBreak(SCIF_SCSPTR2.SPB2DT == 0 && SCIF_SCSCR2.TE == 0);
	else
		setBreak(false);

	SCIF_LOG("SCIF_SCSPTR2= %s%s%s%s%s%s",
			SCIF_SCSPTR2.RTSIO ? "RTSIO " : "",
			SCIF_SCSPTR2.RTSDT ? "RTSDT " : "",
			SCIF_SCSPTR2.CTSIO ? "CTSIO " : "",
			SCIF_SCSPTR2.CTSDT ? "CTSDT " : "",
			SCIF_SCSPTR2.SPB2IO ? "SPB2IO " : "",
			SCIF_SCSPTR2.SPB2DT ? "SPB2DT" : "");
}

// SCFCR2 - FIFO Control Register
u16 SCIFSerialPort::SCFCR2_read(u32 addr)
{
//	SCIF_LOG("SCIF_SCFCR2.read %x", SCIF_SCFCR2.full);
	return SCIF_SCFCR2.full;
}

void SCIFSerialPort::SCFCR2_write(u16 data)
{
	if (SCIF_SCFCR2.TFRST == 1 && !(data & 4))
	{
		// when TFRST 1 -> 0
		// seems to help tetris send data during sync
		setStatusBit(TEND);
		setStatusBit(TDFE);
		updateInterrupts();
	}
	SCIF_SCFCR2.full = data & 0x00ff;
	if (SCIF_SCFCR2.TFRST == 1)
	{
		txFifo.clear();
		if (pipe == nullptr)
			sh4_sched_request(schedId, -1);
		transmitting = false;
	}
	if (SCIF_SCFCR2.RFRST == 1)
		rxFifo.clear();
	SCIF_LOG("SCIF_SCFCR2= %s%s%sTTRG %d RTRG %d",
			SCIF_SCFCR2.RFRST ? "RFRST " : "",
			SCIF_SCFCR2.TFRST ? "TFRST " : "",
			SCIF_SCFCR2.MCE ? "MCE " : "",
			SCIF_SCFCR2.TTRG,
			SCIF_SCFCR2.RTRG);
}

// SCBRR2 - Bit Rate Register
void SCIFSerialPort::SCBRR2_write(u32 addr, u8 data)
{
	SCIF_SCBRR2 = data;
	Instance().updateBaudRate();
}

// SCSMR2 - Serial Mode Register
void SCIFSerialPort::SCSMR2_write(u32 addr, u16 data)
{
	SCIF_SCSMR2.full = data & 0x007b;
	Instance().updateBaudRate();
}

void SCIFSerialPort::receiveBreak()
{
	SCIF_LOG("Break received");
	setStatusBit(BRK);
	updateInterrupts();
}

void SCIFSerialPort::sendBreak()
{
	if (pipe != nullptr)
		pipe->sendBreak();
}


void SCIFSerialPort::init()
{
	if (schedId == -1)
		schedId = sh4_sched_register(0, schedCallback, this);
	if (brkSchedId == -1)
		brkSchedId = sh4_sched_register(1, schedCallback, this);
}

void SCIFSerialPort::term()
{
	if (schedId != -1) {
		sh4_sched_unregister(schedId);
		schedId = -1;
	}
	if (brkSchedId != -1) {
		sh4_sched_unregister(brkSchedId);
		brkSchedId = -1;
	}
}

void SCIFSerialPort::reset()
{
	sh4_sched_request(brkSchedId, -1);
	transmitting = false;
	statusLastRead = 0;
	txFifo.clear();
	rxFifo.clear();
	updateBaudRate();
}

void SCIFSerialPort::serialize(Serializer& ser)
{
	sh4_sched_serialize(ser, schedId);
	sh4_sched_serialize(ser, brkSchedId);
	ser << statusLastRead;
	ser << (int)txFifo.size();
	for (u8 b : txFifo)
		ser << b;
	ser << (int)rxFifo.size();
	for (u8 b : rxFifo)
		ser << b;
	ser << transmitting;
}

void SCIFSerialPort::deserialize(Deserializer& deser)
{
	txFifo.clear();
	rxFifo.clear();
	if (deser.version() >= Deserializer::V43)
	{
		sh4_sched_deserialize(deser, schedId);
		sh4_sched_deserialize(deser, brkSchedId);
		deser >> statusLastRead;
		int size;
		deser >> size;
		for (int i = 0; i < size; i++)
		{
			u8 b;
			deser >> b;
			txFifo.push_back(b);
		}
		deser >> size;
		for (int i = 0; i < size; i++)
		{
			u8 b;
			deser >> b;
			rxFifo.push_back(b);
		}
		deser >> transmitting;
	}
	else
	{
		statusLastRead = 0;
		transmitting = false;
	}
	SCIF_SCBRR2 &= 0xff;	// work around previous issues with dynarecs
	updateBaudRate();
}

struct PTYPipe : public SerialPort::Pipe
{
	void write(u8 data) override
	{
		if (config::SerialConsole) {
			int rc = ::write(tty, &data, 1);
			(void)rc;
		}
	}

	int available() override {
		int count = 0;
#if defined(__unix__) || defined(__APPLE__)
		if (config::SerialConsole && tty != 1)
			ioctl(tty, FIONREAD, &count);
#endif
		return count;
	}

	u8 read() override
	{
		u8 data = 0;
		if (tty != 1) {
			int rc = ::read(tty, &data, 1);
			(void)rc;
		}
		return data;
	}

	void init()
	{
#if defined(__unix__) || defined(__APPLE__)
		if (config::SerialConsole && config::SerialPTY && tty == 1)
		{
			tty = open("/dev/ptmx", O_RDWR | O_NDELAY | O_NOCTTY | O_NONBLOCK);
			if (tty < 0)
			{
				ERROR_LOG(BOOT, "Cannot open /dev/ptmx: errno %d", errno);
				tty = 1;
			}
			else
			{
				grantpt(tty);
				unlockpt(tty);
				NOTICE_LOG(BOOT, "Pseudoterminal is at %s", ptsname(tty));
			}
		}
#elif defined(_WIN32)
		if (config::SerialConsole && tty == 1)
		{
			if (AllocConsole())
			{
				SetConsoleTitle(TEXT("Flycast Serial Output"));

				// Pipe stdout
				HANDLE hStd = GetStdHandle(STD_OUTPUT_HANDLE);
				tty = _open_osfhandle((intptr_t)hStd, _O_TEXT);
				_dup2(tty, fileno(stdout));
				SetStdHandle(STD_OUTPUT_HANDLE, (HANDLE)_get_osfhandle(fileno(stdout)));
			}
			else
			{
				ERROR_LOG(BOOT, "Cannot AllocConsole(): errno %d", GetLastError());
			}
		}
#endif
		SCIFSerialPort::Instance().setPipe(this);
	}

	void term()
	{
		if (tty != 1)
		{
			::close(tty);
			tty = 1;
		}
		SCIFSerialPort::Instance().setPipe(nullptr);
	}

private:
	int tty = 1;
};

void setupPtyPipe()
{
	static PTYPipe ptyPipe;

	if (config::SerialConsole || config::SerialPTY)
	{
		if (SCIFSerialPort::Instance().getPipe() == nullptr)
			ptyPipe.init();
	}
	else
	{
		if (SCIFSerialPort::Instance().getPipe() == &ptyPipe)
			ptyPipe.term();
	}
}

template <typename T>
class SingletonForward {

};

template<typename Ret, typename Class, typename... Args>
struct SingletonForward<Ret(Class::*)(Args...)>
{
	static Ret(*forward(Ret(*function)(u32 addr, Args...)))(u32 addr, Args...) {
		return function;
	}
};

#define SINGLETON_FORWARD(accessor, function) \
	SingletonForward<decltype(&std::remove_reference<decltype(accessor)>::type::function)>::forward([](u32 addr, auto... args) { \
		return accessor.function(args...); \
	})

//Init term res
void SCIFRegisters::init()
{
	super::init();

	// Serial Communication Interface with FIFO

	//SCIF SCSMR2 0xFFE80000 0x1FE80000 16 0x0000 0x0000 Held Held Pclk
	setWriteHandler<SCIF_SCSMR2_addr, u16>(SCIFSerialPort::SCSMR2_write);

	//SCIF SCBRR2 0xFFE80004 0x1FE80004 8 0xFF 0xFF Held Held Pclk
	setWriteHandler<SCIF_SCBRR2_addr, u8>(SCIFSerialPort::SCBRR2_write);

	//SCIF SCSCR2 0xFFE80008 0x1FE80008 16 0x0000 0x0000 Held Held Pclk
	setHandlers<SCIF_SCSCR2_addr>(SCSCR2_read, SINGLETON_FORWARD(SCIFSerialPort::Instance(), SCSCR2_write));

	//SCIF SCFTDR2 0xFFE8000C 0x1FE8000C 8 Undefined Undefined Held Held Pclk
	setWriteOnly<SCIF_SCFTDR2_addr>(SINGLETON_FORWARD(SCIFSerialPort::Instance(), SCFTDR2_write));

	//SCIF SCFSR2 0xFFE80010 0x1FE80010 16 0x0060 0x0060 Held Held Pclk
	setHandlers<SCIF_SCFSR2_addr>(SINGLETON_FORWARD(SCIFSerialPort::Instance(), readStatus),
			SINGLETON_FORWARD(SCIFSerialPort::Instance(), writeStatus));

	//READ only
	//SCIF SCFRDR2 0xFFE80014 0x1FE80014 8 Undefined Undefined Held Held Pclk
	setReadOnly<SCIF_SCFRDR2_addr>(SINGLETON_FORWARD(SCIFSerialPort::Instance(), SCFRDR2_read));

	//SCIF SCFCR2 0xFFE80018 0x1FE80018 16 0x0000 0x0000 Held Held Pclk
	setHandlers<SCIF_SCFCR2_addr>(SCIFSerialPort::SCFCR2_read, SINGLETON_FORWARD(SCIFSerialPort::Instance(), SCFCR2_write));

	//Read only
	//SCIF SCFDR2 0xFFE8001C 0x1FE8001C 16 0x0000 0x0000 Held Held Pclk
	setReadOnly<SCIF_SCFDR2_addr>(SINGLETON_FORWARD(SCIFSerialPort::Instance(), SCFDR2_read));

	//SCIF SCSPTR2 0xFFE80020 0x1FE80020 16 0x0000 0x0000 Held Held Pclk
	setHandlers<SCIF_SCSPTR2_addr>(SCSPTR2_read, SINGLETON_FORWARD(SCIFSerialPort::Instance(), SCSPTR2_write));

	//SCIF SCLSR2 0xFFE80024 0x1FE80024 16 0x0000 0x0000 Held Held Pclk
	setRW<SCIF_SCLSR2_addr, u16, 1>();

	SCIFSerialPort::Instance().init();

	reset(true);
}

void SCIFRegisters::reset(bool hard)
{
	super::reset();

	/*
	SCIF SCSMR2 H'FFE8 0000 H'1FE8 0000 16 H'0000 H'0000 Held Held Pclk
	SCIF SCBRR2 H'FFE8 0004 H'1FE8 0004 8 H'FF H'FF Held Held Pclk
	SCIF SCSCR2 H'FFE8 0008 H'1FE8 0008 16 H'0000 H'0000 Held Held Pclk
	SCIF SCFTDR2 H'FFE8 000C H'1FE8 000C 8 Undefined Undefined Held Held Pclk
	SCIF SCFSR2 H'FFE8 0010 H'1FE8 0010 16 H'0060 H'0060 Held Held Pclk
	SCIF SCFRDR2 H'FFE8 0014 H'1FE8 0014 8 Undefined Undefined Held Held Pclk
	SCIF SCFCR2 H'FFE8 0018 H'1FE8 0018 16 H'0000 H'0000 Held Held Pclk
	SCIF SCFDR2 H'FFE8 001C H'1FE8 001C 16 H'0000 H'0000 Held Held Pclk
	SCIF SCSPTR2 H'FFE8 0020 H'1FE8 0020 16 H'0000*2 H'0000*2 Held Held Pclk
	SCIF SCLSR2 H'FFE8 0024 H'1FE8 0024 16 H'0000 H'0000 Held Held Pclk
	*/
	SCIF_SCBRR2 = 0xFF;
	SCIF_SCFSR2.full = 0x060;

	if (hard)
		SCIFSerialPort::Instance().setPipe(nullptr);
	SCIFSerialPort::Instance().reset();
}

void SCIFRegisters::term()
{
	SCIFSerialPort::Instance().term();

	super::term();
}

void SCIRegisters::init()
{
	super::init();

	// Serial Communication Interface
	setRW<SCI_SCSMR1_addr, u8>();
	setRW<SCI_SCBRR1_addr, u8>();
	setRW<SCI_SCSCR1_addr, u8>();
	setRW<SCI_SCTDR1_addr, u8>();
	setRW<SCI_SCSSR1_addr, u8, 0xf9>();
	setReadOnly<SCI_SCRDR1_addr, u8>();
	setRW<SCI_SCSPTR1_addr, u8, 0x8f>();

	reset();
}

void SCIRegisters::reset()
{
	super::reset();

	SCI_SCBRR1 = 0xff;
	SCI_SCTDR1 = 0xff;
	SCI_SCSSR1 = 0x84;
}
