#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
#include <string.h>
#include "sock.h"
#include "opt.h"
#include "mytime.h"

static const char *version = "$Id: dmxctl.c,v 1.2 2010/03/31 23:38:50 michael Exp $";

/*
(c)2010 Technologic Systems
Author: Michael Schmidt

Notes:
1. For greater reliability the following is recommended:
     total_channels = 512
       some devices do not appear to work properly (missing some events)
       when fewer channels are sent
     send_continuously = 1
       when this is set the current DMX state will be sent out regularly,
       which can be helpful in the case of data corruption, or fixture
       testing.  (e.g. you don't have to do anything when plugging in
       a new device for it to come up in the current state)
*/

/*
  readt
    read bytes to <buf>
    returns when bytes read reaches <count>
    or when <ms> milliseconds have elapsed, whichever comes first
    returns the number of bytes read
  assumptions: file is open in non-blocking mode

  TODO: 
  * if <ms> is a null pointer, function will wait indefinitely
  * if count is zero, wait <ms> or until data is ready, then return
*/

// ==================================================================
// SERVER defs
typedef struct event_req {
  unsigned tick;
  unsigned short channel;
  unsigned char value;
  unsigned char command;
  //0 = queue event
  //1 = ignore tick, assign value immediately
  //2 = ignore value, run all queued events at their specified times
} event_req;
typedef struct event_repl {
  unsigned char seq;
} event_repl;
struct instance_data {
};
struct server_data {
};
int server_service(SC *conn,void *data,SC *head);
SC *new_server_conn(int s);

// ==================================================================
// API
int set_channels(int n); // return refresh rate in ticks
int set_refresh(unsigned ticks); // return channels available
void set(unsigned tick,int channel, int value);
void step(unsigned ticks,int channel,int begin,int end,int totalTicks);
void run();

// ==================================================================
// XUART OPCODES

static inline unsigned short ch_break(int bits) {
  unsigned short opcode;

  // assert(bits < 65536)
  if (bits > 1023) {
    opcode = 0x0C00 + bits / 1024;
  } else {
    opcode = 0x0800 + bits;
  }
  return opcode;
}

static inline unsigned short ch_idle(int bits) {
  unsigned short opcode;

  // assert(bits < 65536)
  if (bits > 1023) {
    opcode = 0x1400 + bits / 1024;
  } else {
    opcode = 0x1000 + bits;
  }
  return opcode;
}

static inline unsigned short ch_data(unsigned char data) {
  unsigned short opcode = 0x100 + data;

  return opcode;
}

#define T_DATA 0
#define T_BREAK 1
#define T_IDLE 2
static inline int ch_type(unsigned short data) {
  return (data >> 11) & 3;
}

static inline int ch_uart(unsigned short data) {
  return (data >> 13);
}

static inline unsigned ch_time(unsigned short data) {
  if (data & 0x400) {
    return (data & 0x3FF) << 6;
  } else {
    return (data & 0x3FF);
  }
}
static inline unsigned char ch_dat(unsigned short data) {
  return (data >> 2) & 0xFF;
}

// ==================================================================
// INTERNAL DATA STRUCTURES

// Internally we run a scheduling algorithm
// Each channel has a linked-list of events, each having following:
//   tick 
//   value
// The entries are sorted ascending by tick.

typedef struct event {
  unsigned tick;
  int value;
  struct event *next;
} event;

void dump_events(event *head) {
  while (head) {
    printf("%d:%d\n",head->tick,head->value);
    head = head->next;
  }
  printf("END\n");
}

// search for the last event in the chain 
// before (tick value less than) the specified tick.
// returns the event found, suitable for inserting after
// returns null if caller should insert before the head
event *find_event(event *head,unsigned tick) {
  // example: insert 5
  // case 1: 4,5,6 returns 4
  // case 2: 5,6,7 returns null
  // case 3: 6,7,8 returns null
  if (!head || head->tick > tick) return 0;
  while (head->next && head->next->tick < tick) {
    head = head->next;
  }
  return head;
}

