#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;

#define LINKLONGSINCEDEADTIMEOUT ((10 * 60) * 1000000) // ten minutes
typedef struct {
	int lid;
	int sock;
	GTimeVal lastactivity;
	bool aborted;
} ActiveLink;

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

static void freelink(ActiveLink* link);

static void touchlink(int lid)
{
	g_get_current_time(&(links[lid]->lastactivity));
}

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 ;
}

// test if a link has expired, this is to avoid dead locks
static bool linkexpired(ActiveLink* link)
{
	GTimeVal cutoff;
	g_get_current_time(&cutoff);
	g_time_val_add(&cutoff, -LINKLONGSINCEDEADTIMEOUT);
	if (link->lastactivity.tv_sec < cutoff.tv_sec) {
#ifdef DEBUG
		printf("Link %d is apparently dead, removing\n", link->lid);
#endif
		freelink(link);
		return true;
	} else {
		return false;
	}
}

// locking wrappers

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

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

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

static bool isLocked()
{
	if (globals.VxiLocks.locked_network_server == NO_SERVER_LOCKED) {
		return false;
	} else {
		ActiveLink* link = links[globals.VxiLocks.locked_network_server];
		return !(linkexpired(link));
	}
}

#define FLAG_WAITLOCK 1

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

//

static void freelink(ActiveLink* link)
{
	links[link->lid] = NULL;
	globals.Remote.vxi_connections--;

	if (globals.Remote.vxi_connections == 0) {
		// cancel any remaining panel locks
		globals.Remote.vxi_panel_lock = 0;
	}

#ifdef DEBUG
	printf("link %d destroyed, %d active links\n", link->lid, globals.Remote.vxi_connections);
#endif

	if (haveLock(link->lid)) {
#ifdef DEBUG
		printf("destroyed link was holding a lock, freeing it\n");
#endif
		unlock();
	}
	g_free(link);
}

void vxi11_deadsocketcallback(int socket)
{
	int linkid;
	for (linkid = 0; linkid < SIZEOFARRAY(links); linkid++) {
		if (links[linkid] != NULL ) {
			if (links[linkid]->sock == socket) {
#ifdef DEBUG
				printf("Freeing link %d, socket died\n", linkid);
#endif
				freelink(links[linkid]);
			}
		}
	}
}

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;
}

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 {
		links[*argp]->aborted = true;
		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 ) { // clean up any dead links
					linkexpired(links[linkid]);
				}
				if (links[linkid] == NULL ) {
					links[linkid] = link;
					break;
				}
			}
			g_assert(linkid != SIZEOFARRAY(links));
			link->lid = linkid;
			link->sock = rqstp->rq_xprt->xp_sock;
			touchlink(linkid);
			globals.Remote.vxi_connections++;
#ifdef DEBUG
			printf("created link %d(socket %d), %d active links\n", linkid, link->sock, 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;

			if (argp->lockDevice) {
				lock(link->lid);
			}

		}
	}
	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 {
		touchlink(argp->lid);
		links[argp->lid]->aborted = false;
		gchar* str = NULL;
		if (argp->data.data_val != NULL ) {
			str = g_strndup(argp->data.data_val, argp->data.data_len);
		}
		if (str != NULL ) {
#ifdef DEBUG
			int n;
			for (n = 0; str[n] != '\0'; n++) {
				printf("%02x ", (unsigned char) str[n]);
			}
			printf("\n");
			printf("got <<%s>> (reported length %d)on link %d.\n", str, (int) argp->data.data_len, (int) argp->lid);
#endif
			Parser_main(str, 0, GPIB_and_VXI_start_query_response, NULL );
			g_free(str);
		}
		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 {
		touchlink(argp->lid);
		if (globals.Registers.pending_output_message != NULL ) {
			result.data.data_len = strlen(globals.Registers.pending_output_message);
			result.data.data_val = globals.Registers.pending_output_message;
			globals.Registers.pending_output_message = NULL;
			GPIB_and_VXI_clear_MAV();
#ifdef DEBUG
			printf("sending ---%s---\n", result.data.data_val);
#endif
		} else {
			result.data.data_val = NULL;
		}

		if (links[argp->lid]->aborted) {
			result.error = ERR_ABORT;
			links[argp->lid]->aborted = false;
		}

		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 {
		touchlink(argp->lid);
		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 {
		touchlink(argp->lid);
		result.error = ERR_OPERATIONNOTSUPPORTED;
	}
	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 {
		touchlink(argp->lid);
		result.error = 0;
		GPIB_and_VXI_device_clear();
	}
	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 {
			touchlink(argp->lid);
			result.error = 0;
			globals.Remote.vxi_panel_lock = 1;
		}
	}
	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 {
			touchlink(argp->lid);
			result.error = 0;
			globals.Remote.vxi_panel_lock = 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 {
			touchlink(argp->lid);
			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 {
			touchlink(*argp);
			result.error = 0;
			unlock();
		}
	}
	return &result;
}

void vxi11_fireinterrupt()
{
#ifdef DEBUG
	printf("vxi11_fireinterrupt()\n");
#endif
	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 {
		touchlink(argp->lid);
		if (inthandler != NULL ) {
			g_free(inthandler);
			inthandler = NULL;
		}
		if (argp->enable) {
			if (argp->handle.handle_val != NULL ) {
				inthandler = g_strndup(argp->handle.handle_val, argp->handle.handle_len);
#ifdef DEBUG
				printf("Interrupt handle set to %s\n", inthandler);
#endif
			}
			//vxi11_fireinterrupt();
		}
#ifdef DEBUG
		else {
			printf("Interrupts disabled\n");
		}
#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;
	memset(&result, 0, sizeof(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 {
		touchlink(argp->lid);
		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 {
		freelink(links[lid]);
		result.error = 0;
	}

	return &result;
}

Device_Error *
create_intr_chan_1_svc(Device_RemoteFunc *argp, struct svc_req *rqstp)
{
	static Device_Error result;

	in_port_t port = htons(argp->hostPort);
	uint32_t addr = htonl(argp->hostAddr);

#ifdef DEBUG
	printf("create_intr_chan_1_svc()\n");
	char clientaddressstring[INET_ADDRSTRLEN];
	inet_ntop(AF_INET, &addr, clientaddressstring, sizeof(clientaddressstring));
	printf("Client %s is asking for an interrupt connection on port %u\n", clientaddressstring, (unsigned int) port);
#endif

// if a client had an interrupt channel and died without closing it
// we need to clean it up to avoid a deadlock
	static struct rpc_err err;
	if (intclient != NULL ) {
		vxi11_fireinterrupt();
		clnt_geterr(intclient, &err);
		if (err.re_errno != 0) {
#ifdef DEBUG
			printf("killing stale client\n");
#endif
			clnt_destroy(intclient);
			intclient = NULL;
		}
	}

	if (argp->progFamily != DEVICE_TCP || argp->progNum != DEVICE_INTR || argp->progVers != DEVICE_INTR_VERSION) {
		result.error = ERR_OPERATIONNOTSUPPORTED;
	} else if (intclient != NULL ) {
#ifdef DEBUG
		printf("Not creating interrupt channel, already exists\n");
#endif
		result.error = ERR_CHANNELALREADYESTABLISHED;
	} else {
		struct sockaddr_in clientaddr;
		clientaddr.sin_family = AF_INET;
		clientaddr.sin_port = port;
		clientaddr.sin_addr.s_addr = addr;
		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;
#ifdef DEBUG
			printf("Client created\n");
#endif
		}
	}
	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;
}