/*

                                   _JNJ`
                               .JNMH`
                              JMMF`       `;.
                            .NMM)          `MN.
                            MMM)            (MML
                           (MMM`             MMML
                     M  I  D  N  I  G  H  T    C  o  D  E
                           (NMMF             MHNH
                            NMML            .MMM
                             NMML          .NMH
                              4MMNL       .#F
                               `4HNNL_   `
                                   `"""`

                          Copyright (C) 2004-2014
       "Ian (Larry) Latter" <ian dot latter at midnightcode dot org>

    Midnight Code is a registered trademark of Ian Latter.

    This program 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, and mirrored at the Midnight Code web
    site; as at version 2 of the License only.

    This program 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
    (version 2) along with this program; if not, write to the Free
    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA, or see http://midnightcode.org/gplv2.txt

*/

/* tcxf-dce.c :: ThruConsoleXfer - Data Centre End                      */

/*
  Software Architecture

     TCXF PCE                                              TCXF DCE
        socket -> r => keyb xmt [~TKXF~] keyb rcv => w -> socket
               <- w <= scrn rcv [~TGXF~] scrn xmt <= r <-

*/
// #define ANSIONLY

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <termios.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <linux/tcp.h>
#include <signal.h>
// #include <qrencode.h>
#ifndef ANSIONLY
#include <gtk/gtk.h>
#endif




int terminate = 0;
int listener_socket_fd;

typedef struct {
  int socket_fd;
  int ttyinp_fd;
} tkxf_rcv_parms;

typedef struct {
  int socket_fd;
  int in_ascii;
} tgxf_xmt_parms;




// ---------------------------------------------------------------- TKXF RCV --

#define DATA_MODE 0
#define CONTROL_MODE 1
#define EXIT_MODE 2


unsigned int
tkxf_setup_terminal(struct termios * save_term) {
  struct termios ubuf_term;

  tcgetattr(0, save_term);
  memcpy(&ubuf_term, save_term, sizeof(struct termios));
  ubuf_term.c_lflag &= ~(ICANON | ECHO);
  ubuf_term.c_cc[VTIME] = 0;
  ubuf_term.c_cc[VMIN] = 1;
  tcsetattr(0, TCSANOW, &ubuf_term);

  return 0;
}


void
tkxf_restore_terminal(struct termios * save_term) {

  tcsetattr(0, TCSANOW, save_term);

  return;
}


// USB keyboad packet doesn't allow duplicates
//
// Proposed dedupe scheme is as follows;
//     0x10 = letter m, meaning "as per key[0]"
//     0x11 = letter n, meaning "as per key[1]"
//     0x12 = letter o, meaning "as per key[2]"
//     0x13 = letter p, meaning "as per key[3]"
//     0x14 = letter q, meaning "as per key[4]"
//     0x15 = letter r, meaning "as per key[5]"
//
// So byte data -> 0x00,0x00,0x00
//   Encoded nibbles => 0x27,0x27,0x27,0x27,0x27,0x27
//   Deduped nibbles => 0x27,0x10,0x11,0x12,0x13,0x14
//
// Encode right to left, decode left to right.
int
tkxf_redupe_key_buffer(unsigned char * key_buffer, unsigned int max_offset) {
  int reference_pos;
  int compare_pos;  
  
  for(reference_pos = 0; reference_pos <= max_offset; reference_pos++) {
    for(compare_pos = reference_pos + 1; 
      compare_pos <= max_offset; compare_pos++) {
        if(key_buffer[compare_pos] - 109 == reference_pos) {
          key_buffer[compare_pos] = key_buffer[reference_pos];
          break;
        }
    }
  }

  return 0;
}


int
tkxf_dispatch_char(int fd, unsigned int mode, unsigned char octet) {

  if(mode == DATA_MODE) {
    if(write(fd, &octet, 1) < 0) {
      fprintf(stderr, "TCXF Error: tkxf_data_byte/write failed: %s\n",
        strerror(errno));
      return -1;
    }
  }

  return 0;
}