// create a new event and insert it in the correct position in the chain
// returns the head of the chain, which may have changed
event *add_event(event *head,unsigned tick,int value) {
  event *prev,*ev = malloc(sizeof(event));
  ev->tick = tick;
  ev->value = value;
  ev->next = 0;
  prev = find_event(head,tick);
  if (!prev) {
    ev->next = head;
    return ev;
  } else {
    if (ev->value == prev->value) {
      free(ev); // redundant, ignore
    } else {
      ev->next = prev->next;
      prev->next = ev;
    }
    return head;
  }
}

// delete the first event in the change and return the new head
event *del_event(event *head) {
  if (!head) return 0;
  event *next = head->next;
  free(head);
  return next;
}

// ==================================================================
// INTERNAL DATA STRUCTURES
event *chhead[512]; // linked-list of events for each channel
unsigned short txbuf[515]; // DMX transmit buffer
int total_channels = 512; // total number of channels to send
int fd=-1; // file descriptor to send to
int send_continuously = 0;
int opt_debug=0;
// ==================================================================
// API implementation internal functions

// *current_time is what the gettimeofday() was when *current_tick was at
// its current value.  now some time has passed and we want to synchronize
// the two again.  see how much time has actually passed and update
// *current_tick accordingly, and then update *current_time as well.
// in general not too much time should pass between calls, so we will
// assume we won't have any integer overflows using only 32-bits.
void update_current_tick(struct timeval *current_time,unsigned *current_tick) {
  struct timeval now;
  unsigned ticks;
  static int unused_us = 0;

  gettimeofday(&now,0);
  ticks = 100*(now.tv_sec - current_time->tv_sec)
    + (now.tv_usec - current_time->tv_usec + unused_us) / 10000;
  // to avoid rounding errors, we accumulate the us not in the tick
  unused_us = (now.tv_usec - current_time->tv_usec + unused_us) % 10000;
  *current_time = now;

  *current_tick += ticks;
}

void send_dmx();
/*
  There are two ways to sleep.  One is to sleep in the program using
  a combination of usleep and busy-waiting.  The other is to make the
  xuart do the sleeping.

  with busy waiting we assume all our waits will be short enough we
  won't overflow anything.

  even when we are waiting we should transmit packets if we have time.
  the reason is that (apparently) sometimes there will be a hiccup and
  a unit won't get a message.

  if the "maysend" flag is set, we will send DMX packets while waiting,
  if that option is enabled.  not set when we are waiting in send_dmx()
  for obvious reasons!
 */
void dosleep(unsigned us,int maysend) {
  int rem = us % 10000;
  struct timeval now,then;
  unsigned uswait = 100 + 10 + 44*(3+total_channels);

  if (us <= 0) return;
  gettimeofday(&now,0);
  then.tv_usec = now.tv_usec + (us % 1000000);
  then.tv_sec = now.tv_sec + (us / 1000000);
  if (then.tv_usec > 1000000) {
    then.tv_usec -= 1000000;
    then.tv_sec++;
  }
  us -= rem;
  if (maysend && send_continuously) {
    while (us >= uswait) {
      if (opt_debug) {
	printf("sleep: %u >= %u\n",us,uswait);
      }
      send_dmx();
      gettimeofday(&now,0);
      if (now.tv_sec > then.tv_sec
	  || now.tv_sec == then.tv_sec && now.tv_usec >= then.tv_usec) {
	return;
      }
      us = (then.tv_sec - now.tv_sec) * 1000000 + (then.tv_usec - now.tv_usec);
    }
  }
  if (us) {
    usleep(us);
  }
  while (1) {
    gettimeofday(&now,0);
    if (now.tv_sec > then.tv_sec
	|| now.tv_sec == then.tv_sec && now.tv_usec >= then.tv_usec) {
      return;
    }
  }
}

