// Tk-based remote control for xcqcam 
// Philip Blundell <Philip.Blundell@pobox.com> 24-Dec-96
//
// based on the xview-based remote by
// Patrick Reynolds <patrickr@virginia.edu>

#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include <tcl.h>
#include <tk.h>

#include "xqcctl.h"

static int out_fd, in_fd;
static int child_pid;

void ctl_main(int argc, char *argv[], int iout_fd, int iin_fd,
  camera_t *cam, char *dpy_name);
void trap_sig(int signal);
void print_status(camera_t *cam);
void msg_send(char type, long value);
void die(int ignored=0);

// for use by the signal-handler, since it can't take client data
static struct camera_t *the_cam;
static Tcl_Interp *the_interp;

void update_sliders(void)
{
  char cmd[] = "        .red set $red \n \
                        .green set $green \n \
                        .blue set $blue \n \
                        .black set $black \n \
                        .white set $white \n \
                        .bright set $bright";
  Tcl_Eval(the_interp, cmd);
}

int xqc_poll(camera_t *cam) {
  struct xqc_msg msg;
  int bytes;
  char cmd[256];
  while (1) {                      // as long as there are data, read 'em in
    switch (bytes = read(in_fd, &msg, sizeof(msg))) {
      case -1: if (errno == EWOULDBLOCK) return 0;
                 else { perror("xqc_poll: read"); return -1; }  // a real error
               break;
      case 0:  return -1;                                       // just eof
    }
    // real data. process 'em.
    switch (msg.type) {
      case XQC_STARTUP:          return XQC_STARTUP;              break;
      case XQC_BRIGHTNESS:       cam->set_brightness(msg.value);  break;
      case XQC_BLACK_LEVEL:      cam->set_black_level(msg.value); break;
      case XQC_WHITE_LEVEL:      cam->set_white_level(msg.value); break;

      case XQC_RED:              cam->set_red(msg.value);         break;
      case XQC_GREEN:            cam->set_green(msg.value);       break;
      case XQC_BLUE:             cam->set_blue(msg.value);        break;

      case XQC_STATUS:           print_status(cam);               break;
      case XQC_ADJUST:           return XQC_ADJUST;               break;
      case XQC_TAKE:             return XQC_TAKE;                 break;
      case XQC_QUIT:             return -1;                       break;

      case SEND_BRIGHT:   
	sprintf(cmd, "set bright %d", msg.value);
	Tcl_Eval(the_interp, cmd);
     	update_sliders();
	break;
      case SEND_RED:
	sprintf(cmd, "set red %d", msg.value);
	Tcl_Eval(the_interp, cmd);
	update_sliders();
	break;
	break;
      case SEND_GREEN:
	sprintf(cmd, "set green %d", msg.value);
	Tcl_Eval(the_interp, cmd);
	update_sliders();
	break;
	break;
      case SEND_BLUE:
	sprintf(cmd, "set blue %d", msg.value);
	Tcl_Eval(the_interp, cmd);
	update_sliders();
	break;
	break;

      default: fprintf(stderr, "xqc_poll: Unknown adjustment type: %c\n",
                 msg.type);
               return -1;
    }
  }                                // keep getting data until they're gone
}

int xqc_fork(int argc, char *argv[], camera_t *cam, char *dpy_name) {
// child process = XV remote control
// parent process = quickcam engine + window
// in pipes, fd[0] is for reading, fd[1] is for writing
// pipe_fd:        child -------> parent
//          (remote control events/adjustments)
// back_pipe_fd:  parent -------> child
//          (auto-adjust results + quit messages)
  int pipe_fd[2], back_pipe_fd[2];
  if (pipe(pipe_fd) == -1) { perror("xqc_fork: pipe"); return -1; }
  if (pipe(back_pipe_fd) == -1) { perror("xqc_fork: pipe"); return -1; }
  if (fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK) == -1) {
    perror("xqc_fork: fcntl");
    return -1;
  }
  if (fcntl(back_pipe_fd[0], F_SETFL, O_NONBLOCK) == -1) {
    perror("xqc_fork: fcntl");
    return -1;
  }
  switch (child_pid = fork()) {
    case -1: perror("xqc_fork: fork");
             return -1;
             break;
    case 0:  ctl_main(argc, argv, pipe_fd[1],
               back_pipe_fd[0], cam, dpy_name);              // child 
             exit(0);
             break;
    default: in_fd = pipe_fd[0];  out_fd = back_pipe_fd[1];  // parent
      // Set up a signal handler so that when the child dies, we go too
             signal(SIGCHLD, die);
             return 0;
  }
}

void xqc_adj(camera_t *cam) {
  msg_send(SEND_BRIGHT, cam->get_brightness());
  msg_send(SEND_RED, cam->get_red());
  msg_send(SEND_GREEN, cam->get_green());
  msg_send(SEND_BLUE, cam->get_blue());
  kill(child_pid, SIGUSR1);
}

void xqc_quit(void) {
  msg_send(XQC_QUIT, 0);
  if (kill(child_pid, SIGUSR1) == -1) perror("xqc_quit: kill");
  if (close(out_fd) == -1) perror("xqc_quit: close");
  if (close(in_fd) == -1) perror("xqc_quit: close");
}

int SetCamValCmd(ClientData clientdata, Tcl_Interp *interp,
		 int argc, char *argv[])
{
  char type;
  long value = atoi(argv[2]);
  if (!strcmp(argv[1], "red")) type = XQC_RED;
  else if (!strcmp(argv[1], "blue")) type = XQC_BLUE;
  else if (!strcmp(argv[1], "green")) type = XQC_GREEN;
  else if (!strcmp(argv[1], "bright")) type = XQC_BRIGHTNESS;
  else if (!strcmp(argv[1], "black")) type = XQC_BLACK_LEVEL;
  else if (!strcmp(argv[1], "white")) type = XQC_WHITE_LEVEL;
  msg_send(type, value);
  return TCL_OK;
}

