#include "vxi11_server.h"
#include "vxi11.h"
#include "globals.h"
#include "gpib.h"
#include "parser.h"
#include <unistd.h>
#include <stdio.h>
#include <rpc/rpc.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define DEBUG

#ifdef DEBUG
#include <arpa/inet.h>
#include <stdlib.h>
#endif

#define SIZEOFARRAY(a) (sizeof(a) / sizeof(a[0]))

#define MAXRECV 1024 // change this to change the max recv size
#define ERR_SYNTAXERROR 1
#define ERR_DEVICENOTACCESSIBLE 3
#define ERR_INVALIDLINKINDENTIFIER 4
#define ERR_PARAMETERERROR 5
#define ERR_CHANNELNOTESTABLISHED 6
#define ERR_OPERATIONNOTSUPPORTED 8
#define ERR_OUTOFRESOURCES 9
#define ERR_DEVICELOCKEDBYANOTHERLINK 11
#define ERR_NOLOCKHELDBYTHISLINK 12
#define ERR_IOTIMEOUT 15
#define ERR_IOERROR 17
#define ERR_INVALIDADDRESS 21
#define ERR_ABORT 23
#define ERR_CHANNELALREADYESTABLISHED 29

// these are to handle the RPC client that is needed to
// connect back to the client for handling interrupts
// the "interrupt channel destroy" method doesn't give
// us anything to tell which channel to destroy so
// it seems like there can only be one.
static CLIENT* intclient = NULL;
static bool intenabled = false;
static gchar* inthandler = NULL;

typedef struct {
	int lid;
} ActiveLink;

static ActiveLink* links[MAX_SESSIONS] = { NULL };

static bool isValidLink(int linkid) {
	if (linkid > MAX_SESSIONS - 1) {
#ifdef DEBUG
		printf("Crazy link id %d\n", linkid);
#endif
		return false;
	}
	return links[linkid] != NULL;
}

static bool isLocked() {
	return globals.VxiLocks.locked_network_server != NO_SERVER_LOCKED;
}

static bool haveLock(int lid) {
	return globals.VxiLocks.locked_network_server == lid;
}

#define FLAG_WAITLOCK 1

// todo
static bool waitForLock(Device_Flags flags, long timeout) {
	if (!(flags & FLAG_WAITLOCK))
		return false;
	return false;
}

static bool waitforio(long timeout) {
	do {
		if (!globals.VxiLocks.command_in_progress)
			return true;
		usleep(250);
		timeout -= 250;
	} while (timeout < 0);
#ifdef DEBUG
	printf("timeout waiting for command to finish\n");
#endif
	return false;
}

static void lock(int lid) {
	globals.VxiLocks.locked_network_server = lid;
}

static void unlock() {
	globals.VxiLocks.locked_network_server = NO_SERVER_LOCKED;
}

Device_Error *
device_abort_1_svc(Device_Link *argp, struct svc_req *rqstp) {
	static Device_Error result;
#ifdef DEBUG
	printf("device_abort_1_svc()\n");
#endif
	if (!isValidLink(*argp))
		result.error = ERR_INVALIDLINKINDENTIFIER;
	else {
		result.error = 0;
	}
	return &result;
}

Create_LinkResp *
create_link_1_svc(Create_LinkParms *argp, struct svc_req *rqstp) {
	static Create_LinkResp result;
	memset(&result, 0, sizeof(result));
#ifdef DEBUG
	printf("create_link_1_svc()\n");
#endif
	if (globals.Remote.vxi_connections == MAX_SESSIONS) {
		result.error = ERR_OUTOFRESOURCES;
	}
	else if (argp->lockDevice && isLocked() && !waitForLock(FLAG_WAITLOCK, argp->lock_timeout)) {
		result.error = ERR_DEVICELOCKEDBYANOTHERLINK;
	}
	else {
		ActiveLink* link = g_malloc(sizeof(ActiveLink));
		if (link == NULL) {
			result.error = ERR_OUTOFRESOURCES;
		}
		else {
			int linkid;
			for (linkid = 0; linkid < SIZEOFARRAY(links); linkid++) {
				if (links[linkid] == NULL) {
					links[linkid] = link;
					break;
				}
			}
			g_assert(linkid != SIZEOFARRAY(links));
			link->lid = linkid;

			globals.Remote.vxi_connections++;
#ifdef DEBUG
			printf("created link %d, %d active links\n", linkid, globals.Remote.vxi_connections);
#endif

			struct sockaddr_in sin;
			socklen_t len = sizeof(sin);
			getsockname(rqstp->rq_xprt->xp_sock, (struct sockaddr *) &sin, &len);
			result.error = 0;
			result.abortPort = ntohs(sin.sin_port);
			result.maxRecvSize = MAXRECV;
			result.lid = linkid;
		}
	}
	return &result;
}