void send_dmx() {
  int uswait;

  write(fd,txbuf,(3+total_channels)*sizeof(unsigned short));
  // microseconds to sleep = break + idle + 44 per byte sent
  uswait = 100 + 10 + 44*(3+total_channels);
  dosleep(uswait,0);
}

// ==================================================================
// API implementation

// schedule channel to be set to value when specified tick occurs
void set(unsigned tick,int channel, int value) {
  if (channel < 0 || channel > 511) return;
  chhead[channel] = add_event(chhead[channel],tick,value);
}

// schedule a steady transition between begin and end value on the
// specified channel to take the specified number of total ticks
void step(unsigned ticks,int channel,int begin,int end,int totalTicks) {
  unsigned i,n=0;

  // (end - begin) values to iterate through
  // total values / total ticks = values per tick
  if ((end-begin) >= totalTicks) {
    // step by one or more values per tick
    // (end-begin) / totalTicks  
    for (i=0;i<=totalTicks;i++) {
      n += (end-begin);
      set(begin+i,channel,begin+n/totalTicks);
    }
  } else {
    // step by one or more ticks per value
    // totalTicks / (end-begin)
    if (end >= begin) {
      for (i=0;i<=(end-begin);i++) {
	n += totalTicks;
	set(n/(end-begin),channel,begin+i);
      }
    } else {
      for (i=0;i<=(begin-end);i++) {
	n += totalTicks;
	set(n/(begin-end),channel,begin-i);
      }
    }
  }
}
/*
  A bit on the arithmetic above.

  suppose we want to step 7 values in 4 ticks. how do we do that?
  if we add 7/4 per tick we will be short, since 7/4 = 1 in integers.

  the trick is to increment by the numerator.  for 7/4 we
  increment by 7 each time and then divide the running count by 4.

  tick  value int_value 4ths 4ths/4
  1      1.75  1 or 2     7    1
  2      3.5   3 or 4    14    3
  3      5.25    5       21    5
  4      7       7       28    7  
 */

// run() divides up or partitions sets of DMX control
// it waits until all pending events have occured before returning
// after each run the tick counter is reset to 0, so all subsequent
// API calls must specify ticks relative to the last run.
void run() {
  int i;
  unsigned current_tick = 0,next_tick;
  struct timeval current_time;

  gettimeofday(&current_time,0);
  while (1) {
    next_tick = 0xFFFFFFFF;
    // peek at the head of each channel and find the lowest tick value.
    // that is how many ticks until we need to do anything
    for (i=0;i<total_channels;i++) {
      if (chhead[i] && chhead[i]->tick < next_tick) {
	next_tick = chhead[i]->tick;
      }
    }
    if (next_tick == 0xFFFFFFFF) {
      send_dmx(); // make sure we are up to date
      return; // nothing left to do!
    }
    // sleep that many ticks, and increment the tick counter
    // usleep might sleep more than we wanted to but we track actual time
    // passed using gettimeofday.
    if (opt_debug) {
      printf("@%d.%06d:%d:",current_time.tv_sec,current_time.tv_usec,current_tick);
      printf("now %u, sleeping to %u\n",current_tick,next_tick);
    }
    if (next_tick > current_tick) {
      dosleep((next_tick - current_tick)*10000,1);
    }
    update_current_tick(&current_time,&current_tick);

    if (opt_debug) {
      printf("%d.%06d:%d:(%d):",current_time.tv_sec,current_time.tv_usec,current_tick,next_tick);
    }
    //for each channel
    //  while head's tick value <= current tick value
    //    update the value of that channel in the dmx packet buffer
    //    delete the head from the channel queue
    for (i=0;i<total_channels;i++) {
      while (chhead[i] && chhead[i]->tick <= next_tick) {
	if (opt_debug) {
	  printf("%d=%d ",i,chhead[i]->value);
	}
	// set_dmx(i+3,chhead[i]->value);
	txbuf[3+i] = ch_data(chhead[i]->value);
	chhead[i] = del_event(chhead[i]);
      }
    }
    if (opt_debug) {
      printf("\n");
      //    printf("\t%d %d %d\n",txbuf[6],txbuf[10],txbuf[14]);
    }
    // peek again for the purpose of seeing if we are keeping up
    next_tick = 0xFFFFFFFF;
    for (i=0;i<total_channels;i++) {
      if (chhead[i] && chhead[i]->tick < next_tick) {
	next_tick = chhead[i]->tick;
      }
    }
    // send a DMX packet and wait for it to finish send on the wire
    if (next_tick >= current_tick) { // don't send (yet) if we are behind
      send_dmx();
      update_current_tick(&current_time,&current_tick);
    } else {
      if (opt_debug) {
	printf("* %u>=%u\n",next_tick,current_tick);
      }
    }
  }
}