int
tkxf_process_key_buffer(int fd, unsigned char * key_buffer,
  unsigned int max_offset, unsigned int mode) {
  unsigned char key_result[2];
  unsigned int offset;
  unsigned int key_state;

  tkxf_redupe_key_buffer(key_buffer, max_offset);

  key_state = 0;
  for(offset = 0; offset < max_offset; offset++) {
    if(key_buffer[offset] >= '0' && key_buffer[offset] <= '9') {
      key_result[key_state] = key_buffer[offset] - 48;
    } else {
      if(key_buffer[offset] >= 'a' && key_buffer[offset] <= 'f') {
        key_result[key_state] = 10 + key_buffer[offset] - 97;
      }
    }
    if(key_state)
      tkxf_dispatch_char(fd, mode, key_result[0] * 16 + key_result[1]); 
    key_state = !key_state;
  }

  return 0;
}


unsigned int
tkxf_octets_available(int fd) {
  int available_octets;

  // Win ioctlsocket(fd, FIONREAD, &available_octets)
  ioctl(fd, FIONREAD, &available_octets);

  if(available_octets < 0)
    return 0;

  return available_octets;
}


void *
tkxf_rcv(void * thread_param) {
  tkxf_rcv_parms * param;
  unsigned char user_key;
  unsigned int mode;
  unsigned int processed_octets;
  unsigned char key_buffer[6];
  unsigned int offset;
  unsigned int octets;

  param = (tkxf_rcv_parms *)thread_param;

  mode = DATA_MODE;
  offset = 0;
  processed_octets = 0;
  while(!terminate && mode != EXIT_MODE) {
    octets = tkxf_octets_available(param->ttyinp_fd);
    if(!octets) {
      usleep(50);
      continue;
    }
    if(read(param->ttyinp_fd, &user_key, 1) != 1) {
      fprintf(stderr, "TCXF Error: tkxf_rcv/read failed: %s\n",
        strerror(errno));
      mode = EXIT_MODE;
    }
    processed_octets++;
    switch(user_key) {
      // HEX data
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
      case 'a':
      case 'b':
      case 'c':
      case 'd':
      case 'e':
      case 'f':
      // Dedupe data
      case 'm':
      case 'n':
      case 'o':
      case 'p':
      case 'q':
        key_buffer[offset] = user_key;
        offset++;
        if(offset == 6) {
          tkxf_process_key_buffer(param->socket_fd, key_buffer, offset, mode);
          offset = 0;
        }
        break;
      case '.':
        if(offset) {
          fprintf(stderr, "DEBUG: Last %d bytes\n", offset);
          tkxf_process_key_buffer(param->socket_fd, key_buffer, offset, mode);
        }
        mode = EXIT_MODE;
        offset = 0;
        break;
      case ',':
        if(offset)
          tkxf_process_key_buffer(param->socket_fd, key_buffer, offset, mode);
        mode = CONTROL_MODE;
        offset = 0;
        break;
      case ' ':
        if(offset)
          tkxf_process_key_buffer(param->socket_fd, key_buffer, offset, mode);
        mode = DATA_MODE;
        offset = 0;
        break;
      default:
        offset = 0;
        break;
    }
  }

  terminate = 1;

  // return processed_octets;
  return;
}



// ---------------------------------------------------------------- TGXF XMT --

#define TGXF_DEBUG 0

#define TGXF_DISPLAY_ASCII_SIMPLE 1
#define TGXF_DISPLAY_ASCII_SIMPLE_SQUARE 2
#define TGXF_DISPLAY_ASCII_COMPRESSED 3
#define TGXF_DISPLAY_ANSI_SIMPLE 4
#define TGXF_DISPLAY_ANSI_SIMPLE_SQUARE 5

// TGXf Session Parameters (TGXf State Machine)
unsigned int tgxf_session_frame_count = 0;
unsigned int tgxf_session_debug_image_counter = 0;