Device_WriteResp *
device_write_1_svc(Device_WriteParms *argp, struct svc_req *rqstp) {
	static Device_WriteResp result;
	memset(&result, 0, sizeof(result));
#ifdef DEBUG
	printf("device_write_1_svc()\n");
#endif
	if (!isValidLink(argp->lid))
		result.error = ERR_INVALIDLINKINDENTIFIER;
	else if (isLocked() && !haveLock(argp->lid) && !waitForLock(argp->flags, argp->lock_timeout))
		result.error = ERR_DEVICELOCKEDBYANOTHERLINK;
	else if (!waitforio(argp->io_timeout))
		result.error = ERR_IOTIMEOUT;
	else {
#ifdef DEBUG
		printf("got %s on link %d\n", argp->data.data_val, (int) argp->lid);
#endif
		Parser_main(argp->data.data_val, 0, GPIB_and_VXI_start_query_response, NULL);
		result.size = argp->data.data_len;
	}

	return &result;
}

Device_ReadResp *
device_read_1_svc(Device_ReadParms *argp, struct svc_req *rqstp) {
	static Device_ReadResp result;
#ifdef DEBUG
	printf("device_read_1_svc()\n");
#endif

	// this free's the data from the last call
	if (result.data.data_val != NULL)
		g_free(result.data.data_val);

	memset(&result, 0, sizeof(result));

	if (!isValidLink(argp->lid))
		result.error = ERR_INVALIDLINKINDENTIFIER;
	else if (isLocked() && !haveLock(argp->lid) && !waitForLock(argp->flags, argp->lock_timeout))
		result.error = ERR_DEVICELOCKEDBYANOTHERLINK;
	else if (!waitforio(argp->io_timeout)) {
		result.error = ERR_IOTIMEOUT;
	}
	else {
		if (globals.Registers.pending_output_message != NULL) {
			result.data.data_len = strlen(globals.Registers.pending_output_message) + 1;
			result.data.data_val = globals.Registers.pending_output_message;
			globals.Registers.pending_output_message = NULL;
#ifdef DEBUG
			printf("sending ---%s---\n", result.data.data_val);
#endif
		}
		else
			result.data.data_val = NULL;

		result.reason = 0x4;
	}
	return &result;
}

Device_ReadStbResp *
device_readstb_1_svc(Device_GenericParms *argp, struct svc_req *rqstp) {
	static Device_ReadStbResp result;
	memset(&result, 0, sizeof(result));
#ifdef DEBUG
	printf("device_readstb_1_svc()\n");
#endif
	if (!isValidLink(argp->lid))
		result.error = ERR_INVALIDLINKINDENTIFIER;
	else if (isLocked() && !haveLock(argp->lid) && !waitForLock(argp->flags, argp->lock_timeout))
		result.error = ERR_DEVICELOCKEDBYANOTHERLINK;
	else
		result.stb = GPIB_and_VXI_get_STB();

	return &result;
}

Device_Error *
device_trigger_1_svc(Device_GenericParms *argp, struct svc_req *rqstp) {
	static Device_Error result;
#ifdef DEBUG
	printf("device_trigger_1_svc()\n");
#endif
	if (!isValidLink(argp->lid))
		result.error = ERR_INVALIDLINKINDENTIFIER;
	else if (isLocked() && !haveLock(argp->lid) && !waitForLock(argp->flags, argp->lock_timeout))
		result.error = ERR_DEVICELOCKEDBYANOTHERLINK;
	else {
		result.error = 0;
	}
	return &result;
}