/*
  arun is like run, except instead of playing the queued events over
  DMX it instead prints the events out without any delays.
 */
void arun() {
  int i;
  unsigned current_tick = 0,next_tick;
  struct timeval current_time;

  while (1) {
    next_tick = 0xFFFFFFFF;
    for (i=0;i<total_channels;i++) {
      if (chhead[i] && chhead[i]->tick < next_tick) {
	next_tick = chhead[i]->tick;
      }
    }
    if (next_tick == 0xFFFFFFFF) {
      return; // nothing left to do!
    }
    for (i=0;i<total_channels;i++) {
      while (chhead[i] && chhead[i]->tick <= next_tick) {
	printf("%3d.%03d %d %d\n",next_tick/100,next_tick%100,i,chhead[i]->value);
	chhead[i] = del_event(chhead[i]);
      }
    }
  }
}

/*
  arun is like arun, except instead of printing the queued events
  it instead queues them to a server via the passed socket

  run, arun, and nrun has enough similarity we could probably factor out
  the common functionality.
 */
void nrun(int s) {
  int i;
  unsigned current_tick = 0,next_tick;
  struct timeval current_time;
  event_req req;
  event_repl repl;

  while (1) {
    next_tick = 0xFFFFFFFF;
    for (i=0;i<total_channels;i++) {
      if (chhead[i] && chhead[i]->tick < next_tick) {
	next_tick = chhead[i]->tick;
      }
    }
    if (next_tick == 0xFFFFFFFF) {
      req.command = 2;
      write(s,&req,sizeof(req));
      read(s,&repl,1);
      return; // nothing left to do!
    }
    for (i=0;i<total_channels;i++) {
      while (chhead[i] && chhead[i]->tick <= next_tick) {
	req.tick = next_tick;
	req.channel = i;
	req.value = chhead[i]->value;
	req.command = 0;
	write(s,&req,sizeof(req));
	read(s,&repl,1);
	chhead[i] = del_event(chhead[i]);
      }
    }
  }
}

static inline int decval(char ch) {
  switch (ch) {
  case '0': return 0;
  case '1': return 1;
  case '2': return 2;
  case '3': return 3;
  case '4': return 4;
  case '5': return 5;
  case '6': return 6;
  case '7': return 7;
  case '8': return 8;
  case '9': return 9;
  default: return -1;
  }
}

// {D}+{"."{D}*}?
char *parse_time(char *txt,unsigned *sec,unsigned *us) {
  long long val = 0, dec=0, decdiv=1;
  
  while (decval(*txt)>=0) {
    val *= 10;
    val = val + (decval(*txt));
    txt++;
  }
  if (*txt == '.') {
    txt++; // '.'
    while (decval(*txt)>=0) {
      dec *= 10;
      decdiv *= 10;
      dec = dec + (decval(*txt));
      txt++;
    }
  }
  *sec = val;
  while (decdiv > 6) {
    decdiv--;
    dec /= 10;
  }
  while (decdiv < 6) {
    decdiv++;
    dec *= 10;
  }
  *us = dec;
  return txt;
}