int CamButtonCmd(ClientData clientdata, Tcl_Interp *interp,
		 int argc, char *argv[])
{
  char type;
  long value = 0;
  if (!strcmp(argv[1], "quit")) type = XQC_QUIT;
  else if (!strcmp(argv[1], "adj")) type = XQC_ADJUST;
  else if (!strcmp(argv[1], "snap")) type = XQC_TAKE;
  msg_send(type, value);
  return TCL_OK;
}

void ctl_main(int argc, char *argv[], int iout_fd, int iin_fd,
  camera_t *cam, char *dpy_name) {
  Tcl_Interp *interp;
  int code;
  char cmd[256];

  if (dpy_name)
    setenv("DISPLAY", dpy_name, 1);

  char BuildWindow[] = "wm title . \"QuickCam Controls\" \n \
                        frame .buttons \n \
                        frame .lsliders \n \
                        frame .rsliders \n \
                        frame .sliders \n \
                        button .quit -text quit -command \"CamButton quit ; exit\" \n \
                        button .adjust -text adjust -command \"CamButton adj\" \n \
                        button .snap -text snapshot -command \"CamButton snap\" \n \
                        scale .bright -label brightness -from 0 -to 254 -length 5c -orient horizontal -command \"SetCamVal bright\" \n \
                        scale .black -label black -from 0 -to 254 -length 5c -orient horizontal -command \"SetCamVal black\" \n \
                        scale .white -label white -from 0 -to 254 -length 5c -orient horizontal -command \"SetCamVal white\" \n \
                        scale .red -label red -from 0 -to 254 -length 5c -orient horizontal -command \"SetCamVal red\" \n \
                        scale .green -label green -from 0 -to 254 -length 5c -orient horizontal -command \"SetCamVal green\" \n \
                        scale .blue -label blue -from 0 -to 254 -length 5c -orient horizontal -command \"SetCamVal blue\" \n \
                        pack .buttons .sliders -side top \n \
                        pack .lsliders .rsliders -side left -in .sliders \n \
                        pack .quit .adjust .snap -side left -in .buttons \n \
                        pack .bright .black .white -side top -in .lsliders \n \
                        pack .red .green .blue -side top -in .rsliders ";
 
  out_fd = iout_fd;
  in_fd = iin_fd;

  interp = Tcl_CreateInterp();
  the_interp = interp;

  // set up the message-trapping mechanism
  the_cam = cam;
  signal(SIGUSR1, trap_sig);
  msg_send(XQC_STARTUP, 0);

  Tcl_Init(interp);
  Tk_Init(interp);
  Tcl_StaticPackage(interp, "Tk", Tk_Init, (Tcl_PackageInitProc *) NULL);

  Tcl_CreateCommand(interp, "SetCamVal", SetCamValCmd, (ClientData)NULL,
		    (Tcl_CmdDeleteProc *)NULL);

  Tcl_CreateCommand(interp, "CamButton", CamButtonCmd, (ClientData)NULL,
		    (Tcl_CmdDeleteProc *)NULL);

  sprintf(cmd, "set bright %d", cam->get_brightness());
  Tcl_Eval(interp, cmd);
  sprintf(cmd, "set black %d", cam->get_black_level());
  Tcl_Eval(interp, cmd);
  sprintf(cmd, "set white %d", cam->get_white_level());
  Tcl_Eval(interp, cmd);
  sprintf(cmd, "set red %d", cam->get_red());
  Tcl_Eval(interp, cmd);
  sprintf(cmd, "set green %d", cam->get_green());
  Tcl_Eval(interp, cmd);
  sprintf(cmd, "set blue %d", cam->get_blue());
  Tcl_Eval(interp, cmd);

  code = Tcl_Eval(interp, BuildWindow);
  if (code != TCL_OK) {
    printf("%s\n", interp->result);
    exit(1);
  }

  update_sliders();

  Tk_MainLoop();

  if (close(out_fd) == -1) perror("xqc: close");
  if (close(in_fd) == -1) perror("xqc: close");
}

void die(int ignored) {
  exit(0);
}

void msg_send(char type, long value) {
  struct xqc_msg msg;
  msg.type = type;
  msg.value = value;
  if (write(out_fd, &msg, sizeof(msg)) != sizeof(msg)) {
    perror("msg_send: write");
    die();
  }
}

void trap_sig(int sig) {
  if (sig != SIGUSR1) printf("Unknown signal %d\n", sig);
  else {
    signal(sig, trap_sig);  // keep grabbing this signal
    if (xqc_poll(the_cam) == -1) die();
  }
}

void print_status(camera_t *cam) {
  printf("brightness  = %d\n", cam->get_brightness());
  printf("black level = %d\n", cam->get_black_level());
  printf("white level = %d\n", cam->get_white_level());
  printf("red         = %d\n", cam->get_red());
  printf("green       = %d\n", cam->get_green());
  printf("blue        = %d\n", cam->get_blue());
  printf("hue         = %d\n", cam->get_hue());
  printf("saturation  = %d\n", cam->get_saturation());
  printf("top         = %d\n", cam->get_top());
  printf("left        = %d\n", cam->get_left());
  printf("width       = %d\n", cam->get_width());
  printf("height      = %d\n", cam->get_height());
  printf("port mode   = %s\n", (cam->get_port_mode() == QC_BI_DIR)?
    "bi-directional" : "uni-directional");
}