// TGXf Session Parameters (TGXf Global Options)
unsigned int tgxf_session_qrcode_version = 0;
unsigned int tgxf_session_qrcode_fps = 0;
unsigned int tgxf_session_qrcode_bytes = 0;

// Global Window and Image handles
#ifndef ANSIONLY
  unsigned char * image_buffer;
  unsigned int image_buffer_size;
  unsigned int display_header = 70;
  char * xpm_data[255];
  GtkWidget *window;
  GtkWidget *aspect_frame;
  GtkWidget *image;
#endif


unsigned int
tgxf_pack_data_payload_bin(unsigned char *payload, void *data, unsigned int data_len) {

  if(!payload || !data || !data_len)
    return 0;

  memcpy(payload, data, data_len);

  return data_len;
}


unsigned char *
tgxf_build_data_frame(unsigned char * packet, unsigned int counter, void * data, unsigned int payload_size) {
  unsigned char control_byte;
  unsigned int control_bit;
  unsigned char * payload;

  // Zero Control Byte 
  control_byte = 0;

  // Data Frame, Control Bit = 0 (bit 0); 
  control_bit = 0;
  control_byte = control_byte | control_bit;

  // Control Byte has incremented counter (bits 4,3,2,1) 
  if(counter < 0 || counter > 15)
    counter = 0;
  counter = counter << 1;
  control_byte = control_byte | counter;

  // Frame consists of Control Byte and Data Payload 
  payload = packet + 1;
  *packet = (control_byte) & 0xFF;
  if(memcpy(payload, data, payload_size) != payload) {
    // fprintf(stderr,
      // "TCXF Error: tgxf_build_data_frame/failed to build frame: %s\n",
      // strerror(errno));
    // What else can we do here?
    return packet;
  }

  return packet;
}


#ifndef ANSIONLY
gboolean
tgxf_window_resize(GtkWidget *widget, GdkEvent *event, GtkWidget *window) {
  GdkPixbuf *o_pixbuf;
  GdkPixbuf *n_pixbuf;

  o_pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(widget));
  if(o_pixbuf == NULL) {
    return 1;
  }
  n_pixbuf = gdk_pixbuf_scale_simple(o_pixbuf,
    widget->allocation.width, widget->allocation.height,
    GDK_INTERP_TILES);
  if(n_pixbuf == NULL) {
    return 1;
  }
  gtk_image_set_from_pixbuf(GTK_IMAGE(widget), n_pixbuf);
  gdk_pixbuf_unref(o_pixbuf);

  return 0;
}


void
tgxf_window_close(GtkWidget *widget, GdkEvent *event, gpointer data) {

  terminate = 1;
  gtk_main_quit();
}
#endif


