#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include "watch.h"
#include "lock.h"
#include "file.h"
#include "opt.h"
#include "ts.h"
#include "dioctl.h"
#include "dioname.h"
#include "netdio.h"
#include "dioctl_api.h"

#define Assert(expr,str,...) if (!(expr)) { fprintf(stderr,"assertion failed in file %s, function %s, line %d:\n" str "\n",__FILE__,__FUNCTION__,__LINE__,__VA_ARGS__); exit(3); }

#define Type Map
#define MapNull {0,0}
#include "array2.h"
#undef Type

int MapCompareKey(Map a,Map b) {
  return strcmp(a.key,b.key);
}

//==================================================================
// CLIENT FUNCTIONS

#define TxByte(val) fputc(val,f)
#define RxByte(ch) { fflush(f); int ch2 = fgetc(f); if (ch2 < 0) return FAILURE; ch=ch2; }
#define RxData(buf,len,dest) { fflush(f); if (fread(buf,len,1,f) != 1) { dest; return FAILURE; } }

// netDIOLockBlocking
// netDIOLockRelease
// netDIOLockNonBlocking
#define FAILURE 0
struct diocaps *netDIOCapabilitiesGet(FILE *f) {
  int numdios;
  struct diocaps *caps;

  TxByte(0); // Get DIO Capabilities
  RxByte(numdios);
  caps = malloc(1+numdios);
  caps->numdios = numdios;
  RxData(&caps->caps,numdios,free(caps));
  return caps;
}

#undef FAILURE
void netDIOSet(FILE *f,int num,int asOutput,int drive) {
  TxByte(asOutput ? (drive ? 2 : 3) : 1);  // Set Single DIO State
  TxByte(num);
  fflush(f);
}

void netDIOTimeArm(FILE *f) {
  TxByte(30);
}

// return:
// 0 - DIO is an input, reads LOW
// 1 - DIO is an input, reads HIGH
// 2 - DIO is an output, reads LOW
// 3 - DIO is an output, reads HIGH
#define FAILURE -1
int netDIOGet(FILE *f,int num) {
  int ch;

  TxByte(4); // Get Single DIO State
  TxByte(num);
  RxByte(ch);
  return ch >> 6;
}

int netBoardModelGet(FILE *f) {
  int ret;
  unsigned char byte[4];
	
  TxByte(22); // Get Model Id
  RxByte(byte[0]);
  RxByte(byte[1]);
  RxByte(byte[2]);
  RxByte(byte[3]);
  ret = (((int)byte[0]) << 24) + (((int)byte[1]) << 16) + (((int)byte[2]) << 8) + ((int)byte[3]);
  return ret;
}

int netDIONameLookup(FILE *f,char *name) {
  int i=0,val,len;
	
  len = strlen(name);
  if (len > 255) return -1;
  TxByte(23); // look up logical DIO name
  TxByte(len);
  while (name[i]) {
    TxByte(name[i++]);
  }
  RxByte(i);
  RxByte(val);
  if (i == 0) {
    return 0x7FFFFFFF;
  } else {
    return val;
  }
}

#undef FAILURE
#define FAILURE 0
char *netDIOLookup(FILE *f,char *name,int value) {
  int i=0,val,len;
  char *result;

  len = strlen(name);
  if (len > 255) return 0;
  TxByte(27); // look up logical DIO name
  TxByte(len);
  while (name[i]) {
    TxByte(name[i++]);
  }
  TxByte(value);
  RxByte(len);
  result = malloc(len+1);
  for (i=0;i<len;i++) {
    RxByte(result[i]);
  }
  result[i] = 0;
  return result;
}

#undef FAILURE
void netDIOGroupDefine(FILE *f,int groupNum,int numDios,int *dios) {
  int i;

  if (groupNum < 0 || groupNum > 15) return;
  if (numDios < 0 || numDios > 16) return;

  TxByte(5); // Define DIO Group Mapping
  TxByte((groupNum << 4) + (numDios-1));
  for (i=0;i<numDios;i++) {
    TxByte(dios[i]);
  }
}

void netDIOGroupSetAs(FILE *f,int groupNum,int asOutput,int drive) {
  TxByte(asOutput ? (drive ? 7 : 8) : 6);
  TxByte(groupNum << 4);
  fflush(f);
}