Device_Error *
device_clear_1_svc(Device_GenericParms *argp, struct svc_req *rqstp) {
	static Device_Error result;
#ifdef DEBUG
	printf("device_clear_1_svc()\n");
#endif
	if (!isValidLink(argp->lid))
		result.error = ERR_INVALIDLINKINDENTIFIER;
	else if (isLocked() && !haveLock(argp->lid) && !waitForLock(argp->flags, argp->lock_timeout))
		result.error = ERR_DEVICELOCKEDBYANOTHERLINK;
	else {
		result.error = 0;
	}
	return &result;
}

Device_Error *
device_remote_1_svc(Device_GenericParms *argp, struct svc_req *rqstp) {
	static Device_Error result;
#ifdef DEBUG
	printf("device_remote_1_svc()\n");
#endif
	if (!isValidLink(argp->lid))
		result.error = ERR_INVALIDLINKINDENTIFIER;
	else if (isLocked() && !haveLock(argp->lid) && !waitForLock(argp->flags, argp->lock_timeout))
		result.error = ERR_DEVICELOCKEDBYANOTHERLINK;
	else {
		if (isLocked() && !haveLock(argp->lid))
			result.error = ERR_DEVICELOCKEDBYANOTHERLINK;
		else
			result.error = 0;
	}
	return &result;
}

Device_Error *
device_local_1_svc(Device_GenericParms *argp, struct svc_req *rqstp) {
	static Device_Error result;
#ifdef DEBUG
	printf("device_local_1_svc()\n");
#endif
	if (!isValidLink(argp->lid))
		result.error = ERR_INVALIDLINKINDENTIFIER;
	else if (isLocked() && !haveLock(argp->lid) && !waitForLock(argp->flags, argp->lock_timeout))
		result.error = ERR_DEVICELOCKEDBYANOTHERLINK;
	else {
		if (isLocked() && !haveLock(argp->lid))
			result.error = ERR_DEVICELOCKEDBYANOTHERLINK;
		else
			result.error = 0;
	}
	return &result;
}

Device_Error *
device_lock_1_svc(Device_LockParms *argp, struct svc_req *rqstp) {
	static Device_Error result;
#ifdef DEBUG
	printf("device_lock_1_svc()\n");
#endif
	if (!isValidLink(argp->lid))
		result.error = ERR_INVALIDLINKINDENTIFIER;
	else {
		if (isLocked(argp->lid)) {
			result.error = ERR_DEVICELOCKEDBYANOTHERLINK;
			if (haveLock(argp->lid)) {
				// maybe do something here to warn about a device trying to lock multiple times?
			}
		}
		else {
			lock(argp->lid);
			result.error = 0;
		}
	}
	return &result;
}

Device_Error *
device_unlock_1_svc(Device_Link *argp, struct svc_req *rqstp) {
	static Device_Error result;
#ifdef DEBUG
	printf("device_unlock_1_svc()\n");
#endif
	if (!isValidLink(*argp))
		result.error = ERR_INVALIDLINKINDENTIFIER;
	else {
		if (!isLocked(*argp) || !haveLock(*argp))
			result.error = ERR_NOLOCKHELDBYTHISLINK;
		else {
			result.error = 0;
			unlock();
		}
	}
	return &result;
}

void vxi11_fireinterrupt() {
	if (intclient != NULL) {
		Device_SrqParms params;
		params.handle.handle_val = inthandler;
		params.handle.handle_len = strlen(inthandler);
		device_intr_srq_1(&params, intclient);
	}
}

