//	Altirra - Atari 800/800XL/5200 emulator
//	Native device emulator - serial port interface engine
//	Copyright (C) 2009-2015 Avery Lee
//
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program; if not, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include <stdafx.h>
#include <vd2/system/error.h>
#include <vd2/system/math.h>
#include <vd2/system/time.h>
#include <at/atcore/logging.h>
#include "serialengine.h"
#include "serialhandler.h"

ATLogChannel g_ATSLCSerialEngine(true, false, "serengine", "Serial engine");

ATSSerialEngine::ATSSerialEngine() {
}

ATSSerialEngine::~ATSSerialEngine() {
}

void ATSSerialEngine::Init(IATSSerialHandler *p) {
	mpHandler = p;

	mhCommReadEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (!mhCommReadEvent)
		throw MyMemoryError();

	mhCommWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (!mhCommWriteEvent)
		throw MyMemoryError();

	mhCommStatusEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (!mhCommStatusEvent)
		throw MyMemoryError();

	mOverlappedRead.hEvent = mhCommReadEvent;
	mOverlappedWrite.hEvent = mhCommWriteEvent;
	mOverlappedStatus.hEvent = mhCommStatusEvent;

	mbRun = true;

	ThreadStart();
}

void ATSSerialEngine::Shutdown() {
	if (isThreadAttached()) {
		PostRequest([this]() { mbRun = false; });

		HANDLE h = getThreadHandle();
		MSG msg;
		for(;;) {
			bool actionPending = false;

			DWORD r = MsgWaitForMultipleObjects(1, &h, FALSE, actionPending ? 0 : INFINITE, QS_SENDMESSAGE);

			if (r == WAIT_OBJECT_0)
				break;

			if (r == WAIT_OBJECT_0 + 1) {
				while(PeekMessage(&msg, NULL, 0, 0, PM_QS_SENDMESSAGE | PM_NOYIELD | PM_REMOVE)) {
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}
			}
		}

		ThreadWait();
	}

	if (mhCommWriteEvent) {
		CloseHandle(mhCommWriteEvent);
		mhCommWriteEvent = nullptr;
	}

	if (mhCommReadEvent) {
		CloseHandle(mhCommReadEvent);
		mhCommReadEvent = nullptr;
	}

	if (mhCommStatusEvent) {
		CloseHandle(mhCommStatusEvent);
		mhCommStatusEvent = nullptr;
	}
}

ATSSerialConfig ATSSerialEngine::GetConfig() const {
	return mConfig;
}

void ATSSerialEngine::SetConfig(const ATSSerialConfig& cfg) {
	mConfig = cfg;

	PostRequest([=]() { InternalSetConfig(mConfig); });
}

void ATSSerialEngine::SetConfig(ATSSerialConfig&& cfg) {
	mConfig = std::move(cfg);
}

void ATSSerialEngine::InternalSetConfig(const ATSSerialConfig& cfg) {
	if (mPortPath != cfg.mSerialPath) {
		ClosePort();

		mPortPath = cfg.mSerialPath;
		OpenPort();
	}
}

const void *ATSSerialEngine::LockRead(uint32& len) {
	len = mReadLevel;

	return len ? mReadBuffer + mReadTail : nullptr;
}

void ATSSerialEngine::UnlockRead(uint32 len) {
	VDASSERT(len <= mReadLevel);

	mReadLevel -= len;
	mReadTail += len;
	if (mReadTail >= kReadBufferSize)
		mReadTail -= kReadBufferSize;
}

void *ATSSerialEngine::LockWrite(uint32 len) {
	VDASSERT(len <= kWriteBufferSize);

	if (kWriteBufferSize - mWriteLevel < len)
		return nullptr;

	mWriteLockLen = len;
	return mWriteBuffer + mWriteHead;
}

void ATSSerialEngine::UnlockWrite() {
	if (mWriteLockLen) {
		VDASSERT(mWriteLockLen + mWriteLevel <= kWriteBufferSize);

		if (mWriteTail + mWriteLockLen > kWriteBufferSize) {
			uint32 size1 = kWriteBufferSize - mWriteHead;
			uint32 size2 = mWriteLockLen - size1;

			memcpy(mWriteBuffer + kWriteBufferSize + mWriteHead, mWriteBuffer + mWriteHead, size1);
			memcpy(mWriteBuffer, mWriteBuffer + kWriteBufferSize, size2);

			mWriteHead = size2;
		} else {
			memcpy(mWriteBuffer + kWriteBufferSize + mWriteHead, mWriteBuffer + mWriteHead, mWriteLockLen);
			mWriteHead += mWriteLockLen;

			if (mWriteHead == kWriteBufferSize)
				mWriteHead = 0;
		}

		mWriteLevel += mWriteLockLen;
		
		if (!mbWritePending)
			QueueWrite();
	}

	mWriteLockLen = 0;
}

void ATSSerialEngine::PostRequest(vdfunction<void()>&& fn) {
	{
		VDCriticalSection::AutoLock lock(mMutex);
		mRequests.emplace_back(std::move(fn));
	}

	mRequestSignal.signal();
}