void netDIOGroupSet(FILE *f,int groupNum,int numDios,int *dir,int *val) {
  int i;
  char byte=0;

  TxByte(9); // Set DIO Group States
  TxByte(groupNum << 4);
  for (i=0;i<numDios;i++) {
    byte |= ((dir[i]?1:0) << (7-2*(i%4)));
    byte |= ((val[i]?1:0) << (6-2*(i%4)));
    if (i % 4 == 3 || i+1 == numDios) {
      TxByte(byte);
      byte = 0;
    }
  }
  fflush(f);
}

// Idea: 
// for the callback we could pass a closure. create the closure by
// defining a function inside the function, let it declare a static
// (or malloced on demand?) variable, then pass certain values to
// initialize and clear it, and dump out its contents. kinda like
// a mini-server.
#define FAILURE -1
int netDIOGroupGet(FILE *f,int num,void (*callback)(int,int)) {
  int i,numdios,len,byte;

  TxByte(10); // Get DIO Group States
  TxByte(num << 4);
  RxByte(numdios);
  len = 1 + (numdios / 4) + ((numdios % 4) ? 1 : 0);
  for (i=0;i<numdios;i++) {
    if (i % 4 == 0) {
      RxByte(byte);
    }
    callback(i,byte >> 6);
    byte = (byte << 2) & 0xFF;
  }
  return 0;
}

#undef FAILURE
void netDIOGroupLockBlocking(FILE *f,int groupNum) {
  TxByte(11);
  TxByte(groupNum << 4);
  fflush(f);
}

void netDIOGroupLockRelease(FILE *f,int groupNum) {
  TxByte(12); // Release DIO Group Lock
  TxByte(groupNum << 4);
  fflush(f);
}

#define FAILURE -1
int netDIOGroupLockNonBlocking(FILE *f,int groupNum) {
  int result;

  TxByte(13); // Try to Aquire DIO Group Lock (Non-Blocking)
  TxByte(groupNum << 4);
  RxByte(result);
  return result == 0x77;
}

#undef FAILURE
#define FAILURE 0

/*
int netDIOConnectorList(FILE *f,int *map) {
	int i;
	
	TxByte(27); //enumerate all logical DIO names by connector
	for (i=0;i<200;i++) {
		RxByte(map[i]);
	}
	return 1;
}
*/

int netDIONamesList(FILE *f,Map **dptr) {
        unsigned char b[2];
	char *byte;
	unsigned short count;
	int i,j,len;
	Map entry;
	
	TxByte(26); // enumerate all logical DIO names
	RxByte(b[0]);
	RxByte(b[1]);
	count = (256 * (int)b[0] + b[1]);

	for (i=0;i<count;i++) {
		RxByte(entry.val);
		RxByte(len);
		entry.key = malloc(2+len);
		for (j=0;j<len;j++) {
			RxByte(entry.key[j]);
		}
		entry.key[j] = 0;
		MapQueue(dptr,entry);
	}
	return 1;	
}

#undef FAILURE
#define FAILURE -1

int netDIOGroupWait(FILE *f,int groupNum,int val,int mask,int min,int max) {
	unsigned char low,high;
	
	TxByte(114);
	TxByte(groupNum << 4);
	TxByte(val >> 8);
	TxByte(val & 0xFF);
	TxByte(mask >> 8);
	TxByte(mask & 0xFF);
	TxByte(min >> 8);
	TxByte(min & 0xFF);
	TxByte(max >> 8);
	TxByte(max & 0xFF);
	RxByte(low);
	RxByte(high);
	return 256*(int)low + high;
}

#undef FAILURE
#define FAILURE

// callback(int groupIndex,int value)
void netDIOGroupWaitOld(FILE *f,int groupNum,int numdios,int *val,void (*callback)(int,int)) {
  int i,n,byte;
  TxByte(14); // block until any DIO in Group is value specified for it
  TxByte(groupNum << 4);
  for (i=0;i<numdios;i++) {
    byte |= ((val[i]?1:0) << 7-(i%8));
    if (i%8 == 7 || i+1==numdios) TxByte(byte);
  }
  RxByte(n);
  for (i=0;i<n;i++) {
    RxByte(byte);
    callback(byte,val[byte]);
  }
}

