/*
 * Copyright (c) 2018-2021 Niklas Ekström
 */

#include <exec/types.h>
#include <exec/ports.h>
#include <exec/tasks.h>
#include <exec/memory.h>

#include <libraries/dos.h>
#include <libraries/dosextens.h>

#include <proto/dos.h>
#include <proto/exec.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#include <clib/alib_protos.h>
#include <proto/expansion.h>
#include <clib/expansion_protos.h>

#include "../../a314device/a314.h"
#include "../../a314device/proto_a314.h"

#define PICMD_SERVICE_NAME "picmd"

#define ID_314_DISK (('3' << 24) | ('1' << 16) | ('4' << 8))

struct MsgPort *sync_mp;
struct MsgPort *async_mp;

struct A314_IORequest *read_ior;
struct A314_IORequest *sync_ior;

struct Library *A314Base;

struct FileHandle *con;

ULONG socket;

UBYTE arbuf[256];

struct StandardPacket sync_sp;
struct StandardPacket wait_sp;

BOOL pending_a314_read = FALSE;
BOOL pending_con_wait = FALSE;
BOOL stream_closed = FALSE;

ULONG a314_addr = 0xFFFFFFFF;

//#define DEBUG printf
#define DEBUG(...)
#define printf(...)

void put_con_sp(struct MsgPort *reply_port, struct StandardPacket *sp, LONG action, LONG arg1, LONG arg2, LONG arg3)
{
	sp->sp_Msg.mn_Node.ln_Type = NT_MESSAGE;
	sp->sp_Msg.mn_Node.ln_Pri = 0;
	sp->sp_Msg.mn_Node.ln_Name = (char *)&(sp->sp_Pkt);
	sp->sp_Msg.mn_Length = sizeof(struct StandardPacket);
	sp->sp_Msg.mn_ReplyPort = reply_port;
	sp->sp_Pkt.dp_Link = &(sp->sp_Msg);
	sp->sp_Pkt.dp_Port = reply_port;
	sp->sp_Pkt.dp_Type = action;
	sp->sp_Pkt.dp_Arg1 = arg1;
	sp->sp_Pkt.dp_Arg2 = arg2;
	sp->sp_Pkt.dp_Arg3 = arg3;
	PutMsg(con->fh_Type, &(sp->sp_Msg));
}

LONG set_screen_mode(LONG mode)
{
	put_con_sp(sync_mp, &sync_sp, ACTION_SCREEN_MODE, mode, 0, 0);
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_sp.sp_Pkt.dp_Res1;
}

LONG con_write(char *s, int length)
{
	put_con_sp(sync_mp, &sync_sp, ACTION_WRITE, con->fh_Arg1, (LONG)s, length);
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_sp.sp_Pkt.dp_Res1;
}

LONG con_read(char *s, int length)
{
	put_con_sp(sync_mp, &sync_sp, ACTION_READ, con->fh_Arg1, (LONG)s, length);
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_sp.sp_Pkt.dp_Res1;
}

void start_con_wait()
{
	put_con_sp(async_mp, &wait_sp, ACTION_WAIT_CHAR, 100000, 0, 0);
	pending_con_wait = TRUE;
}

void start_a314_cmd(struct MsgPort *reply_port, struct A314_IORequest *ior, UWORD cmd, char *buffer, int length)
{
	ior->a314_Request.io_Message.mn_ReplyPort = reply_port;
	ior->a314_Request.io_Command = cmd;
	ior->a314_Request.io_Error = 0;
	ior->a314_Socket = socket;
	ior->a314_Buffer = buffer;
	ior->a314_Length = length;
	SendIO((struct IORequest *)ior);
}

BYTE a314_connect(char *name)
{
	socket = time(NULL);
	start_a314_cmd(sync_mp, sync_ior, A314_CONNECT, name, strlen(name));
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_ior->a314_Request.io_Error;
}

BYTE a314_write(char *buffer, int length)
{
	ULONG *bef = (ULONG *)buffer;
	DEBUG("Buf[0]: %.8X Buf[1]: %.8X\n", bef[0], bef[1]);
	DEBUG("Len: %d\n", length);
	start_a314_cmd(sync_mp, sync_ior, A314_WRITE, buffer, length);
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_ior->a314_Request.io_Error;
}

BYTE a314_eos()
{
	start_a314_cmd(sync_mp, sync_ior, A314_EOS, NULL, 0);
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_ior->a314_Request.io_Error;
}

BYTE a314_reset()
{
	start_a314_cmd(sync_mp, sync_ior, A314_RESET, NULL, 0);
	Wait(1L << sync_mp->mp_SigBit);
	GetMsg(sync_mp);
	return sync_ior->a314_Request.io_Error;
}

void start_a314_read()
{
	start_a314_cmd(async_mp, read_ior, A314_READ, arbuf, 255);
	pending_a314_read = TRUE;
}