/*
  Format of DMX replay file:
  <time in seconds> <channel number> <value>

  Time is formatted like this:
    nnn.mmm
  where nnn is the second and mmm is the fraction of a section.
    
  The time field must start with the first character of the line.

  If the first character of the line is whitespace, the last time
  specified is used and whitespace skipped to get channel number and value.
  If no previous time was specified, 0.0 is assumed.

  Maximum line length: 80 characters

  Total time elapsed between first and last time specified must be
  no more than 1 hour, 11 minutes, 1 second.
 */
void load_dmx(FILE *f) {
  char line[83]; // 80 + CR + LF + NULL
  char *s;
  long long starting_time = -1, this_time=0;
  unsigned sec=0,usec=0,next_tick;
  unsigned channel, value;
  unsigned tick;

  while (!feof(f)) {
    fgets(line,83,f);
    s = parse_time(line,&sec,&usec);
    if (s != line) {
      this_time = (long long)sec * 1000000 + usec;
    }
    if (starting_time == -1) {
      starting_time = this_time;
    }
    tick = (this_time - starting_time) / 10000;
    if (sscanf(s,"%u %u",&channel,&value) == 2
	&& channel < total_channels) {
      chhead[channel] = add_event(chhead[channel],tick,value);
    }
  }
}

static inline void decode_ch(unsigned short data) {
  static int last_channel = -1;

  if (ch_uart(data) != last_channel) {
    last_channel = ch_uart(data);
    printf("\n%02d:",ch_uart(data));
  }
  switch (ch_type(data)) {
  case T_DATA:
    printf("%02X ",((data >> 2) & 0xFF));
    break;
  case T_BREAK:
    printf("BREAK %d ",ch_time(data));
    fflush(stdout);
    break;
  case T_IDLE:
    printf("IDLE %d ",ch_time(data));
    fflush(stdout);
    break;
  default:
    printf("ERROR ");
    break;
  }
}

/*
  psuedo-fifo
  whenever data is requested and the buffer is empty,
  we try to fill it up, then note home much we actually got.
  after that, we just take bytes out until we are empty.
  dmxq is the head, dmxh is the tail
 */
unsigned short get_dmx() {
  static unsigned short dmxbuf[1024];
  static int dmxq = 0, dmxh = 0;
  unsigned ms;
  int n;

  if (dmxq == 0 || dmxh == dmxq) {
    ms = 270; // should get an IDLE every 260ms if nothing else happens!
    n = readt(fd,dmxbuf,2048,&ms);
    if (n == 0) {
      return 0;
    }
    assert (n%2 == 0);
    dmxq = n/2;
    dmxh = 0;
  } 
  if (opt_debug) {
    decode_ch(dmxbuf[dmxh]);
  }
  return dmxbuf[dmxh++];
}

int get_idle(int min) {
  unsigned short val = get_dmx();
  if (ch_type(val) == T_IDLE && ch_time(val) >= min) {
    return ch_time(val);
  } else {
    return -1;
  }
}

int alarmed = 0;
static void alarmsig(int x) {
  alarmed = 1;
}

int wait_for_break(int min) {
  unsigned short val;
  int bts=0;
  do {
    val = get_dmx();
    switch (ch_type(val)) {
    case T_DATA:
      bts += 11;
      break;
    case T_BREAK:
    case T_IDLE:
      bts += ch_time(val);
      break;
    }
  } while (!alarmed && ch_type(val) != T_BREAK || ch_time(val) < min);
  return bts;
}

// get the next data bytes
// returns number of bit times reported 
// returns -1 if a break was encountered
// and puts the break time in *bt.
int get_data_no_break(unsigned char *data,int *bt) {
  unsigned short val;
  int bts=0;
  while (!alarmed) {
    val = get_dmx();
    switch (ch_type(val)) {
    case T_DATA:
      bts += 11;
      *data = ch_dat(val);
      return bts;
    case T_BREAK:
      *bt = ch_time(val);
      return bts;
    case T_IDLE:
      bts += ch_time(val);
      break;
    }
  }
}