#undef FAILURE
#define FAILURE -1

int netDIOWait(FILE *f,int dionum,int isHigh) {
  int n;
	
  if (isHigh) {
	TxByte(16); // BlockUntilDIOHigh
  } else {
	TxByte(15); // BlockUntilDIOLow
  }
  TxByte(dionum);
  RxByte(n);
  return (n!=dionum) ?  -1 : 0;
}

#undef FAILURE
#define FAILURE

void netDIONameAssign(FILE *f,int dionum,char *name) {
  int i=0,len;
	
  len = strlen(name);
  if (len > 255) return;
  TxByte(24); // assign logical DIO name
  TxByte(dionum);
  TxByte(len);
  while (name[i]) {
    TxByte(name[i++]);
  }
  fflush(f); // since change is global
}

void netDIONameRemove(FILE *f,char *name) {
  int i=0,len;
	
  len = strlen(name);
  if (len > 255) return;
  TxByte(25); // assign logical DIO name
  TxByte(len);
  while (name[i]) {
    TxByte(name[i++]);
  }
  fflush(f); // since change is global
}

#undef FAILURE
#define FAILURE -1
int netDIOTimeEventGet(FILE *f,struct timeval *tv) {
  unsigned b[4];
	
  TxByte(28);
  RxByte(b[0]);
  RxByte(b[1]);
  RxByte(b[2]);
  RxByte(b[3]);
  tv->tv_sec = (b[0] << 24) + (b[1] << 16) + (b[2] << 8) + b[3];
  RxByte(b[0]);
  RxByte(b[1]);
  RxByte(b[2]);
  RxByte(b[3]);
  tv->tv_usec = (b[0] << 24) + (b[1] << 16) + (b[2] << 8) + b[3];
  return 1;
}

int netDIOTimeCurrentGet(FILE *f,struct timeval *tv) {
  unsigned b[4];
	
  TxByte(29);
  RxByte(b[0]);
  RxByte(b[1]);
  RxByte(b[2]);
  RxByte(b[3]);
  tv->tv_sec = (b[0] << 24) + (b[1] << 16) + (b[2] << 8) + b[3];
  RxByte(b[0]);
  RxByte(b[1]);
  RxByte(b[2]);
  RxByte(b[3]);
  tv->tv_usec = (b[0] << 24) + (b[1] << 16) + (b[2] << 8) + b[3];
  return 1;
}


// must call this first:
//   FILE *f = 
// must call this as needed. auto-called before Rx
//   fflush(f); // optional

/*
./dioctl -p 4500 -g
./dioctl -p 4500 -r 0 -r 4 -r 8 # expect 0 1 0 1 0 0
./dioctl -p 4500 -0 4 -r 0 -r 4 # expect 0 0 1 0
./dioctl -p 4500 -1 4 -r 0 -r 4 # expect 0 1 1 1
./dioctl -p 4500 -G 1:4:5:8 -1 1001 -r 4 -r 5 -r 8 # expect 1 1 1 1 1 1
./dioctl -p 4500 -G 1:4:5:8 -r 1001 # expect 1 1 1 1 1 1

# client 1
./dioctl -p 4500 -G 1:4:5:8 -w 1:0:0:1
# client 2
./dioctl -p 4500 -0 4
*/

/*
  --test notes:
  1. Need to run the following to prevent the test from throwing the reset switch
  and rebooting the board:
  killall ts7500ctl;ts7500ctl --setrng --getrtc --resetswitchoff --autofeed 2
  2. This test is very susceptible to failure if any of the DIOs are in use
  for other things, such as xuarts or jumpers on the board being changed.
  3. Two failures are expected, one on the CAN_RX line and one on the 485 Rx.

 */
#ifndef USERMAIN
#endif