void handle_con_wait_completed()
{
	DEBUG("handling con wait completed.\n");
	pending_con_wait = FALSE;

	if (stream_closed)
		return;

	if (wait_sp.sp_Pkt.dp_Res1 == DOSFALSE)
	{
		start_con_wait();
	}
	else
	{
		unsigned char buf[64];
		int len = con_read(buf, sizeof(buf));

		if (len == 0 || len == -1)
		{
			a314_reset();
			stream_closed = TRUE;
		}
		else
		{
			a314_write(buf, len);
			start_con_wait();
		}
	}
}

void handle_a314_read_completed()
{
	DEBUG("handling read completed.\n");
	pending_a314_read = FALSE;

	if (stream_closed)
		return;

	int res = read_ior->a314_Request.io_Error;
	if (res == A314_READ_OK)
	{
		UBYTE *p = read_ior->a314_Buffer;
		int len = read_ior->a314_Length;

		con_write(p, len);
		start_a314_read();
	}
	else if (res == A314_READ_EOS)
	{
		a314_eos();
		stream_closed = TRUE;
	}
	else if (res == A314_READ_RESET)
	{
		stream_closed = TRUE;
	}
}

UBYTE *create_and_send_start_msg(int *buffer_len, BPTR current_dir, int argc, char **argv, short rows, short cols)
{
	int buf_len = 6;

	int component_count = 0;
	UBYTE *components[20];

	DEBUG("casmm: SetupDir\n");
	if (current_dir != 0)
	{
		struct FileLock *fl = (struct FileLock *)BADDR(current_dir);
		struct DeviceList *dl = (struct DeviceList *)BADDR(fl->fl_Volume);

		if (dl->dl_DiskType == ID_314_DISK)
		{
			struct FileInfoBlock *fib = AllocMem(sizeof(struct FileInfoBlock), 0);

			BPTR lock = DupLock(current_dir);

			while (lock != 0)
			{
				if (Examine(lock, fib) == 0)
				{
					UnLock(lock);
					break;
				}

				int n = strlen(fib->fib_FileName);
				UBYTE *p = AllocMem(n + 1, 0);
				p[0] = (UBYTE)n;
				memcpy(&p[1], fib->fib_FileName, n);
				components[component_count++] = p;

				buf_len += n + 1;

				BPTR child = lock;
				lock = ParentDir(child);
				UnLock(child);
			}

			FreeMem(fib, sizeof(struct FileInfoBlock));
		}
	}

	DEBUG("casmm: Stage 2\n");
	for (int i = 1; i < argc; i++)
		buf_len += strlen(argv[i]) + 1;

	UBYTE *buffer = AllocMem(buf_len, MEMF_FAST);

	UBYTE *p = buffer;

	*(short *)p = rows;
	p += 2;
	*(short *)p = cols;
	p += 2;

	DEBUG("casmm: Stage 3\n");
	DEBUG("p: %.8X\n", (ULONG)p);
	DEBUG("component count: %d\n", component_count);
	*p++ = (UBYTE)component_count;
	for (int i = 0; i < component_count; i++)
	{
		UBYTE *q = components[component_count - 1 - i];
		int n = *q;
		memcpy(p, q, n + 1);
		p += n + 1;
		FreeMem(q, n + 1);
	}

	DEBUG("casmm: Stage 4\n");
	*p++ = (UBYTE)(argc - 1);
	for (int i = 1; i < argc; i++)
	{
		UBYTE *q = (UBYTE *)argv[i];
		int n = strlen(q);
		*p++ = (UBYTE)n;
		memcpy(p, q, n);
		p += n;
	}

	DEBUG("casmm: Stage 5\n");
	ULONG buf_desc[2] = {(ULONG)buffer, buf_len};
	DEBUG("casmm: Stage 6\n");
	a314_write((char *)buf_desc, sizeof(buf_desc));

	DEBUG("casmm: Stage 7\n");
	*buffer_len = buf_len;
	return buffer;
}