/*
  wait for break >= 22, idle >= 2
  read up to 513 values. if we get any break we stop reading
  track how much time is between packets.  11 per byte plus explicit idle times
  up the idle threshold if needed
 */
void listen_dmx(int s) {
  unsigned short buffer[1024];
  int i,n,bt=0,bts=0,started=0;
  unsigned char data[513];
  unsigned char chdata[512];
  unsigned noww = 0, nowr = 0;
  struct sigaction sa;

  for (i=0;i<512;i++) {
    data[i] = chdata[i] = 0;
  }
  data[i] = 0;

  sa.sa_handler = alarmsig;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = 0;
  sigaction(SIGALRM, &sa, NULL);
  alarm(s);
  while (!alarmed) {
    bts = bt + nowr;
    nowr = 0;
    if (bt == 0) {
      bts += wait_for_break(22);
    }
    n = get_idle(2);
    if (n < 0) {
      continue;
    }
    bts += n;
    bt = 0;
    for (i=0;i<513;i++) {
      bts += get_data_no_break(data+i,&bt);
      if (bt) {
	break;
      }
    }
    if (bt) {
      continue;
    }
    if (started == 0) {
      noww = 0;
      started = 1;
    } else {
      noww += bts/250;
      nowr = bts % 250;
    }
    for (i=0;i<512;i++) {
      if (chdata[i] != data[i+1]) {
	chdata[i] = data[i+1];
	set(noww/10,i,chdata[i]);
	//printf("%d.%d %d %d\n",noww/1000,noww%1000,i,chdata[i]);
      }
    }
  }
  arun();
}

int opt_int(char *arg,int *target,int opt) {
  if (!arg) {
    *target = 7033;
  } else {
    *target = atoi(arg);
  }
  return 1;
}

// tries to open a file; descriptor goes in *target
int opt_fd(char *arg,int *target,int opt) {
  *target = open(arg,O_RDWR|O_NONBLOCK);
  if (*target < 0) {
    fprintf(stderr,"open(%s):%m",arg);
    return 0;
  }
  return 1;
}

/*
  The following code illustrates how one can write C code to generate
  DMX events, e.g. for a light show.
 */
