/* This file is part of the Q programming system.

   The Q programming system is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   The Q programming system is distributed in the hope that it will be
   useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

/* system headers */

#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <math.h>

/* check for standard C headers */
#if STDC_HEADERS
# include <stdlib.h>
# include <string.h>
#else
# ifndef HAVE_STRCHR
#  define strchr index
#  define strrchr rindex
# endif
char *strchr (), *strrchr ();
#endif

#ifdef HAVE_MALLOC_H
#include <stdlib.h>
#endif

#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif

#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include <sys/types.h>
#if HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#ifndef WEXITSTATUS
# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
#endif
#ifndef WIFEXITED
# define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef WIN32
#include <windows.h>
#endif

#include <dxl.h>
#include <libq.h>

#ifdef DMALLOC
#include <dmalloc.h>
#endif

MODULE(dxl)

#ifndef HAVE_STRDUP

static char *strdup(char *s)
{
  char *t;
  return ((t=malloc(strlen(s)+1))?strcpy(t, s):NULL);
}

#endif

/* handle SIGINT and SIGTERM */

#if RETSIGTYPE == void
#define SIGHANDLER_RETURN(status) return
#else
#define SIGHANDLER_RETURN(status) return status
#endif

#ifdef MUST_REINSTALL_SIGHANDLERS
#define SIGHANDLER_RESTORE(sig,handler) syssignal(sig,handler)
#else
#define SIGHANDLER_RESTORE(sig,handler) /* nop */
#endif

typedef RETSIGTYPE (*sighandler_t)(int);

static sighandler_t syssignal(sig, handler)
     int sig;
     sighandler_t handler;
{
#ifdef HAVE_POSIX_SIGNALS
  struct sigaction new_action, old_action;
  new_action.sa_handler = handler;
  sigemptyset(&new_action.sa_mask);
  sigemptyset(&old_action.sa_mask);
  new_action.sa_flags = 0;
  sigaction(sig, &new_action, &old_action);
  return old_action.sa_handler;
#else
  return signal(sig, handler);
#endif
}

static volatile int brkflag = 0;
static sighandler_t int_handler = NULL, term_handler = NULL,
  hup_handler = NULL;

static RETSIGTYPE break_handler(int sig)
{
  if (sig == SIGINT && int_handler) int_handler(sig);
  if (sig == SIGTERM && term_handler) term_handler(sig);
#ifdef SIGHUP
  if (sig == SIGHUP && hup_handler) hup_handler(sig);
#endif
  SIGHANDLER_RESTORE(sig, break_handler);
  brkflag = 1;
  SIGHANDLER_RETURN(0);
}

/* take a short sleep (approx 1/100 secs) */

static void sleep_some(void)
{
#ifdef WIN32
  Sleep(10);
#else
#ifdef HAVE_NANOSLEEP
  struct timespec req;
  req.tv_sec = 0; req.tv_nsec = 10000000;
  nanosleep(&req, NULL);
#else
#ifdef HAVE_USLEEP
  usleep(10000);
#else
  sleep(1);
#endif
#endif
#endif
}

/* DXHandle struct */

typedef struct qentry {
  char *s, *t;
  struct qentry *next;
} qentry;

typedef struct {
  volatile int rdy; /* lost connection indicator */
  DXLConnection *conn; /* DXLink connection */
  qentry *head, *tail; /* message queue */
} DXLHandle;

/* message queue operations */

static void initqueue(DXLHandle *h)
{
  h->head = h->tail = NULL;
}

static void freequeue(DXLHandle *h)
{
  qentry *head, *next;
  for (head = h->head; head; head = next) {
    next = head->next;
    if (head->s) free(head->s);
    if (head->t) free(head->t);
    free(head);
  }
  h->head = h->tail = NULL;
}

static int emptyqueue(DXLHandle *h)
{
  return h->head == NULL;
}

static char *head_s(DXLHandle *h)
{
  if (h->head)
    return h->head->s;
  else
    return NULL;
}

static char *head_t(DXLHandle *h)
{
  if (h->head)
    return h->head->t;
  else
    return NULL;
}

static void enqueue(DXLHandle *h, char *s, char *t)
{
  qentry *next = malloc(sizeof(qentry));
  if (!next) {
    /* FIXME: do some more appropriate error handling here */
    h->rdy = 0;
    return;
  }
  next->s = s;
  next->t = t;
  next->next = NULL;
  if (h->head) {
    h->tail->next = next;
    h->tail = next;
  } else
    h->head = h->tail = next;
}

static void dequeue(DXLHandle *h)
{
  qentry *next;
  if (!h->head) return;
  next = h->head->next;
  free(h->head);
  if (next)
    h->head = next;
  else
    h->head = h->tail = NULL;
}