unsigned char *
tgxf_render_frame(unsigned char * data, int length, int qr_ver, int qr_ecc, int qr_scale, int qr_margin, int in_ascii) {
  unsigned int w;
  unsigned int h;
  unsigned int imgW;
  unsigned int imgH;
  unsigned int x;
  unsigned int y;
  unsigned int double_x;
  const char padding[5] = "    ";
  QRcode *code;
  unsigned char * bit_p;
#ifndef ANSIONLY
  unsigned char * blit_p;
  GdkPixbuf *o_pixbuf;
  GdkPixbuf *n_pixbuf;
#endif

  // Generate QRCode as an array of 1 and 0 values, from 8bit data 
  code = QRcode_encodeData(length, data, qr_ver, qr_ecc);

  // Define image dimensions as 1:1 to QRCode size 
  w = code->width;
  h = w;
  imgW = w + (2 * qr_scale * qr_margin);
  imgH = h + (2 * qr_scale * qr_margin);

  if(in_ascii) {
  // ASCII Output
  // TODO: Run length encoding (group horizontal white/black spaces)
    double_x = 1;               // 1 row + 2 col per bit
    printf("\033[0;30;47m ");
    printf("\033[2J");     // Clear screen
    printf("\033[0;0H");   // Home top left
    printf("\033[0;30;47m\n");
    for(y=0; y<h; y++) {
      printf("\033[0;30;47m%s", padding);
      for(x=0; x<w; x++) {
        bit_p = code->data+(y*w)+x;
        if(*bit_p & 0x1) {
          printf("\033[0;37;0m ");
          if(double_x)
            printf(" ");
          } else {
            printf("\033[0;30;47m ");
            if(double_x)
             printf(" ");
        }
      }
      printf("\033[0;30;47m%s", padding);
      printf("\n");
    }
    printf("\033[0;30;47m\n");

#ifndef ANSIONLY
  } else {
  // GRAPHIC Output

    // Mark pixels in GDK image per QRCode array 
    // Two pixel border (top)
    for(y=0; y<2; y++) {
      for(x=0; x<w+4; x++) {
        blit_p = image_buffer+display_header+(y*(w+5))+x;
        *blit_p = '0';
      }
      // Terminal string
      blit_p++;
      *blit_p = 0;
    }
    for(y=0; y<h; y++) {
      // Two pixel border (left)
      for(x=0; x<2; x++) {
        blit_p = image_buffer+display_header+((y+2)*(w+5))+x;
        *blit_p = '0';
      }
      for(x=0; x<w; x++) {
        bit_p = code->data+(y*w)+x;
        blit_p = image_buffer+display_header+((y+2)*(w+5))+x+2;
        if(*bit_p & 0x1) {
          *blit_p = '1';
        } else {
          *blit_p = '0';
        }
      }
      // Two pixel border (right)
      for(x=w+2; x<w+4; x++) {
        blit_p = image_buffer+display_header+((y+2)*(w+5))+x;
        *blit_p = '0';
      }
      // Terminal string
      blit_p++;
      *blit_p = 0;
    }
    // Two pixel border (bottom)
    for(y=h+2; y<h+4; y++) {
      for(x=0; x<w+4; x++) {
        blit_p = image_buffer+display_header+(y*(w+5))+x;
        *blit_p = '0';
      }
      // Terminal string
      blit_p++;
      *blit_p = 0;
    }

    gdk_threads_enter();
    o_pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(image));
    n_pixbuf = gdk_pixbuf_new_from_xpm_data((const char **)xpm_data);
    gtk_image_set_from_pixbuf(GTK_IMAGE(image), n_pixbuf);
    gtk_widget_queue_draw(image);
    if(o_pixbuf != NULL)
      gdk_pixbuf_unref(o_pixbuf);
    gdk_threads_leave();

#endif
  }

  if(code != NULL)
    QRcode_free(code);

  return data;
}


unsigned int
tgxf_octets_available(int fd) {
  int available_octets;

  // Win ioctlsocket(fd, FIONREAD, &available_octets)
  ioctl(fd, FIONREAD, &available_octets);

  if(available_octets < 0)
    return 0;

  return available_octets;
}