void run_demo(int s) {
  // sample light show
  int i,j,n=0;

  // channel aliases
  const int light1 = 3;
  const int light2 = 7;
  const int light3 = 11;

  const int RED = 0;
  const int GREEN = 1;
  const int BLUE = 2;

  const int ds1 = 0;
  const int blue1 = 1;
  const int green1 = 2;
  const int red1 = 3;
  const int ds2 = 4;
  const int blue2 = 5;
  const int green2= 6;
  const int red2 = 7;
  const int ds3 = 8;
  const int blue3 = 9;
  const int green3= 10;
  const int red3 = 11;

  // set dimmers to maximum
  set(0,ds1,255);
  set(0,ds2,255);
  set(0,ds3,255);
  if (s < 0) {
    run();
  } else {
    nrun(s);
  }

  // fade up red1, green2 and blue3 in 1 second
  step(0,red1,1,152,100);
  step(0,green2,1,152,100);
  step(0,blue3,1,152,100);
  if (s < 0) {
    run();
  } else {
    nrun(s);
  }
  // fade down red1, green2 and blue3 in 1 second
  step(0,red1,152,0,100);
  step(0,green2,152,0,100);
  step(0,blue3,152,0,100);
  if (s < 0) {
    run();
  } else {
    nrun(s);
  }
  // turn everything off
  set(0,red1,0);
  set(0,green2,0);
  set(0,blue3,0);
  if (s < 0) {
    run();
  } else {
    nrun(s);
  }

  // rotate between colors on each fixture at increasing frequency
  int color1 = RED;
  int color2 = GREEN;
  int color3 = BLUE;

  int hz = 8;
  int sec = 1;
  n = 0;
  for (hz=1;hz<16;hz++) {
    for (i=0;i<hz*sec;i++) {
      set((n*100*sec)+100/hz*i,light1-color1,0);
      if ((++color1) > BLUE) color1 = RED;
      set((n*100*sec)+100/hz*i,light1-color1,152);
      
      set((n*100*sec)+100/hz*i,light2-color2,0);
      if ((++color2) > BLUE) color2 = RED;
      set((n*100*sec)+100/hz*i,light2-color2,152);
      
      set((n*100*sec)+100/hz*i,light3-color3,0);
      if ((++color3) > BLUE) color3 = RED;
      set((n*100*sec)+100/hz*i,light3-color3,152);
    } 
    n++;
  }
  if (s < 0) {
    run();
  } else {
    nrun(s);
  }

  // turn everything off
  set(0,red1,0);
  set(0,red2,0);
  set(0,red3,0);
  set(0,green1,0);
  set(0,green2,0);
  set(0,green3,0);
  set(0,blue1,0);
  set(0,blue2,0);
  set(0,blue3,0);
  if (s < 0) {
    run();
  } else {
    nrun(s);
  }

  // now cycle red back and forth
  for (i=0;i<10;i++) {
    set(0,red1,152);
    set(20,red1,0);
    set(20,red2,152);
    set(40,red2,0);
    set(40,red3,152);
    set(60,red3,0);
    set(60,red2,152);
    set(80,red2,0);
    set(80,red1,152);
    if (s < 0) {
      run();
    } else {
      nrun(s);
    }
  }

  // turn everything off
  set(0,red1,0);
  set(0,red2,0);
  set(0,red3,0);
  if (s < 0) {
    run();
  } else {
    nrun(s);
  }

  // now flash the green and blue LEDs quickly
  for (i=0;i<2;i++) {
    n=0;
    set(n,green1,152);  set(n,green2,152);  set(n,green3,152);
    set(n,blue1,152);   set(n,blue2,152);   set(n,blue3,152);
    set(n+20,green1,0); set(n+20,green2,0); set(n+20,green3,0);
    set(n+20,blue1,0);  set(n+20,blue2,0);  set(n+20,blue3,0);
    n += 100;
    for (j=0;j<4;j++) {
      set(n,green1,152);  set(n,green2,152);  set(n,green3,152);
      set(n+10,green1,0); set(n+10,green2,0); set(n+10,green3,0);
      n += 20;
    }
    n += 50;
    set(n,blue1,152);   set(n,blue2,152);   set(n,blue3,152);
    set(n+20,blue1,0);  set(n+20,blue2,0);  set(n+20,blue3,0);
    n += 100;
    set(n,red1,0); // timing only
    if (s < 0) {
      run();
    } else {
      nrun(s);
    }
  }

  color1 = RED;
  color2 = GREEN;
  color3 = BLUE;

  for (i=0;i<24;i++) {
    // fade up red1, green2 and blue3 in .1 second
    step(0,light1-color1,1,150,10);
    step(0,light2-color2,1,150,10);
    step(0,light3-color3,1,150,10);
    if (s < 0) {
      run();
    } else {
      nrun(s);
    }
    // fade down red1, green2 and blue3 in .1 second
    step(0,light1-color1,150,0,10);
    step(0,light2-color2,150,0,10);
    step(0,light3-color3,150,0,10);
    if (s < 0) {
      run();
    } else {
      nrun(s);
    }
    // turn everything off
    set(0,red1,0);
    set(0,red2,0);
    set(0,red3,0);
    set(0,green1,0);
    set(0,green2,0);
    set(0,green3,0);
    set(0,blue1,0);
    set(0,blue2,0);
    set(0,blue3,0);
    if (s < 0) {
      run();
    } else {
      nrun(s);
    }

    if (i < 12) {
      if ((++color1) > BLUE) color1 = RED;
      if ((++color2) > BLUE) color2 = RED;
      if ((++color3) > BLUE) color3 = RED;
    }
  }

}

//==================================================================
// SERVER

static unsigned char seq = 0;