/* check handle for validity */

static int valid(DXLHandle *h)
{
  if (h->rdy)
    if (h->conn)
      return 1;
    else
      return (h->rdy = 0);
  else if (h->conn) {
    /* close the connection on our side */
    DXLCloseConnection(h->conn);
    h->conn = NULL;
    freequeue(h);
    return 0;
  } else {
    freequeue(h);
    return 0;
  }
}

/* close a DXLHandle structure */

static void close_handle(DXLHandle *h)
{
  h->rdy = 0;
  valid(h);
}

/* message handlers */

static void conn_lost(DXLConnection *conn, void *data)
{
  DXLHandle *h = data;
  h->rdy = 0;
}

static void err_handler(DXLConnection *conn, const char *msg, void *data)
{
  DXLHandle *h = data;
  enqueue(h, NULL, strdup(msg));
}

static void val_handler(DXLConnection *conn, const char *label,
			const char *value, void *data)
{
  DXLHandle *h = data;
  char *s = strdup(label), *t = strdup(value);
  if (s && t)
    enqueue(h, s, t);
  else {
    if (s) {
      free(s); h->head->s = NULL;
    }
    if (t) {
      free(t); h->head->t = NULL;
    }
  }
}

DESTRUCTOR(dxl,DXLHandle,v)
{
  DXLHandle *h = v;
  close_handle(h);
  free(h);
}