void *
tgxf_xmt(void * thread_param) {
  tgxf_xmt_parms * param;
  unsigned int frame_rounding_correction;
  unsigned int frame_bytes_version[64];
  unsigned int read_bytes;
  unsigned int recv_bytes;
  int mode_ecc_level;
  int mode_pixel_size;
  int mode_margin_size;
  unsigned int duration;
  unsigned int frame_number;
  unsigned char * tgxf_packet;
  unsigned char * raw_data;
  unsigned int octets;
#ifndef ANSIONLY
  GdkColor background_color;
  unsigned int display_pixels_version[64];
  unsigned char * image_ptr;
  unsigned int y;
#endif
  int flags;

  param = (tgxf_xmt_parms *)thread_param;

  // Static tables
  frame_rounding_correction = 4;        // Allow for variable ECC encoding
  frame_bytes_version[1] = 14 - frame_rounding_correction;
  frame_bytes_version[2] = 26 - frame_rounding_correction;
  frame_bytes_version[8] = 152 - frame_rounding_correction;
  frame_bytes_version[15] = 412 - frame_rounding_correction;
#ifndef ANSIONLY
  display_pixels_version[1] = 21;       //  1 (21x21)
  display_pixels_version[2] = 25;       //  2 (25x25)
  display_pixels_version[8] = 49;       //  8 (49x49)
  display_pixels_version[15] = 77;      // 15 (77x77)
#endif

  // Customisable parameters
  mode_ecc_level = QR_ECLEVEL_M;        // _L, _M, _Q, _H
  mode_pixel_size = 3;                  // Size according to your display
  mode_margin_size = 1;

  // Calculations based on custom parameters
  tgxf_session_qrcode_bytes = frame_bytes_version[tgxf_session_qrcode_version];
  read_bytes = tgxf_session_qrcode_bytes - 1;  // Subtract the Control Byte   
  duration = 100 / tgxf_session_qrcode_fps;

  // Initialisation
  tgxf_session_frame_count = 0;
  if((tgxf_packet = (unsigned char *)malloc(tgxf_session_qrcode_bytes))
    == NULL) {
    fprintf(stderr,
      "TCXF Error: tgxf_xmit/unable to allocate frame buffer: %s\n",
      strerror(errno));
    terminate = 1;
    return;
  }
  if((raw_data = (unsigned char *)malloc(read_bytes)) == NULL) {
    fprintf(stderr,
      "TCXF Error: tgxf_xmit/unable to allocate raw data buffer: %s\n",
      strerror(errno));
    free(tgxf_packet);
    terminate = 1;
    return;
  }
#ifndef ANSIONLY
  if(!param->in_ascii) {
    image_buffer_size =
      display_header + 
      ((2 + display_pixels_version[tgxf_session_qrcode_version] + 2) *
      (2 + display_pixels_version[tgxf_session_qrcode_version] + 2 + 1));
    if((image_buffer = (unsigned char *)malloc(image_buffer_size)) == NULL) {
      fprintf(stderr,
        "TCXF Error: tgxf_xmit/unable to allocate image buffer: %s\n",
        strerror(errno));
      free(raw_data);
      free(tgxf_packet);
      terminate = 1;
      return;
    }
    memset(image_buffer, 0, image_buffer_size);
    // image_ptr = image_buffer;
    // xpm_data[0] = image_ptr;
    xpm_data[0] = image_buffer;
    // sprintf(image_ptr, "%02d %02d 3 1",            // 9 + 1
    snprintf(xpm_data[0], 10, "%02d %02d 3 1",     // 9 + 1
      display_pixels_version[tgxf_session_qrcode_version] + 4,
      display_pixels_version[tgxf_session_qrcode_version] + 4);
    // image_ptr += 10;
    // sprintf(image_ptr, "       c None");           // 13 + 1
    xpm_data[1] = xpm_data[0] + 10;
    snprintf(xpm_data[1], 14, "       c None");    // 13 + 1
    // image_ptr += 14;
    xpm_data[2] = xpm_data[1] + 14;
    snprintf(xpm_data[2], 23, "1      c #000000000000");  // 22 + 1
    // sprintf(image_ptr, "1      c #000000000000");  // 22 + 1
    // image_ptr += 23;
    xpm_data[3] = xpm_data[2] + 23;
    snprintf(xpm_data[3], 23, "0      c #FFFFFFFFFFFF");  // 22 + 1
    // sprintf(image_ptr, "0      c #FFFFFFFFFFFF");  // 22 + 1
    xpm_data[4] = xpm_data[3] + 23;
    for(y=1; y<(display_pixels_version[tgxf_session_qrcode_version]+4); y++) {
      xpm_data[y+4] = xpm_data[y+3] +
        2 + display_pixels_version[tgxf_session_qrcode_version] + 2 + 1;
    }
  }
#endif

  // Text Setup
  if(param->in_ascii) {
    printf("\033[0;30;47m");            // White on Black
    printf("\033[2J");                  // Clear screen
    printf("\033[0;0H");
    printf("\n");
  }
#ifndef ANSIONLY
  if(!param->in_ascii) {                // Set window background white
    background_color.red = 0xffff;
    background_color.green = 0xffff;
    background_color.blue = 0xffff;
    gdk_threads_enter();
    gtk_widget_modify_bg(window, GTK_STATE_NORMAL, &background_color);
    gdk_threads_leave();
  }
#endif
  sleep(2);                             // Let camera contrast settle

  // Read from socket, render TGXF frames
  while(!terminate) {
    // Data to be read on network socket?
    octets = tgxf_octets_available(param->socket_fd);
    if(!octets) {
      usleep(50);
      continue;
    }
    // If so, acquire it
    recv_bytes = read_bytes;
    if(octets < recv_bytes)
      recv_bytes = octets;
    flags = 0;
    if(recv(param->socket_fd, raw_data, recv_bytes, flags) > 0) {
      memset(tgxf_packet, 0, tgxf_session_qrcode_bytes);
      // Generate Data Frame
      tgxf_build_data_frame(tgxf_packet, tgxf_session_frame_count,
        raw_data, recv_bytes);
      // Display Data Frame
      tgxf_render_frame(tgxf_packet, tgxf_session_qrcode_bytes,
        tgxf_session_qrcode_version,
        mode_ecc_level, mode_pixel_size, mode_margin_size,
        param->in_ascii);
      // Update Data Counter
      tgxf_session_frame_count++;
      if(tgxf_session_frame_count > 15)
        tgxf_session_frame_count = 0;
      // Wait for display refresh period
      usleep(duration * 10000);
    }
  }

  // Text Cleanup
  if(param->in_ascii) {
    sleep(1);
    printf("\033[0;37;0m");              // Black on White
    printf("\033[2J");
    printf("\033[0;0H");
  }

#ifndef ANSIONLY
  if(!param->in_ascii) {
    free(image_buffer);
  }
#endif
  free(raw_data);
  free(tgxf_packet);

  return;
}