/*
DIOCTL protocol
  The protocol for talking to the dioctl server is documented below.
  All data sent to the server is prefixed with "->"
  All data returned to the client is prefixed with "<-"
  After the prefix comes the data type and the name of the data.
  When the value of data determines what comes next, that is denoted
  by indention to a new level for each permissible value.
  Comments start with a semi-colon and go to the end of the line.

  Example: In the protocol spec below, the server expects a request
  to start with an 8-bit unsigned integer containing the command ("CMD").
  If that value is equal to 0, it is interpreted as a "Get DIO 
  Capabilities" command, and the server returns an 8-bit unsigned
  value containing the total number of DIOs on the board, followed by
  an 8-bit unsigned value for each DIO containing it's capabilities.

  The commands that change DIOs (direction or state) are:
    CMD = 1,2,3,6,7,8, or 9
  These commands will block if any of the DIOs specified are locked
  by another client.  When the lock is released, the DIO operation
  will be performed.   Multiple clients can be waiting on the same
  DIO(s) and will get access in the order that complete requests are 
  received.

  Any locks held by a client will be automatically released when the
  client connection is closed.
*/
/*
  Simple DIO API (written on top of the more complex dioctl API)
*/


dio_connection **dio_conns;
int max_dio_conn;

static dio_connection *dio_handle_to_ptr(int handle) {
	if (handle < 0 || handle >= max_dio_conn) return 0;
	return dio_conns[handle];
}

// create a new handle from the given pointer
static int dio_ptr_to_handle(dio_connection *conn) {
	int i;
	
	// first see if we have an existing close slot we can use
	for (i=0;i<max_dio_conn;i++) {
		if (dio_conns[i] == 0) {
			dio_conns[i] = conn;
			return i;
		}
	}
	// nope, extend the slot list by 1 and create new entry
	// now i == max_dio_conn
	dio_conns = realloc(dio_conns,sizeof(dio_connection *)*(i+1));
	dio_conns[max_dio_conn++] = conn;
	return i;
}

// we assume that we never create more than one handle for a pointer!
static void free_dio_handle(int handle) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return;

	if (conn->conn) fclose(conn->conn);
	free(dio_conns[handle]);
	dio_conns[handle] = 0;
}

// dio_open()
//     returns an integer representing the connection 
//     and state to a dioctl --server.  
//     On failure, returns -1

int dio_fdopen(int fd) {
	int i;
	dio_connection *conn;
	
	conn = malloc(sizeof(dio_connection));
	conn->conn = fdopen(fd,"a+");
	conn->dioNameCache = 0;
	//MapInit0(&conn->dioNameCache);
	conn->nameIterator = 0;
	netDIONamesList(conn->conn,&conn->dioNameCache);
	conn->lastEvent = 0;
	conn->groupsInUse = 0;
	for (i=0;i<256;i++) {
		conn->pinInGroup[i] = 0;
		conn->lastPinRead[i] = -1;
		conn->nextPinWrite[i] = -1;
	}
	return dio_ptr_to_handle(conn);
}

int dio_open(const char *host) {
	int i;
	
	opt_cli_defaultport = 4500;
	if (!opt_cli((char *)host,&i,0)) {
		return -1;
	}
	return dio_fdopen(i);
}

// use NULL for localhost
// specify IP address or name of server to use default port
// use "host:port" format to specify address/or and port
// always returns -1 because the handle is guaranteed to be invalid on return
int dio_close(int handle) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;
	free_dio_handle(handle);
	return -1;
}

#define CHECK_FAIL(ret) if (ret == -1) { dio_close(handle); return -1; }
#define EVENT_PROLOG if (conn->lastEvent && conn->lastEvent->tv_sec ==0 && conn->lastEvent->tv_usec ==0) netDIOTimeArm(conn->conn)
#define EVENT_EPILOG if (conn->lastEvent && conn->lastEvent->tv_sec ==0 && conn->lastEvent->tv_usec ==0) CHECK_FAIL(dio_gettimestamp(handle,conn->lastEvent))

// returns server timestamp of last waitpin() wakeup
int dio_gettimestamp(int handle, struct timeval *tv) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;
	int ret = netDIOTimeEventGet(conn->conn,tv) ? 0 : -1;
	CHECK_FAIL(ret);
	return 0;
}

// get the current time on the server
int dio_getcurrenttime(int handle,struct timeval *tv) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;
	int ret = netDIOTimeCurrentGet(conn->conn,tv);
	CHECK_FAIL(ret);
	return 0;
}

int dio_configfile(int handle,char *filename) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;
       	DIOConfigFileProcess(&conn->dioNameCache,filename);
	return -1;
}