void server_process_event(event_req *ev) {
  if (ev->channel >= total_channels) return;
  switch (ev->command) {
  case 0:
    set(ev->tick,ev->channel,ev->value);
    break;
  case 1:
    if (ev->channel < total_channels) {
      txbuf[3+ev->channel] = ch_data(ev->value);
      send_dmx();
    }
    break;
  case 2:
    run();
    break;
  }
}

int server_service(SC *conn,void *data,SC *head) {
  event_req *ev = data;

  server_process_event((event_req *)conn->buf);
  seq++;
  write(conn->s,&seq,1);
  conn->used = 0;
  return 1;
}

SC *new_server_conn(int s) {
  SC *conn = malloc(sizeof(SC));

  conn->s = s;
  conn->buf = malloc(sizeof(event_req));
  conn->len = sizeof(event_req);
  conn->used = 0;
  conn->want = sizeof(event_req);
  conn->inst_data = 0;
  conn->next = 0;
  conn->wbuf = 0;
  conn->wlen = 0;
  conn->wwr = 0;
  conn->wfd = 0;
  return conn;
}

//==================================================================
int main(int argc, char **argv) {
  int opt_listen = 0 , opt_server = 0, 
      opt_client = -1, opt_demo = 0;
  char *opt_file = 0;
  struct option2 opts[] = {
    {1, (opt_func)opt_int , &opt_listen       , "<l>isten","<seconds>   capture DMX packets and print to stdout" },
    {1, (opt_func)opt_str , &opt_file         , "<r>eplay","<file>      play DMX events as specified in file" },
    {0, (opt_func)opt_bool, &send_continuously, "<a>utosend","          sends DMX packets even when no new events occur" },
    {1, (opt_func)opt_int , &total_channels   , "<c>hannels","<num>     send the specified number of DMX channels" },
    {2, (opt_func)opt_int , &opt_server       , "<s>erver","<port>      run as a server.  The default port is 7033." },
    {1, (opt_func)opt_fd  , &fd               , "<d>evice",0 },
    {2, (opt_func)opt_cli , &opt_client       , "cl<i>ent","<host:>port connect to the specified DMX server\n"
                                             "-i | --client=dev         connect to the specified UART device" },
    {0, (opt_func)opt_bool, &opt_debug        , "debu<g>",0 },
    {0, (opt_func)opt_bool, &opt_demo         , "de<m>o","              play a demo sequence" },
    { 0,0,0,"Technologic Systems DMX application\n","" }
  };

  process_options(argc,argv,opts);

  if (opt_debug) {
    printf("version: " __TIME__ "\n");
  }

  if (opt_client == -1 && opt_server == 0 && fd == -1) {
    printf("Need to specify exactly one of --server, --client, or --device.\n");
    return 1;
  }

  if (opt_client != -1) {
    fd = opt_client; // assumes local device file!
  }

  int i;
  // initialization
  txbuf[0] = ch_break(100); // 100
  txbuf[1] = ch_idle(10); // 10
  txbuf[2] = ch_data(0);
  for (i=3;i<515;i++) txbuf[i] = ch_data(0);

  if (opt_demo) {
    run_demo(opt_client);
  }
  if (opt_listen) {
    fileSetBlocking(fd,0);
    listen_dmx(opt_listen);
  }
  if (opt_file) {
    FILE *f = fopen(opt_file,"r");
    if (!f) {
      fprintf(stderr,"fopen(%s):%m",opt_file);
    } else {
      load_dmx(f);
      if (opt_client < 0) {
	run();
      } else {
	nrun(opt_client);
      }
    }
  }
  if (opt_server) {
    int s;
    struct server_data sd;

    signal(SIGPIPE,SIG_IGN);
    s = create_server_socket(opt_server);
    if (s < 0) {
      perror("create_server_socket:");
      return 3;
    }
    daemon(1,0);
    run_multi_server(s,server_service,new_server_conn,&sd,0);
    return 0;
  }
}