FUNCTION(dxl,dxl_start,argc,argv)
{
  char *s;
  if (argc == 1 && isstr(argv[0], &s)) {
    DXLHandle *h;
    if ((h = (DXLHandle*)malloc(sizeof(DXLHandle)))) {
      h->conn = DXLStartDX(s, NULL);
      if (h->conn) {
	h->rdy = 1;
	initqueue(h);
	DXLSetBrokenConnectionCallback(h->conn, conn_lost, h);
	DXLSetErrorHandler(h->conn, err_handler, h);
	return mkobj(type(DXLHandle), h);
      } else {
	free(h);
	return __FAIL;
      }
    } else
      return __ERROR;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_close,argc,argv)
{
  DXLHandle *h;
  if (argc == 1 && isobj(argv[0], type(DXLHandle), (void**)&h)) {
    if (valid(h)) {
      close_handle(h);
      return mkvoid;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_exit,argc,argv)
{
  DXLHandle *h;
  if (argc == 1 && isobj(argv[0], type(DXLHandle), (void**)&h)) {
    if (valid(h)) {
      DXLError res = DXLExitDX(h->conn);
      h->conn = NULL;
      close_handle(h);
      return (res == OK)?mkvoid:__FAIL;
    } else {
      return __FAIL;
    }
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_load,argc,argv)
{
  DXLHandle *h;
  char *s;
  if (argc == 2 && isobj(argv[0], type(DXLHandle), (void**)&h) &&
      isstr(argv[1], &s)) {
    if (valid(h)) {
      DXLError res = DXLLoadVisualProgram(h->conn, s);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_load_macro,argc,argv)
{
  DXLHandle *h;
  char *s;
  if (argc == 2 && isobj(argv[0], type(DXLHandle), (void**)&h) &&
      isstr(argv[1], &s)) {
    if (valid(h)) {
      DXLError res = DXLLoadMacroFile(h->conn, s);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_load_script,argc,argv)
{
  DXLHandle *h;
  char *s;
  if (argc == 2 && isobj(argv[0], type(DXLHandle), (void**)&h) &&
      isstr(argv[1], &s)) {
    if (valid(h)) {
      DXLError res = exDXLLoadScript(h->conn, s);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_load_config,argc,argv)
{
  DXLHandle *h;
  char *s;
  if (argc == 2 && isobj(argv[0], type(DXLHandle), (void**)&h) &&
      isstr(argv[1], &s)) {
    if (valid(h)) {
      DXLError res = uiDXLLoadConfig(h->conn, s);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl,argc,argv)
{
  DXLHandle *h;
  char *s;
  if (argc == 2 && isobj(argv[0], type(DXLHandle), (void**)&h) &&
      isstr(argv[1], &s)) {
    if (valid(h)) {
      DXLError res = DXLSend(h->conn, s);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_begin_macro,argc,argv)
{
  DXLHandle *h;
  char *s;
  if (argc == 2 && isobj(argv[0], type(DXLHandle), (void**)&h) &&
      isstr(argv[1], &s)) {
    if (valid(h)) {
      DXLError res = exDXLBeginMacroDefinition(h->conn, s);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_end_macro,argc,argv)
{
  DXLHandle *h;
  if (argc == 1 && isobj(argv[0], type(DXLHandle), (void**)&h)) {
    if (valid(h)) {
      DXLError res = exDXLEndMacroDefinition(h->conn);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_exec_once,argc,argv)
{
  DXLHandle *h;
  if (argc == 1 && isobj(argv[0], type(DXLHandle), (void**)&h)) {
    if (valid(h)) {
      DXLError res = DXLExecuteOnce(h->conn);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_exec_on_change,argc,argv)
{
  DXLHandle *h;
  if (argc == 1 && isobj(argv[0], type(DXLHandle), (void**)&h)) {
    if (valid(h)) {
      DXLError res = DXLExecuteOnChange(h->conn);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_exec_once_named,argc,argv)
{
  DXLHandle *h;
  char *s, *t;
  if (argc == 3 && isobj(argv[0], type(DXLHandle), (void**)&h) &&
      isstr(argv[1], &s)) {
    if (valid(h)) {
      expr x, x1, x2;
      long i;
      int n;
      for (n = 0, x = argv[2]; iscons(x, &x1, &x2) && isstr(x1, &t);
	   x = x2, n++)
	if (n == INT_MAX-1) {
	  return __ERROR;
	}
      if (isnil(x)) {
	char **a = (char**)malloc((n+1)*sizeof(char*));
	if (a) {
	  int res;
	  for (n = 0, x = argv[2]; iscons(x, &x1, &x2) && isstr(x1, &t);
	       x = x2, n++)
	    a[n] = t;
	  a[n] = NULL;
	  res = exDXLExecuteOnceNamedWithArgsV(h->conn, s, a);
	  free(a);
	  return (res == OK)?mkvoid:__FAIL;
	} else {
	  return __ERROR;
	}
      } else
	return __FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_exec_on_change_named,argc,argv)
{
  DXLHandle *h;
  char *s, *t;
  if (argc == 3 && isobj(argv[0], type(DXLHandle), (void**)&h) &&
      isstr(argv[1], &s)) {
    if (valid(h)) {
      expr x, x1, x2;
      long i;
      int n;
      for (n = 0, x = argv[2]; iscons(x, &x1, &x2) && isstr(x1, &t);
	   x = x2, n++)
	if (n == INT_MAX-1) {
	  return __ERROR;
	}
      if (isnil(x)) {
	char **a = (char**)malloc((n+1)*sizeof(char*));
	if (a) {
	  int res;
	  for (n = 0, x = argv[2]; iscons(x, &x1, &x2) && isstr(x1, &t);
	       x = x2, n++)
	    a[n] = t;
	  a[n] = NULL;
	  res = exDXLExecuteOnChangeNamedWithArgsV(h->conn, s, a);
	  free(a);
	  return (res == OK)?mkvoid:__FAIL;
	} else {
	  return __ERROR;
	}
      } else
	return __FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_end_exec,argc,argv)
{
  DXLHandle *h;
  if (argc == 1 && isobj(argv[0], type(DXLHandle), (void**)&h)) {
    if (valid(h)) {
      DXLError res = DXLEndExecution(h->conn);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_end_exec_on_change,argc,argv)
{
  DXLHandle *h;
  if (argc == 1 && isobj(argv[0], type(DXLHandle), (void**)&h)) {
    if (valid(h)) {
      DXLError res = DXLEndExecuteOnChange(h->conn);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_seq_ctl,argc,argv)
{
  static int seq_loop_off, seq_loop_on, seq_palindrome_off,
    seq_palindrome_on, seq_play_backward, seq_play_forward, seq_step,
    seq_pause, seq_stop;
  static int init = 0;
  DXLHandle *h;
  if (!init) {
    init = 1;
    seq_loop_off = sym(seq_loop_off);
    seq_loop_on = sym(seq_loop_on);
    seq_palindrome_off = sym(seq_palindrome_off);
    seq_palindrome_on = sym(seq_palindrome_on);
    seq_play_backward = sym(seq_play_backward);
    seq_play_forward = sym(seq_play_forward);
    seq_step = sym(seq_step);
    seq_pause = sym(seq_pause);
    seq_stop = sym(seq_stop);
  }
  if (argc == 2 && isobj(argv[0], type(DXLHandle), (void**)&h)) {
    if (valid(h)) {
      DXLError res;
      if (exprsym(argv[1]) == seq_step)
	res = DXLSequencerCtl(h->conn, SeqStep);
      else if (exprsym(argv[1]) == seq_pause)
	res = DXLSequencerCtl(h->conn, SeqPause);
      else if (exprsym(argv[1]) == seq_stop)
	res = DXLSequencerCtl(h->conn, SeqStop);
      else if (exprsym(argv[1]) == seq_loop_off)
	res = DXLSequencerCtl(h->conn, SeqLoopOff);
      else if (exprsym(argv[1]) == seq_loop_on)
	res = DXLSequencerCtl(h->conn, SeqLoopOn);
      else if (exprsym(argv[1]) == seq_palindrome_off)
	res = DXLSequencerCtl(h->conn, SeqPalindromeOff);
      else if (exprsym(argv[1]) == seq_palindrome_on)
	res = DXLSequencerCtl(h->conn, SeqPalindromeOn);
      else if (exprsym(argv[1]) == seq_play_backward)
	res = DXLSequencerCtl(h->conn, SeqPlayBackward);
      else if (exprsym(argv[1]) == seq_play_forward)
	res = DXLSequencerCtl(h->conn, SeqPlayForward);
      else
	return __FAIL;
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_render_mode,argc,argv)
{
  DXLHandle *h;
  char *s;
  long n;
  if (argc == 3 && isobj(argv[0], type(DXLHandle), (void**)&h) &&
      isstr(argv[1], &s) && isint(argv[2], &n) && n >= 0 && n <= 1) {
    if (valid(h)) {
      DXLError res = uiDXLSetRenderMode(h->conn, s, n);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_reset,argc,argv)
{
  DXLHandle *h;
  if (argc == 1 && isobj(argv[0], type(DXLHandle), (void**)&h)) {
    if (valid(h)) {
      DXLError res = uiDXLResetServer(h->conn);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

static int chk(DXLHandle *h)
{
  brkflag = 0;
  while (!brkflag && valid(h) && DXLIsMessagePending(h->conn))
    DXLHandlePendingMessages(h->conn);
  return !brkflag && valid(h);
}

FUNCTION(dxl,dxl_ready,argc,argv)
{
  DXLHandle *h;
  if (argc == 1 && isobj(argv[0], type(DXLHandle), (void**)&h)) {
    chk(h);
    if (brkflag)
      return __FAIL;
    else {
      int res = valid(h);
      return res?mktrue:mkfalse;
    }
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_busy,argc,argv)
{
  DXLHandle *h;
  if (argc == 1 && isobj(argv[0], type(DXLHandle), (void**)&h)) {
    if (chk(h)) {
      int status;
      if (DXLGetExecutionStatus(h->conn, &status) == OK) {
	return status?mktrue:mkfalse;
      } else
	return __FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_wait,argc,argv)
{
  DXLHandle *h;
  if (argc == 1 && isobj(argv[0], type(DXLHandle), (void**)&h)) {
    int status;
    while (chk(h) && DXLGetExecutionStatus(h->conn, &status) == OK &&
	   status) {
      release_lock();
      sleep_some();
      acquire_lock();
    }
    if (brkflag)
      return __FAIL;
    else
      return mkvoid;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_check,argc,argv)
{
  DXLHandle *h;
  if (argc == 1 && isobj(argv[0], type(DXLHandle), (void**)&h)) {
    if (chk(h)) {
      int res = emptyqueue(h);
      return res?mkfalse:mktrue;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_input,argc,argv)
{
  DXLHandle *h;
  char *s, *t;
  if (argc == 3 && isobj(argv[0], type(DXLHandle), (void**)&h) &&
      isstr(argv[1], &s) && isstr(argv[2], &t)) {
    if (valid(h)) {
      DXLError res = DXLSetValue(h->conn, s, t);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_output,argc,argv)
{
  DXLHandle *h;
  char *s;
  if (argc == 2 && isobj(argv[0], type(DXLHandle), (void**)&h) &&
      isstr(argv[1], &s)) {
    if (valid(h)) {
      DXLError res = DXLSetValueHandler(h->conn, s, val_handler, h);
      return (res == OK)?mkvoid:__FAIL;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(dxl,dxl_read,argc,argv)
{
  DXLHandle *h;
  if (argc == 1 && isobj(argv[0], type(DXLHandle), (void**)&h)) {
    while (chk(h) && emptyqueue(h)) {
      release_lock();
      sleep_some();
      acquire_lock();
    }
    if (emptyqueue(h))
      return __FAIL;
    else if (!head_s(h) && !head_t(h)) {
      dequeue(h);
      return __ERROR;
    } else {
      expr x;
      if (head_s(h))
	x = mktuplel(2, mkstr(head_s(h)), mkstr(head_t(h)));
      else
	x = mkapp(mksym(sym(dxl_error)), mkstr(head_t(h)));
      dequeue(h);
      if (x)
	return x;
      else
	return __ERROR;
    }
  } else
    return __FAIL;
}

INIT(dxl)
{
  int_handler = syssignal(SIGINT, break_handler);
  term_handler = syssignal(SIGTERM, break_handler);
#ifdef SIGHUP
  hup_handler = syssignal(SIGHUP, break_handler);
#endif
}


syntax highlighted by Code2HTML, v. 0.9.1