/*
All functions can return -1 meaning socket failure.
The handle is invalidated and any further attempts to use it fail.
	
*/
// 
// On socket/proto failure, API function should also close socket
// so all the user has to do again is rerun dio_open().  
// It is not a failure if attempting to set an output value
// to an input-only pin.

// val is 0, 1, or DIO_HIGHZ
// pins outside the range of 0-255 are ignored
int dio_setpin(int handle, int pin, int val) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;

	if (pin < 0 || pin > 255) return 0;
	if (conn->pinInGroup[pin]) {
		conn->nextPinWrite[pin] = val;
	} else {
		EVENT_PROLOG;
		netDIOSet(conn->conn,pin,val==DIO_HIGHZ?0:1,val & 1);
		EVENT_EPILOG;
	}
	return 0;
}

// returns 0 if reads low, 1 if reads high
// pins outside the range of 0-255 are ignored
int dio_getpin(int handle, int pin) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;

	if (pin < 0 || pin > 255) return 0;
	if (conn->pinInGroup[pin]) {
		return 1 & conn->lastPinRead[pin];
	} else {
		EVENT_PROLOG;
		int ret = netDIOGet(conn->conn,pin);
		CHECK_FAIL(ret);
		CHECK_FAIL(ret);
		EVENT_EPILOG;
		return 1 & ret;
	}
}

// return 1 if pin is an output, otherwise 0
int dio_getpinstate(int handle, int pin) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;

	if (pin < 0 || pin > 255) return 0;
	if (conn->pinInGroup[pin]) {
		return 1 & conn->lastPinRead[pin];
	} else {
		EVENT_PROLOG;
		int ret = netDIOGet(conn->conn,pin);
		CHECK_FAIL(ret);
		CHECK_FAIL(ret);
		EVENT_EPILOG;
		return 2 & ret;
	}
}

// returns pin output state in bit 1 and read state in bit 0
int dio_getpinfullstate(int handle, int pin) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;

	if (pin < 0 || pin > 255) return 0;
	if (conn->pinInGroup[pin]) {
		return 1 & conn->lastPinRead[pin];
	} else {
		EVENT_PROLOG;
		int ret = netDIOGet(conn->conn,pin);
		CHECK_FAIL(ret);
		CHECK_FAIL(ret);
		EVENT_EPILOG;
		return ret;
	}
}


// blocks execution until pin value becomes val
// pins outside the range of 0-255 are ignored (return immediately)
int dio_waitpin(int handle, int pin, int val) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;

	if (pin < 0 || pin > 255) return 0;
	EVENT_PROLOG;
	CHECK_FAIL(netDIOWait(conn->conn,pin,val));
	EVENT_EPILOG;
	return 0;
}

// registers a timeval to be filled in by the
// server at the completion of the next DIO op.  It
// DOES NOT fill in *tv immediately on return!
// This function must be called before every time we want a new timestamp.
int dio_timestamp(int handle, struct timeval *tv) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;
	conn->lastEvent = tv;
	conn->lastEvent->tv_sec = 0;
	conn->lastEvent->tv_usec = 0;
	return 1;

}

int dio_lookupCached(int handle, const char *name) {
	int pin;
	Map *got,val;
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;
	EVENT_PROLOG;
	val.key = (char *)name;
	got = MapFindBinary(conn->dioNameCache,val,MapCompareKey);
	if (got) {
		return got->val;
	}
	pin = netDIONameLookup(conn->conn,(char *)name);
	CHECK_FAIL(pin);
	EVENT_EPILOG;
	if (pin != 0x7FFFFFFF) {
		val.val = pin;
		MapInsertSorted(&conn->dioNameCache,val,MapCompareKey);
	}
	return pin;
}

// returns MAX_INT if lookup fails
int dio_lookup(int handle, const char *name) {
	int pin;
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;
	EVENT_PROLOG;
	pin = netDIONameLookup(conn->conn,(char *)name);
	CHECK_FAIL(pin);
	if (pin != 0x7FFFFFFF) {
		Map val;
		val.key = strdup((char *)name);
		val.val = pin;
		MapReplaceSorted(&conn->dioNameCache,val,MapCompareKey);
	}
	EVENT_EPILOG;
	return pin;
}