// -------------------------------------------------------------------- MAIN --

void
logo(int tagline) {
  char midnightcode_logo_ascii[26][22] = {
    "               _JNJ`", "                    ",
    "           .JNMH`   ", "                    ",
    "          JMMF`     ", "  `;.               ",
    "        .NMM)       ", "   `MN.             ",
    "        MMM)        ", "    (MML            ",
    "       (MMM`        ", "     MMML           ",
    " M  I  D  N  I  G  H", "  T    C  o  D  E   ",
    "       (NMMF        ", "     MHNH           ",
    "        NMML        ", "    .MMM            ",
    "         NMML       ", "   .NMH             ",
    "          4MMNL     ", "  .#F               ",
    "           `4HNNL_  ", " `                  ",
    "               `\"\"\"`", "                    "
  };
  int idx;

  fprintf(stderr, "\n");
  for(idx=0; idx<26; idx++) {
    if((float)((float)idx/(float)2) == (int)(idx/2)) {
      fprintf(stderr, "                    \033[01;31m");
    } else {
      fprintf(stderr, "\033[31m");
    }
    if(idx == 12 || idx == 13)
      fprintf(stderr, "\033[01;37m");
    fprintf(stderr, "%s\033[0m", midnightcode_logo_ascii[idx]);
    if((float)((float)idx/(float)2) != (int)(idx/2))
      fprintf(stderr, "\n");
  }
  fprintf(stderr, "\n");
  if(tagline)
    fprintf(stderr,
      "              ThruConsoleXfer - Data Centre End - Reference Code"
      "\n\n");

  return;
}