Device_Error *
device_enable_srq_1_svc(Device_EnableSrqParms *argp, struct svc_req *rqstp) {
	static Device_Error result;
#ifdef DEBUG
	printf("device_enable_srq_1_svc()\n");
#endif
	if (!isValidLink(argp->lid))
		result.error = ERR_INVALIDLINKINDENTIFIER;
	else {
		if (inthandler != NULL) {
			g_free(inthandler);
			inthandler = NULL;
		}
		if (argp->enable) {
			if (argp->handle.handle_val != NULL) {
				inthandler = g_strdup(argp->handle.handle_val);
#ifdef DEBUG
				printf("Interrupt handle set to %s\n", inthandler);
#endif
			}
		}
		result.error = 0;
		intenabled = argp->enable;
	}
	return &result;
}

Device_DocmdResp *
device_docmd_1_svc(Device_DocmdParms *argp, struct svc_req *rqstp) {
	static Device_DocmdResp result;
#ifdef DEBUG
	printf("device_docmd_1_svc()\n");
#endif
	if (!isValidLink(argp->lid))
		result.error = ERR_INVALIDLINKINDENTIFIER;
	else if (isLocked() && !haveLock(argp->lid) && !waitForLock(argp->flags, argp->lock_timeout))
		result.error = ERR_DEVICELOCKEDBYANOTHERLINK;
	else {
		result.data_out.data_out_len = 0;
		result.data_out.data_out_val = NULL;
		result.error = ERR_OPERATIONNOTSUPPORTED;
	}
	return &result;
}

Device_Error *
destroy_link_1_svc(Device_Link *argp, struct svc_req *rqstp) {
	static Device_Error result;
#ifdef DEBUG
	printf("destroy_link_1_svc()\n");
#endif
	int lid = *argp;
	if (!isValidLink(lid))
		result.error = ERR_INVALIDLINKINDENTIFIER;
	else {

		if (haveLock(*argp)) {
#ifdef DEBUG
			printf("Disconnecting client was holding a lock, freeing it\n");
#endif
			unlock();
		}

		globals.Remote.vxi_connections--;
#ifdef DEBUG
		printf("link %d destroyed, %d active links\n", lid, globals.Remote.vxi_connections);
#endif
		result.error = 0;
		g_free(links[lid]);
		links[lid] = NULL;
	}

	return &result;
}

Device_Error *
create_intr_chan_1_svc(Device_RemoteFunc *argp, struct svc_req *rqstp) {
	static Device_Error result;
#ifdef DEBUG
	printf("create_intr_chan_1_svc()\n");
	char clientaddressstring[INET_ADDRSTRLEN];
	inet_ntop(AF_INET, &argp->hostAddr, clientaddressstring, sizeof(clientaddressstring));
	printf("Client %s is asking for an interrupt connection on port %u\n", clientaddressstring,
			(unsigned int) argp->hostPort);
#endif
	if (argp->progFamily != DEVICE_TCP || argp->progNum != DEVICE_INTR || argp->progVers != DEVICE_INTR_VERSION)
		result.error = ERR_OPERATIONNOTSUPPORTED;
	else if (intclient != NULL) {
		result.error = ERR_CHANNELALREADYESTABLISHED;
	}
	else {
		struct sockaddr_in clientaddr;
		clientaddr.sin_family = AF_INET;
		clientaddr.sin_port = htons(argp->hostPort);
		clientaddr.sin_addr.s_addr = argp->hostAddr;
		int sock = RPC_ANYSOCK;
		CLIENT* clnt = clnttcp_create(&clientaddr, DEVICE_INTR, DEVICE_INTR_VERSION, &sock, 0, 0);
		if (clnt == NULL) {
#ifdef DEBUG
			printf("Couldn't create interrupt channel client\n");
#endif
			result.error = ERR_CHANNELNOTESTABLISHED;
		}
		else {
			intclient = clnt;
			result.error = 0;
		}
	}
	return &result;
}

Device_Error *
destroy_intr_chan_1_svc(void *argp, struct svc_req *rqstp) {
	static Device_Error result;
#ifdef DEBUG
	printf("destroy_intr_chan_1_svc()\n");
#endif
	if (intclient == NULL) {
		result.error = ERR_CHANNELNOTESTABLISHED;
	}
	else {
		clnt_destroy(intclient);
		intclient = NULL;
		result.error = 0;
	}
	return &result;
}