// pin is looked up by name 
int dio_set(int handle, const char *pin, int val) {
	return dio_setpin(handle,dio_lookupCached(handle,pin),val);	
}

int dio_get(int handle, const char *pin) {
	return dio_getpin(handle,dio_lookupCached(handle,pin));
}

int dio_wait(int handle, const char *pin, int val) {
	return dio_waitpin(handle,dio_lookupCached(handle,pin),val);
}

/* 
	On dio_set|get|wait() there are name->pin lookups done implicitly.
	The API should be smart and implement a simple handle specific
	name->pin LRU replacement lookup cache of 8 entries.  This cache
	should be destroyed when theres a socket/protocol error and
	reinitialized on dio_open().  The 8 entries are searched sequentially
	(e.g. no fancy hashing required) before doing a network name->num
	lookup. 
 */ 

// Returns an empty DIO group ID (in the range of 0 to 15) for handle
// returns MAX_INT if no more groups are available to be used, or if invalid
// handle is provided
int dio_groupinit(int handle) {
	int i;
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;
	
	for (i=0;i<16;i++) {
		if (i == conn->groupsInUse) {
			conn->pinsInGroup[conn->groupsInUse] = -1;
			conn->groupsInUse++;
			return i;
		}
		if (conn->pinsInGroup[i] == 0) {
			conn->pinsInGroup[i] = -1;
			return i;
		}
	}
	return 0x7FFFFFFF;
}

// Adds pin to group
// returns -1 if handle is bad
// returns 1 if succeeded, 0 if bad parameters passed or no more pins can be added
int dio_groupaddpin(int handle, int group,int pin) {
	int i;
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;

	if (pin < 0 || pin > 255) return 0;
	if (group < 0 || group > 15) return 0;
	if (group >= conn->groupsInUse) return 0;
	if (conn->pinsInGroup[group] >= 0) return 0;
	if (conn->pinsInGroup[group] < -16) return 0;
	conn->pinInGroup[pin]++; // do now or wait until finalization?
	i = -conn->pinsInGroup[group] - 1;
	conn->groupPins[group][i] = pin;
	conn->pinsInGroup[group]--;
	return 1;
}

// same as above, except does name lookup
int dio_groupadd(int handle, int group, const char *pin) {
	return dio_groupaddpin(handle,group,dio_lookupCached(handle,pin));
}

void dio_group_finalize(dio_connection *conn,int group) {
	conn->pinsInGroup[group] = -conn->pinsInGroup[group] - 1;
	netDIOGroupDefine(conn->conn,group,
				conn->pinsInGroup[group],
				conn->groupPins[group]);
}

// Acquires blocking lock for group pins
int dio_grouplock(int handle, int group) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;

	if (group < 0 || group > 15) return 0;
	if (group >= conn->groupsInUse) return 0;
	if (conn->pinsInGroup[group] == 0) return 0;
	if (conn->pinsInGroup[group] < 0) {
		dio_group_finalize(conn,group);
	}
	EVENT_PROLOG;
	netDIOGroupLockBlocking(conn->conn,group);
	EVENT_EPILOG;
	return 1;
}

// Doesn't block
// returns 1 on success, 0 if a parameter was bad
int dio_grouptrylock(int handle, int group) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	int ret;
	if (!conn) return -1;

	if (group < 0 || group > 15) return 0;
	if (group >= conn->groupsInUse) return 0;
	if (conn->pinsInGroup[group] == 0) return 0;
	if (conn->pinsInGroup[group] < 0) {
		dio_group_finalize(conn,group);
	}
	
	EVENT_PROLOG;
	ret = netDIOGroupLockNonBlocking(conn->conn,group);
	CHECK_FAIL(ret);
	EVENT_EPILOG;
	return ret;
}

// returns 1 on success, 0 if a parameter was bad
int dio_groupunlock(int handle, int group) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;

	if (group < 0 || group > 15) return 0;
	if (group >= conn->groupsInUse) return 0;
	if (conn->pinsInGroup[group] <= 0) return 0;
	EVENT_PROLOG;
	netDIOGroupLockRelease(conn->conn,group);
	EVENT_EPILOG;
	return 1;
}

 // only update cached input state