void
usage(int help) {

  logo(1);
  if(help) {
    fprintf(stderr,
      "  Usage: tcxf-pce [OPTIONS]...\n"
      "    -t PORT, --tcp=PORT          TCP port to listen on (8442)\n"
#ifndef ANSIONLY
      "    -g, --graphic                Encode to Graphic/GTK output (ANSI)\n"
#endif
      "    -v #, --version={1,2,8,15}   QRcode symbol version to use (8)\n"
      "    -f #, --fps={1,2,5,8,10}     Symbols displayed per second (5)\n"
      "\n"
    );
  }
}


void
signal_handler(int sig) {

  terminate = 1;
  if(listener_socket_fd)
    shutdown(listener_socket_fd, SHUT_RDWR);

  return;
}


int
main(int argc, char **argv) {
  static const struct option options[] = {
    {"help" ,     no_argument, NULL, 'h'},
    {"tcp",       required_argument, NULL, 't'},
#ifndef ANSIONLY
    {"graphic",   no_argument, NULL, 'g'},
#endif
    {"version",   required_argument, NULL, 'v'},
    {"fps",       required_argument, NULL, 'f'},
    {NULL, 0, NULL, 0}
  };
#ifndef ANSIONLY
  static char *optstring = "ht:gv:f:";
#else
  static char *optstring = "ht:v:f:";
#endif
  struct sigaction sa;
  pthread_t pid_r = { 0 };
  pthread_t pid_w = { 0 };
  tkxf_rcv_parms tkxf_rcv_param;
  tgxf_xmt_parms tgxf_xmt_param;
  int opt;
  int val;
  struct termios save_term;
  unsigned int tcp_port;
  struct sockaddr_in listener_addr;
  struct sockaddr_in client_addr;
  socklen_t client_len;
  int socket_fd;
  int ttyinp_fd;
  int in_ascii;
  char nulbuf;

  // Establish defaults
  tcp_port = 8442; // T*
  in_ascii = 1;
  tgxf_session_qrcode_version = 8;
  tgxf_session_qrcode_fps = 5;


  // Parse command line
  while((opt = getopt_long(argc, argv, optstring, options, NULL)) != -1) {
    switch(opt) {
      case 'h':
        usage(1);
        exit(0);
        break;
      case 't':
        tcp_port = atoi(optarg);
        break;
#ifndef ANSIONLY
      case 'g':
        in_ascii = 0;
        break;
#endif
      case 'v':                         // 1 (21x21), 2 (25x25),
        val = atoi(optarg);             // 8 (49x49), 15 (77x77)
        if(val != 1 && val != 2 && val != 8 && val != 15)
          val = 8;
        tgxf_session_qrcode_version = val;
        break;
      case 'f':                         // 1, 2, 5, 8, 10
        val = atoi(optarg);
        if(val != 1 && val != 2 && val != 5 && val != 8 && val != 10)
          val = 5;
        tgxf_session_qrcode_fps = val;
        break;
      default:
        fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]);
        exit(-1);
        break;
    }
  }
 
  // Purdee
  logo(1);

  // Validate user input or Default it
  fprintf(stderr, "TCXF: Using terminal device [stdin]\n");
  if(in_ascii) {
    fprintf(stderr, "TCXF: Using terminal device [stdout]\n");
  } else {
    fprintf(stderr, "TCXF: Using GTK window canvas [~]\n");
  }
  fprintf(stderr,
    "TCXF: Rendering parameters (TGXF) FPS=%d, Version=%d\n",
    tgxf_session_qrcode_fps, tgxf_session_qrcode_version);
  fprintf(stderr, "\n");

  // Install signal handler
  memset(&sa, 0, sizeof(sa));
  sa.sa_handler = &signal_handler;
  if(sigaction(SIGPIPE, &sa, NULL) == -1) {
    fprintf(stderr,
      "TCXF Error: sighandler setup failed: %s\n", strerror(errno));
    return -1;
  }
  if(sigaction(SIGINT, &sa, NULL) == -1) {
    fprintf(stderr,
      "TCXF Error: sighandler setup failed: %s\n", strerror(errno));
    return -1;
  }

  // Initialize hardware
  ttyinp_fd = tkxf_setup_terminal(&save_term);
  if(ttyinp_fd < 0) {
    fprintf(stderr, "TCXF Error: Serial setup failed\n");
    return -1;
  }