void ATSSerialEngine::SendRequest(vdfunction<void()>&& fn) {
	bool exit = false;
	DWORD tid = ::GetCurrentThreadId();

	{
		VDCriticalSection::AutoLock lock(mMutex);
		mRequests.emplace_back(std::move(fn));
		mRequests.emplace_back([=,&exit]() { exit = true; PostThreadMessage(tid, WM_NULL, 0, 0); });
	}

	mRequestSignal.signal();

	MSG msg;
	for(;;) {
		while(PeekMessage(&msg, NULL, 0, 0, PM_QS_SENDMESSAGE | PM_NOYIELD | PM_REMOVE)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		if (exit)
			break;

		WaitMessage();
	}
}

void ATSSerialEngine::ThreadRun() {
	const double ticksToCyclesFx = 4294967296.0 * 1789772.5 / VDGetPreciseTicksPerSecond();
	const double cyclesToMilliseconds = 1000.0 / 1789772.5;

	mLastRealTime = VDGetPreciseTick();

	HANDLE h[4];
	h[0] = mRequestSignal.getHandle();
	h[1] = mhCommReadEvent;
	h[2] = mhCommStatusEvent;
	h[3] = mhCommWriteEvent;

	mpHandler->OnAttach(*this);

	if (mPortPath.empty())
		g_ATSLCSerialEngine <<= "No serial port has been configured. Go to Settings > Serial Port to configure one.\n";
	else
		OpenPort();

	while(mbRun) {
		uint32 dt1 = mScheduler.GetTicksToNextEvent();
		uint32 dt2 = mSlowScheduler.GetTicksToNextEvent() * 114 - mLastEmuSlowTimeAccum;
		uint32 timeoutDelay = (uint32)VDCeilToInt((double)std::min<uint32>(dt1, dt2) * cyclesToMilliseconds);

		DWORD result = WaitForMultipleObjects(4, h, FALSE, timeoutDelay);

		uint64 realTime = VDGetPreciseTick();
		uint64 rtDelta = realTime - mLastRealTime;
		mLastRealTime = realTime;

		if (rtDelta & (uint64(1) << 63))
			rtDelta = 0;

		mLastEmuTimeAccum += (uint64)VDRoundToInt64(ticksToCyclesFx * (double)rtDelta);

		uint32 dt = (uint32)(mLastEmuTimeAccum >> 32);
		mLastEmuTimeAccum = (uint32)mLastEmuTimeAccum;

		AdvanceTime(dt);

		if (result == WAIT_OBJECT_0)
			ProcessNextRequest();
		else if (result == WAIT_OBJECT_0+1)
			ProcessCommReadEvent();
		else if (result == WAIT_OBJECT_0+2)
			ProcessCommStatusEvent();
		else if (result == WAIT_OBJECT_0+3)
			ProcessCommWriteEvent();
		else if (result != WAIT_TIMEOUT)
			break;
	}
}

void ATSSerialEngine::AdvanceTime(uint32 dt) {
	while(dt) {
		uint32 q = dt;
		uint32 q1 = mScheduler.GetTicksToNextEvent();
		uint32 q2 = mSlowScheduler.GetTicksToNextEvent() * 114 + mLastEmuSlowTimeAccum;

		if (q > q1)
			q = q1;

		if (q > q2)
			q = q2;

		mScheduler.mNextEventCounter += q;
		if (!mScheduler.mNextEventCounter)
			mScheduler.ProcessNextEvent();

		mLastEmuSlowTimeAccum += q;
		uint32 slowq = mLastEmuSlowTimeAccum / 114;
		mLastEmuSlowTimeAccum %= 114;

		if (slowq) {
			mSlowScheduler.mNextEventCounter += slowq;
			mSlowScheduler.ProcessNextEvent();
		}

		dt -= q;
	}
}

void ATSSerialEngine::ProcessNextRequest() {
	vdfunction<void()> fn;
	mMutex.Lock();
	fn = std::move(mRequests.front());
	mRequests.pop_front();
	mMutex.Unlock();

	fn();
}

void ATSSerialEngine::ProcessCommReadEvent() {
	ResetEvent(mhCommReadEvent);
	mbReadPending = false;

	DWORD actual;
	if (GetOverlappedResult(mhPort, &mOverlappedRead, &actual, TRUE))
		OnRead(actual);
}

void ATSSerialEngine::OnRead(DWORD size) {
	if (size) {
		mReadLevel += size;
		VDASSERT(mReadLevel <= kReadBufferSize);

		memcpy(mReadBuffer + kReadBufferSize + mReadHead, mReadBuffer + mReadHead, size);

		mReadHead += size;
		VDASSERT(mReadHead <= kReadBufferSize);
		if (mReadHead >= kReadBufferSize)
			mReadHead = 0;

		mpHandler->OnReadDataAvailable(mReadLevel);

		if (mReadLevel < kReadBufferSize)
			QueueRead();
	}
}

void ATSSerialEngine::QueueRead() {
	if (mhPort == INVALID_HANDLE_VALUE || mbReadPending)
		return;

	for(;;) {
		DWORD actual = 0;
		ResetEvent(&mOverlappedRead.hEvent);
		if (!ReadFile(mhPort, mReadBuffer + mReadHead, std::min<uint32>(kReadBufferSize - mReadHead, kReadBufferSize - mReadLevel), &actual, &mOverlappedRead)) {
			if (GetLastError() == ERROR_IO_PENDING)
				mbReadPending = true;

			break;
		}

		if (!actual)
			break;

		OnRead(actual);
	}
}

void ATSSerialEngine::ProcessCommWriteEvent() {
	ResetEvent(mhCommWriteEvent);
	mbWritePending = false;

	DWORD actual;
	GetOverlappedResult(mhPort, &mOverlappedWrite, &actual, TRUE);
	OnWrite();
}

void ATSSerialEngine::OnWrite() {
	VDASSERT(mWriteLevel >= mWritePendingLen);
	mWriteLevel -= mWritePendingLen;

	mWriteTail += mWritePendingLen;
	VDASSERT(mWriteTail <= kWriteBufferSize);
	if (mWriteTail >= kWriteBufferSize)
		mWriteTail = 0;

	mWritePendingLen = 0;

	if (mWriteLevel)
		QueueWrite();

	mpHandler->OnWriteSpaceAvailable(kWriteBufferSize - mWriteLevel);
}

void ATSSerialEngine::QueueWrite() {
	if (mhPort == INVALID_HANDLE_VALUE || mbWritePending || !mWriteLevel)
		return;

	for(;;) {
		uint32 toWrite = mWriteLevel;

		if (toWrite > kWriteBufferSize - mWriteTail)
			toWrite = kWriteBufferSize - mWriteTail;

		DWORD actual = 0;
		ResetEvent(&mOverlappedWrite.hEvent);

		mWritePendingLen = toWrite;
		if (!WriteFile(mhPort, mWriteBuffer + mWriteTail, toWrite, &actual, &mOverlappedWrite)) {
			if (GetLastError() == ERROR_IO_PENDING)
				mbWritePending = true;

			break;
		}

		FlushFileBuffers(mhPort);

		OnWrite();
	}
}

void ATSSerialEngine::ProcessCommStatusEvent() {
	ResetEvent(mhCommStatusEvent);

	for(;;) {
		uint32 evMask = mEventMask;
		mEventMask = 0;

		ResetEvent(mhCommStatusEvent);

		BOOL result = WaitCommEvent(mhPort, &mEventMask, &mOverlappedStatus);

		if (evMask & EV_TXEMPTY)
			mpHandler->OnWriteBufferEmpty();

		if (evMask & EV_ERR) {
			DWORD errors = 0;
			if (ClearCommError(mhPort, &errors, NULL)) {
				if (errors & CE_FRAME)
					mpHandler->OnReadFramingError();
			}
		}

		if (evMask & EV_RXCHAR)
			QueueRead();

		if (evMask & (EV_RING | EV_CTS | EV_DSR | EV_RLSD))
			UpdateControlState();

		if (!result)
			break;
	}
}

void ATSSerialEngine::UpdateControlState() {
	uint8 state = 0;

	if (mhPort != INVALID_HANDLE_VALUE) {
		DWORD status = 0;

		if (GetCommModemStatus(mhPort, &status)) {
			if (status & MS_RING_ON)
				state |= kATSerialCtlState_Command;
		}
	}

	if (state != mLastControlState) {
		mLastControlState = state;
		mpHandler->OnControlStateChanged(state);
	}
}

void ATSSerialEngine::OpenPort() {
	if (mhPort != INVALID_HANDLE_VALUE)
		return;

	ResetEvent(mhCommStatusEvent);
	ResetEvent(mhCommReadEvent);
	ResetEvent(mhCommWriteEvent);

	mhPort = CreateFileW(mPortPath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
	if (mhPort == INVALID_HANDLE_VALUE)
		return;

	DCB dcb = {};
	if (BuildCommDCB(_T("baud=19200 parity=N data=8 stop=1"), &dcb))
		SetCommState(mhPort, &dcb);

	COMMTIMEOUTS ct = {};
	ct.ReadIntervalTimeout = MAXDWORD;
	ct.ReadTotalTimeoutMultiplier = 0;
	ct.ReadTotalTimeoutConstant = 0;
	ct.WriteTotalTimeoutMultiplier = 0;
	ct.WriteTotalTimeoutConstant = 0;

	SetCommTimeouts(mhPort, &ct);

	SetCommMask(mhPort, EV_CTS | EV_DSR | EV_RING | EV_RLSD | EV_RXCHAR | EV_TXEMPTY);

	mEventMask = 0;

	ProcessCommStatusEvent();

	UpdateControlState();

	QueueRead();

	g_ATSLCSerialEngine("Opened serial port: %ls\n", mPortPath.c_str());
}

void ATSSerialEngine::ClosePort() {
	if (mhPort != INVALID_HANDLE_VALUE) {
		CancelIo(mhPort);
		CloseHandle(mhPort);
		mhPort = INVALID_HANDLE_VALUE;
	}
}