int dio_groupisync(int handle, int group) {
	int pin;
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;
	if (group < 0 || group >= conn->groupsInUse) return 0;

	void syncDIOvalue(int index,int value) {
		pin = conn->groupPins[group][index];
		conn->lastPinRead[pin] = value;
	}
	if (conn->pinsInGroup[group] < 0) {
		dio_group_finalize(conn,group);
	}
	EVENT_PROLOG;
	// read back changes
	CHECK_FAIL(netDIOGroupGet(conn->conn,group,syncDIOvalue));
	EVENT_EPILOG;
	return 1;
}

// only update outputs
int dio_grouposync(int handle, int group) {
	int i,dir[16],val[16],pin;
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (group < 0 || group >= conn->groupsInUse) return 0;
	if (!conn) return -1;
	if (conn->pinsInGroup[group] < 0) {
		dio_group_finalize(conn,group);
	}
	// modify with updated values
	for (i=0;i<conn->pinsInGroup[group];i++) {
		pin = conn->groupPins[group][i];
		if (conn->nextPinWrite[pin] != -1) {
			dir[i] = conn->nextPinWrite[pin] == DIO_HIGHZ ? 0 : 1;
			val[i] = conn->nextPinWrite[pin] & 1;
		} else {
			dir[i] = conn->lastPinRead[pin] & 2;
			val[i] = conn->lastPinRead[pin] & 1;
		}
	}
	EVENT_PROLOG;
	netDIOGroupSet(conn->conn,group,conn->pinsInGroup[group],dir,val);
	EVENT_EPILOG;
	return 1;
}

/* 
	If a dio pin is part of a group, special handling is done in
	dio_set/dio_get().  When a pin in a group is updated, dio_set 
	returns immediately without any protocol req/resp.  It is simply
	marked for update for when dio_groupsync() is next called.  
	Similarly, dio_get() returns the value of the pin from the last
	dio_groupsync() call.  Unless dio_set() is called on a group pin,
	dio_groupsync() only reads group pin states.  If dio_set() has been
	called to update output values, dio_groupsync() first latches read
	data and follows with the actual pin output state updates.
 */
int dio_groupsync(int handle, int groupid) {
	CHECK_FAIL(dio_groupisync(handle,groupid));
	CHECK_FAIL(dio_grouposync(handle,groupid));
	return 1;
}

/*
	The server maintains a DNS like lookup table of string names to pin
	numbers.  Some of these names/pin mappings are board specific and
	should be treated as read-only.  Other names may be changed or added 
	to the server later with dio_namepin(). There may be several names for 
	a given pin, e.g. DIO 7 could be "green_led", "cn2_34", "pa12", or 
	"laser_trigger"
 */
int dio_namepin(int handle, int pin, const char *name) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	Map val;
	if (!conn) return -1;
	netDIONameRemove(conn->conn,(char *)name);
	EVENT_PROLOG;
	netDIONameAssign(conn->conn,pin,(char *)name);
	EVENT_EPILOG;
	val.key = strdup((char *)name);
	val.val = pin;
	MapReplaceSorted(&conn->dioNameCache,val,MapCompareKey);
	return 1;
}

// Does a dio_lookup() of "attrib.<pin#>.<attribname>" and returns value.
// returns MAX_INT if value not found
int dio_getattrib(int handle, int pin, const char *attrib) {
	int len = strlen(attrib); {
		char name[20+len];
	
		sprintf(name,"attrib.%d.%s",pin,attrib);
		int ret = dio_lookup(handle, name);
		CHECK_FAIL(ret);
		return ret;
	}
}

// sets "attrib.<pin#>.<attribname>=val"
int dio_setattrib(int handle, int pin, const char *attrib, int val) {
	int len = strlen(attrib); {
		char name[20+len];
	
		sprintf(name,"attrib.%d.%s",pin,attrib);
		CHECK_FAIL(dio_namepin(handle, val, name));
		return 1;
	}
}

