// xview-based control center for xcqcam.C and xscan.C.  See xqcctl-tk.C for
// the Tcl/Tk port of this code.
//
// by: Patrick Reynolds <patrickr@virginia.edu>

#include <xview/xview.h>
#include <xview/panel.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

#include "xqcctl.h"

void adjust(Panel_item item, int value);
void clicked(Panel_item item);
Xv_opaque make_slider(Xv_opaque parent, const char* label, int value,
  int min, int max, char id);
Xv_opaque make_button(Xv_opaque parent, const char* label, char id);
void ctl_main(int argc, char *argv[], int iout_fd, int iin_fd,
  camera_t *cam);
void xqc_die(void);
void msg_send(char type, long value);
void trap_sig(int signal);
void print_status(camera_t *cam);
void die(int ignored) { exit(0); }
	
static int out_fd, in_fd;
static Frame frame;
static Panel_item r_slider, g_slider, b_slider, bri_slider;
static int child_pid;

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

int xqc_poll(camera_t *cam) {
  struct xqc_msg msg;
  int bytes;
  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_ADJUST:           return XQC_ADJUST;               break;
      case XQC_TAKE:             return XQC_TAKE;                 break;
      case XQC_QUIT:             return -1;                       break;

      case SEND_BRIGHT:          xv_set(bri_slider,
        PANEL_VALUE, msg.value, NULL);                            break;
      case SEND_RED:             xv_set(r_slider,
        PANEL_VALUE, msg.value, NULL);                            break;
      case SEND_GREEN:           xv_set(g_slider,
        PANEL_VALUE, msg.value, NULL);                            break;
      case SEND_BLUE:            xv_set(b_slider,
        PANEL_VALUE, msg.value, NULL);                            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)
  dpy_name = NULL;  // doesn't matter, here to prevent compiler warnings
  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);                        // child 
             exit(0);
             break;
    default: in_fd = pipe_fd[0];  out_fd = back_pipe_fd[1];  // parent
             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");
}

void ctl_main(int argc, char *argv[], int iout_fd, int iin_fd,
  camera_t *cam) {
  Panel panel;

  out_fd = iout_fd;
  in_fd = iin_fd;

  xv_init(XV_INIT_ARGC_PTR_ARGV, &argc, argv, NULL);

  frame = (Frame)xv_create(XV_NULL, FRAME,
    FRAME_LABEL, "QuickCam Controls",
    NULL);

  panel = (Panel)xv_create(frame, PANEL,
    NULL);

  make_button(panel, "Quit", XQC_QUIT);
  make_button(panel, "Adjust", XQC_ADJUST);
  make_button(panel, "Take picture", XQC_TAKE);

  xv_set(panel,
    PANEL_LAYOUT, PANEL_VERTICAL,
    NULL);

  bri_slider = make_slider(panel, "Brightness", cam->get_brightness(), 1, 254, 
    XQC_BRIGHTNESS);
  make_slider(panel, "Black level", cam->get_black_level(),
    1, 254, XQC_BLACK_LEVEL);
  make_slider(panel, "White level", cam->get_white_level(),
    1, 254, XQC_WHITE_LEVEL);

  r_slider = make_slider(panel, "Red level", cam->get_red(),
    1, 254, XQC_RED);
  g_slider = make_slider(panel, "Green level", cam->get_green(),
    1, 254, XQC_GREEN);
  b_slider = make_slider(panel, "Blue level", cam->get_blue(),
    1, 254, XQC_BLUE);

  window_fit(panel);
  window_fit(frame);

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

  // tell the parent process that the signal handler is set up, so it's
  // okay to proceed
  msg_send(XQC_STARTUP, 0);

  xv_main_loop(frame);

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

Xv_opaque make_slider(Xv_opaque parent, const char* label, int value,
  int min, int max, char id) {
  return xv_create(parent, PANEL_SLIDER,
    PANEL_LABEL_STRING, label,
    PANEL_VALUE, value,
    PANEL_MIN_VALUE, min,
    PANEL_MAX_VALUE, max,
    PANEL_NOTIFY_PROC, adjust,
    PANEL_CLIENT_DATA, id,
    NULL);
}

Xv_opaque make_button(Xv_opaque parent, const char *label, char id) {
  return xv_create(parent, PANEL_BUTTON,
    PANEL_LABEL_STRING, label,
    PANEL_NOTIFY_PROC, clicked,
    PANEL_CLIENT_DATA, id,
    NULL);
}

void adjust(Panel_item item, int value) {
  msg_send(xv_get(item, PANEL_CLIENT_DATA), value);
}

void clicked(Panel_item item) {
  char type = xv_get(item, PANEL_CLIENT_DATA);
  msg_send(type, 0);
  if (type == XQC_QUIT) xqc_die();
}

void xqc_die(void) {
  xv_destroy_safe(frame);
}

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");
    xqc_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) xqc_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");
}