#ifndef ANSIONLY
  if(!in_ascii) {

    // Threads setup
    g_thread_init(NULL);
    gdk_threads_init();
    gdk_threads_enter();

    // Setup GTK and define Window    
    gtk_init(&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), 
      "ThruConsoleXfer - Data Centre End - Reference Code");
    gtk_container_set_border_width(GTK_CONTAINER(window), 10);
    gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
    gtk_signal_connect(GTK_OBJECT(window), "delete-event",
      GTK_SIGNAL_FUNC(tgxf_window_close), NULL);

    aspect_frame = gtk_aspect_frame_new("TGXf", 0.5, 0.5, 1, FALSE);
    gtk_container_add(GTK_CONTAINER(window), aspect_frame);
    gtk_widget_show(aspect_frame);

    // Add the canvas
    image = gtk_image_new();
    gtk_widget_set_usize(image, 100, 100);
    gtk_container_add(GTK_CONTAINER(aspect_frame), image);
    gtk_widget_show(image);
    g_signal_connect(image, "expose-event",
      G_CALLBACK(tgxf_window_resize), (gpointer)window);

    gtk_widget_show(window);
  }
#endif

  // Setup networking
  listener_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
  if(listener_socket_fd < 0) {
    fprintf(stderr, "TCXF Error: socket failed: %s\n", strerror(errno));
    tkxf_restore_terminal(&save_term);
    return -1;
  }

  // Configure listener socket
  bzero((char *)&listener_addr, sizeof(listener_addr));
  listener_addr.sin_family = AF_INET;
  listener_addr.sin_addr.s_addr = INADDR_ANY;
  listener_addr.sin_port = htons(tcp_port);
  if(bind(listener_socket_fd, (struct sockaddr *)&listener_addr,
    sizeof(listener_addr)) < 0) {
    fprintf(stderr, "TCXF Error: bind failed: %s\n", strerror(errno));
    tkxf_restore_terminal(&save_term);
    return -1;
  }

  // Await one connection
  listen(listener_socket_fd, 1);

  // Accpet the client
  client_len = sizeof(client_addr);
  socket_fd = accept(listener_socket_fd,
    (struct sockaddr *)&client_addr, &client_len);
  if(socket_fd < 0) {
    fprintf(stderr, "TCXF Error: connection failed: %s\n", strerror(errno));
    tkxf_restore_terminal(&save_term);
    return -1;
  }
  close(listener_socket_fd);
  listener_socket_fd = 0;

  // Configure threads
  tkxf_rcv_param.socket_fd = socket_fd;
  tkxf_rcv_param.ttyinp_fd = ttyinp_fd;
  tgxf_xmt_param.socket_fd = socket_fd;
  tgxf_xmt_param.in_ascii = in_ascii;

  // Launch threads
  pthread_create(&pid_r, NULL, &tgxf_xmt, (void *)&tgxf_xmt_param);
  pthread_create(&pid_w, NULL, &tkxf_rcv, (void *)&tkxf_rcv_param);

#ifndef ANSIONLY
  // Start the Window
  if(!in_ascii) {
    gtk_main();  // blocking
    gdk_threads_leave();
    terminate = 1;
  }
#endif

  // Wait around for exit
  while(!terminate) {
    if(recv(socket_fd, &nulbuf, sizeof(nulbuf), MSG_PEEK|MSG_DONTWAIT) == 0) {
      fprintf(stderr, "TCXF: client connection closed\n");
      terminate = 1;
    }
    usleep(250);
  }

  // Shutdown and bail
  pthread_join(pid_r, NULL);
  pthread_join(pid_w, NULL);
  tkxf_restore_terminal(&save_term);
  close(socket_fd);

  return 0;
}