// Iterates through all names/vals, including attribs.  
// Maintains iterator state and at EOF, *name is set to NULL.  
// If dio_names is then called again, restarts at beginning.
// make iterator specific to handle
// caller should NOT modify the contents of *name !!!
// returns -1 at EOF, otherwise a non-negative int
int dio_names(int handle, char **name, int *val) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	Map *next;
	
	if (!conn) return -1;
	conn->nameIterator = MapNext(conn->dioNameCache,conn->nameIterator);
	if (conn->nameIterator) {
		*name = conn->nameIterator->key;
		*val = conn->nameIterator->val;
		return conn->nameIterator - conn->dioNameCache;
	} else {
		*name = 0;
		return -1;
	}
}

static void groupwait_isync(dio_connection *conn,int group,int value) {
	int i,pin;
	
	
	for (i=0;i<conn->pinsInGroup[group];i++) {
		pin = conn->groupPins[group][i];
		conn->lastPinRead[pin] = (value >> i) & 1;
	}
}

// all the group wait functions should take the return value from 
// netDIOGroupWait, which contains the value of all bits in the group,
// and use it to update the lastPinRead field for subsequent access with
// the dio_get* functions.
// wait until dio_in & mask == val & mask, returns dio_in value before mask
int dio_groupwait(int handle, int group, int mask, int val) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;
	int min=0,tmp,ret;
	
	if (group < 0 || group > 15) return -2;
	if (group >= conn->groupsInUse) return -2;
	if (conn->pinsInGroup[group] == 0) return -2;
	if (conn->pinsInGroup[group] < 0) {
		dio_group_finalize(conn,group);
	}
	tmp = mask;
	while (tmp) { // count the number of bits that must match
		if (tmp & 1) min++;
		tmp >>= 1;
	}
	EVENT_PROLOG;
	ret = netDIOGroupWait(conn->conn,group,val,mask,min,min);
	CHECK_FAIL(ret);
	groupwait_isync(conn,group,ret);
	EVENT_EPILOG;
	return ret;
}

// same as dio_groupwait except that instead of all pins in the mask
// having to satisfy the condition, only one has to (any)
// this function returns the index of the first (lowest indexed) pin 
// in the group that matched
int dio_groupwaitirq(int handle, int group, int polarity, int mask) {
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;
	int i=1,ret;
	
	if (group < 0 || group > 15) return 0;
	if (group >= conn->groupsInUse) return 0;
	if (conn->pinsInGroup[group] == 0) return 0;
	if (conn->pinsInGroup[group] < 0) {
		dio_group_finalize(conn,group);
	}
	EVENT_PROLOG;
	ret = netDIOGroupWait(conn->conn,group,polarity,mask,1,16);
	CHECK_FAIL(ret);
	groupwait_isync(conn,group,ret);
	EVENT_EPILOG;
	while (ret) {
		if (ret & 1) return i;
		ret >>= 1;
		i++;
	}
	return -1; // should be unreachable
}

// assumes group is finalized
int dio_groupfree(int handle, int group) {
	int i;
	dio_connection *conn = dio_handle_to_ptr(handle);
	if (!conn) return -1;

	if (group < 0 || group > 15) return 0;
	if (group >= conn->groupsInUse) return 0;
	if (conn->pinsInGroup[group] == 0) return 0;
	for (i=0;i<conn->pinsInGroup[group];i++) {
		conn->pinInGroup[conn->groupPins[group][i]]--;
	}
	conn->pinsInGroup[group] = 0;
	if (group == conn->groupsInUse-1) {
		conn->groupsInUse--;
	}
	return 1;
}


//int dio_delay(int handle, unsigned int ns);	/* Forces the server to honor at least an X ns gap inbetween ops */
/* The server implements this by taking a high precision timestamp at the end of manipulating any register on behalf
 * of a client.  When the delay protocol opcode arrives at the server, the server adds this time to the timestamp of
 * the last DIO register read/write from that particular client and remembers this value.  The server then continues 
 * processing -- it does NOT delay its execution at this point.  When the session next requests a DIO register 
 * read/write another timestamp is taken right before the register is read/written.  It then compares this timestamp 
 * with the timestamp remembered to verify the required time has passed.  If it hasn't yet passed, the server will
 * delay first by calling sched_yield(), then by either busy-waiting or calling usleep() if the time is greater than
 * 1 ms
 */

/*
  Author: Michael Schmidt (michael@embeddedARM.com)
 */