int main(int argc, char **argv)
{
	ULONG board_addr = 0xFFFFFFFF;
    struct ExpansionBase *ExpansionBase = (struct ExpansionBase *)OpenLibrary((STRPTR)"expansion.library", 0L);

    if (ExpansionBase == NULL) {
	    printf("Failed to open expansion.library.\n");
		return 0;
  	}
	else {
		struct ConfigDev* cd = NULL;
		cd = (struct ConfigDev*)FindConfigDev(cd, 2011, 0xA3);
		if (cd != NULL)
			board_addr = (unsigned int)cd->cd_BoardAddr;
		else {
			printf ("Failed to find A314 emulation device.\n");
	        CloseLibrary((struct Library *)ExpansionBase);
			return 0;
		}
        CloseLibrary((struct Library *)ExpansionBase);
	}
	printf ("A314 emulation device found at $%.8X\n", board_addr);
	a314_addr = board_addr;

	sync_mp = CreatePort(NULL, 0);
	if (sync_mp == NULL)
	{
		printf("Unable to create sync reply message port\n");
		return 0;
	}
	DEBUG("Created sync reply message port.\n");

	async_mp = CreatePort(NULL, 0);
	if (async_mp == NULL)
	{
		printf("Unable to create async reply message port\n");
		DeletePort(sync_mp);
		return 0;
	}
	DEBUG("Created async reply message port.\n");

	sync_ior = (struct A314_IORequest *)CreateExtIO(sync_mp, sizeof(struct A314_IORequest));
	if (sync_ior == NULL)
	{
		printf("Unable to create io request for synchronous commands\n");
		DeletePort(async_mp);
		DeletePort(sync_mp);
		return 0;
	}
	DEBUG("Created IORequest for synchronous commands.\n");

	read_ior = (struct A314_IORequest *)CreateExtIO(sync_mp, sizeof(struct A314_IORequest));
	if (read_ior == NULL)
	{
		printf("Unable to create io request for reads\n");
		DeleteExtIO((struct IORequest *)sync_ior);
		DeletePort(async_mp);
		DeletePort(sync_mp);
		return 0;
	}
	DEBUG("Created IORequest for reads.\n");

	if (OpenDevice(A314_NAME, 0, (struct IORequest *)sync_ior, 0) != 0)
	{
		printf("Unable to open a314.device\n");
		DeleteExtIO((struct IORequest *)read_ior);
		DeleteExtIO((struct IORequest *)sync_ior);
		DeletePort(async_mp);
		DeletePort(sync_mp);
		return 0;
	}
	DEBUG("Opened a314.device.\n");

	memcpy(read_ior, sync_ior, sizeof(struct A314_IORequest));

	A314Base = &(sync_ior->a314_Request.io_Device->dd_Library);

	if (a314_connect(PICMD_SERVICE_NAME) != A314_CONNECT_OK)
	{
		printf("Unable to connect to picmd service\n");
		CloseDevice((struct IORequest *)sync_ior);
		DeleteExtIO((struct IORequest *)read_ior);
		DeleteExtIO((struct IORequest *)sync_ior);
		DeletePort(async_mp);
		DeletePort(sync_mp);
		return 0;
	}
	DEBUG("Connected to picmd service.\n");

	struct Process *proc = (struct Process *)FindTask(NULL);
	con = (struct FileHandle *)BADDR(proc->pr_CIS);

	set_screen_mode(DOSTRUE);
	DEBUG("Set screen mode.\n");

	con_write("\x9b" "0 q", 4);

	int len = con_read(arbuf, 32);	// "\x9b" "1;1;33;77 r"
	if (len < 10 || arbuf[len - 1] != 'r')
	{
		printf("Failure to receive window bounds report\n");
		set_screen_mode(DOSFALSE);
		a314_reset();
		CloseDevice((struct IORequest *)sync_ior);
		DeleteExtIO((struct IORequest *)read_ior);
		DeleteExtIO((struct IORequest *)sync_ior);
		DeletePort(async_mp);
		DeletePort(sync_mp);
		return 0;
	}
	DEBUG("Received window bounds report.\n");

	con_write("\x9b" "12{", 4);

	int start = 5;
	int ind = start;
	while (arbuf[ind] != ';')
		ind++;
	arbuf[ind] = 0;
	int rows = atoi(arbuf + start);
	ind++;
	start = ind;
	while (arbuf[ind] != ' ')
		ind++;
	arbuf[ind] = 0;
	int cols = atoi(arbuf + start);

	int start_msg_len;
	DEBUG("Sending start message.\n");
	UBYTE *start_msg = create_and_send_start_msg(&start_msg_len, proc->pr_CurrentDir, argc, argv, (short)rows, (short)cols);
	DEBUG("Sent start message.\n");

	DEBUG("Started con wait.\n");
	start_con_wait();
	DEBUG("Started A314 read.\n");
	start_a314_read();

	ULONG portsig = 1L << async_mp->mp_SigBit;

	DEBUG("Entering main loop.\n");
	while (TRUE)
	{
		ULONG signal = Wait(portsig | SIGBREAKF_CTRL_C);

		if (signal & portsig)
		{
			struct Message *msg;
			while (msg = GetMsg(async_mp))
			{
				if (msg == (struct Message *)&wait_sp)
					handle_con_wait_completed();
				else if (msg == (struct Message *)read_ior)
					handle_a314_read_completed();
			}
		}

		if (stream_closed && !pending_a314_read && !pending_con_wait)
			break;
	}

	set_screen_mode(DOSFALSE);

	FreeMem(start_msg, start_msg_len);

	CloseDevice((struct IORequest *)sync_ior);
	DeleteExtIO((struct IORequest *)read_ior);
	DeleteExtIO((struct IORequest *)sync_ior);
	DeletePort(async_mp);
	DeletePort(sync_mp);
	return 0;
}
