#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <sys/stat.h>
#ifdef _WIN32
# include <io.h>
# include <fcntl.h>
#endif
#include <math.h>
#ifndef ANSIONLY
#include <gd.h>
#include <png.h>
#endif


// The following CRC code comes from RFC1952, Section 8
//   http://tools.ietf.org/html/rfc1952#section-8

/* Table of CRCs of all 8-bit messages. */
unsigned long crc_table[256];

/* Flag: has the table been computed? Initially false. */
int crc_table_computed = 0;

/* Make the table for a fast CRC. */
void make_crc_table(void)
{
  unsigned long c;
  int n, k;
  for (n = 0; n < 256; n++) {
    c = (unsigned long) n;
    for (k = 0; k < 8; k++) {
      if (c & 1) {
        c = 0xedb88320L ^ (c >> 1);
      } else {
        c = c >> 1;
      }
    }
    crc_table[n] = c;
  }
  crc_table_computed = 1;
}


/*
    Update a running crc with the bytes buf[0..len-1] and return
  the updated crc. The crc should be initialized to zero. Pre- and
  post-conditioning (one's complement) is performed within this
  function so it shouldn't be done by the caller. Usage example:

  unsigned long crc = 0L;

  while (read_buffer(buffer, length) != EOF) {
    crc = update_crc(crc, buffer, length);
  }
  if (crc != original_crc) error();
*/
unsigned long update_crc(unsigned long crc,
  unsigned char *buf, int len)
{
  unsigned long c = crc ^ 0xffffffffL;
  int n;

  if (!crc_table_computed)
    make_crc_table();
  for (n = 0; n < len; n++) {
    c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
  }
  return c ^ 0xffffffffL;
}

/* Return the CRC of the bytes buf[0..len-1]. */
unsigned long crc(unsigned char *buf, int len)
{
  return update_crc(0L, buf, len);
}




/*

                                   _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

*/

#define TGXF_DEBUG 0

#define TGXF_CONTROL_TYPE_START 1
#define TGXF_CONTROL_TYPE_STOP 2
#define TGXF_CONTROL_TYPE_STATUS 3
#define TGXF_CONTROL_SUBTYPE_START_FILENAME 1
#define TGXF_CONTROL_SUBTYPE_START_FILESIZE 2
#define TGXF_CONTROL_SUBTYPE_START_QRCODE_VERSION 3
#define TGXF_CONTROL_SUBTYPE_START_QRCODE_FPS 4
#define TGXF_CONTROL_SUBTYPE_START_QRCODE_BYTES 5
#define TGXF_CONTROL_SUBTYPE_STOP_PAUSE 1
#define TGXF_CONTROL_SUBTYPE_STOP_COMPLETE 2
#define TGXF_CONTROL_SUBTYPE_STOP_CANCEL 3
#define TGXF_CONTROL_SUBTYPE_STATUS_SINCE 1
#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_total_frames = 0;
unsigned int tgxf_session_first_data_frame = 0;
unsigned int tgxf_session_good_start = 0;
unsigned int tgxf_session_last_data_counter = 15;
unsigned int tgxf_session_frame_count = 0;
unsigned int tgxf_session_error_count = 0;
unsigned long tgxf_session_recent_crc32 = 0L;
unsigned long tgxf_session_total_crc32 = 0L;
unsigned int tgxf_session_max_errors = 5;
unsigned int tgxf_session_errors = 0;
unsigned int tgxf_session_aborted = 0;
unsigned int tgxf_session_total_bytes = 0;
unsigned int tgxf_session_complete = 0;

unsigned int tgxf_session_debug_image_counter = 0;
unsigned int tgxf_session_debug_image_type = 0; // PNG, JPG=1

// TGXf Session Parameters (TGXf Global Options)
unsigned char tgxf_session_file_name[20];
char * tgxf_session_filepath;
unsigned int tgxf_session_file_size = 0;
unsigned int tgxf_session_qrcode_version = 0;
unsigned int tgxf_session_qrcode_fps = 0;
unsigned int tgxf_session_qrcode_bytes = 0;


char * get_basename_for_file(char *filepath) {
  char *basename;

  basename = filepath;
  while(*filepath) {
    if(*filepath++ == '/')
      basename = filepath;
  }

  return basename;
}


unsigned int get_size_for_file(char *filepath) {
  struct stat st;
  unsigned int size;

  stat(filepath, &st);
  size = st.st_size;

  return size;
}


unsigned long get_crc_for_file(char *filepath) {
  unsigned char data_buffer[1024];
  unsigned char *data_buffer_p;
  int data_buffer_size = 1024;
  unsigned int read_len;
  unsigned long crc;
  FILE *fp;

  crc = 0L;
  if(!filepath)
    return crc;

  if((fp = fopen((const char *)filepath, "rb")) == NULL)
    return crc;

  data_buffer_p = data_buffer;
  while((read_len = fread(data_buffer, 1, data_buffer_size, fp)) > 0) {
    crc = update_crc(crc, data_buffer_p, read_len);
  }

  return crc;
}


unsigned int get_control_bit(unsigned char *data) {
  unsigned char control_byte;
  unsigned int control_bit;

  control_byte = *data;
  control_bit = control_byte & 1;

  return control_bit;
}


unsigned int get_control_type(unsigned char *data) {
  unsigned char control_byte;
  unsigned int control_type;

  control_byte = *data;
  control_type = (control_byte & 14) >> 1;

  return control_type;
}


unsigned int pack_control_payload_16bit_number(unsigned char *payload, void *data) {
  unsigned int value;
  unsigned int *value_p;

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

  value_p = (unsigned int *)data;
  value = *value_p;
  *payload = (value >> 8) & 0xFF;
  payload++;
  *payload = (value >> 0) & 0xFF;

  return value;
}


unsigned long pack_control_payload_32bit_number(unsigned char *payload, void *data) {
  unsigned long value;
  unsigned long *value_p;

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

  value_p = (unsigned long *)data;
  value = *value_p;
  *payload = (value >> 24) & 0xFF;
  payload++;
  *payload = (value >> 16) & 0xFF;
  payload++;
  *payload = (value >> 8) & 0xFF;
  payload++;
  *payload = (value >> 0) & 0xFF;

  return value;
}


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

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

  memset(payload, 0, 9);
  if(data_len > 9)
    data_len = 9;
  memcpy(payload, data, data_len);

  return data_len;
}


unsigned int 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 * build_control_frame(unsigned char * packet, unsigned int control_type, unsigned int control_subtype, void * data, unsigned int length) {
  unsigned char control_byte;
  unsigned int control_bit;
  unsigned char * payload;

  // Zero Control Byte 
  control_byte = 0;

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

  // Control Byte has Control Type (bits 3,2,1) 
  if(control_type < 0 || control_type >= 7)
    control_type = 0;
  control_type = control_type << 1;
  control_byte = control_byte | control_type;

  // Control Byte has Sub-Control Type (bits 7,6,5,4) 
  if(control_subtype < 0 || control_subtype >= 15)
    control_subtype = 0;
  control_subtype = control_subtype << 4;
  control_byte = control_byte | control_subtype;

  payload = packet + 1;

  switch((control_type & 14) >> 1) {
    case TGXF_CONTROL_TYPE_START:
      switch((control_subtype & 240) >> 4) {
       case TGXF_CONTROL_SUBTYPE_START_FILENAME:                    // FILENAME 
          pack_control_payload_string(payload, data, length);
          break;
       case TGXF_CONTROL_SUBTYPE_START_FILESIZE:                    // FILESIZE 
          pack_control_payload_16bit_number(payload, data);
          break;
       case TGXF_CONTROL_SUBTYPE_START_QRCODE_VERSION:              // QRCODE_VERSION 
          pack_control_payload_16bit_number(payload, data);
          break;
       case TGXF_CONTROL_SUBTYPE_START_QRCODE_FPS:                  // QRCODE_FPS 
          pack_control_payload_16bit_number(payload, data);
          break;
       case TGXF_CONTROL_SUBTYPE_START_QRCODE_BYTES:                // QRCODE_BYTES 
          pack_control_payload_16bit_number(payload, data);
          break;
      }
      break;

    case TGXF_CONTROL_TYPE_STOP:
      switch((control_subtype & 240) >> 4) {
       case TGXF_CONTROL_SUBTYPE_STOP_PAUSE:                        // PAUSE
          break;
       case TGXF_CONTROL_SUBTYPE_STOP_COMPLETE:                     // COMPLETE
          pack_control_payload_32bit_number(payload, data);
          break;
       case TGXF_CONTROL_SUBTYPE_STOP_CANCEL:                       // CANCEL
          pack_control_payload_string(payload, data, length);
          break;
      }
      break;

    case TGXF_CONTROL_TYPE_STATUS:
      switch((control_subtype & 240) >> 4) {
       case TGXF_CONTROL_SUBTYPE_STATUS_SINCE:                      // SINCE
          pack_control_payload_32bit_number(payload, data);
          break;
      }
      break;

    default:
      break;
  }

  // Frame consists of Control Byte and Control Payload 
  *packet = (control_byte) & 0xFF;

  return packet;
}


unsigned char * 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, "ERROR(TGXf): failed to build frame\n");
    // What else can we do here?
    return packet;
  }

  return packet;
}


unsigned char * 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;
  unsigned char * bit_p;
  const char padding[5] = "    ";
  QRcode *code;
#ifndef ANSIONLY
  gdImagePtr base_image;
  gdImagePtr target_image;
  int col[2];
  char debug_filename[32];
  FILE *fp;
#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
    double_x = 1;
    switch(TGXF_DISPLAY_ANSI_SIMPLE) {

      case TGXF_DISPLAY_ASCII_COMPRESSED:
/*
          // 0.5 row + 1 col per bit
          $target_image .= "\n";
          for($y=0; $y<$h; $y+=2) {
            $target_image .= $padding;
            for($x=0; $x<$w; $x++) {
              if($frame[$y][$x] == '1') {
                if(!isset($frame[$y+1])) {
                  // Block characters in the IBM850 Character Encoding
                  $target_image .= chr(223);       // 1,0
                } else {
                  if($frame[$y + 1][$x] == '1') {  // 1,1
                    $target_image .= chr(219);
                  } else {                         // 1,0
                    $target_image .= chr(223);
                  }
                }
              } else {
                if(!isset($frame[$y+1])) {
                  $target_image .= " ";            // 0,0
                } else {
                  if($frame[$y + 1][$x] == '1') {  // 0,1
                    $target_image .= chr(220);
                  } else {                         // 0,0
                    $target_image .= " ";
                  }
                }
              }
            }
            $target_image .= "  \n";
          }
*/
        break;

      case TGXF_DISPLAY_ANSI_SIMPLE_SQUARE:
        // 1 row + 2 col per bit
        double_x = 1;
      case TGXF_DISPLAY_ANSI_SIMPLE:
        // 1 row + 1 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");
        break;

      case TGXF_DISPLAY_ASCII_SIMPLE_SQUARE:
        // 1 row + 2 col per bit
        double_x = 1;
      case TGXF_DISPLAY_ASCII_SIMPLE:
        // 1 row + 1 col per bit
        printf("\n");
        printf("\n");
        for(y=0; y<h; y++) {
          printf("%s", padding);
          for(x=0; x<w; x++) {
            bit_p = code->data+(y*w)+x;
            if(*bit_p & 0x1) {
              // Block character in the IBM850 Character Encoding = chr(219);
              printf("#");
              if(double_x)
                printf("#");
            } else {
              printf(" ");
              if(double_x)
                printf(" ");
            }
          }
          printf("\n");
        }
        printf("\n");
        printf("\n");
        break;

    }

#ifndef ANSIONLY
  } else {
  // GRAPHIC Output

    // Create GD image resource 
    base_image = gdImageCreate(imgW, imgH);
    col[0] = gdImageColorAllocate(base_image,255,255,255);   // BG, white 
    // Colors for demonstration only
    if(get_control_bit(data)) {
      switch(get_control_type(data)) {
        case TGXF_CONTROL_TYPE_START:
          col[1] = gdImageColorAllocate(base_image,0,64,0);  // FG, START = Green
          break;
        case TGXF_CONTROL_TYPE_STATUS:
          col[1] = gdImageColorAllocate(base_image,0,0,64);  // FG, STATUS = Blue
          break;
        case TGXF_CONTROL_TYPE_STOP:
          col[1] = gdImageColorAllocate(base_image,64,0,0);  // FG, STOP = Red
          break;
      }
    } else {
      col[1] = gdImageColorAllocate(base_image,0,0,0);       // FG, black for Data 
    }
    gdImageFill(base_image, 0, 0, col[0]);

    // Mark pixels in GD image per QRCode array 
    for(y=0; y<h; y++) {
      for(x=0; x<w; x++) {
        bit_p = code->data+(y*w)+x;
        if(*bit_p & 0x1) {
          gdImageSetPixel(base_image,
            x + (qr_scale * qr_margin),
            y + (qr_scale * qr_margin),
            col[1]);
        }
      }
    }

    // Resize the GD image according to the requested image dimensions 
    target_image = gdImageCreate(imgW * qr_scale, imgH * qr_scale);
    gdImageCopyResized(
        target_image,
        base_image,
        0, 0, 0, 0,
        imgW * qr_scale, imgH * qr_scale, imgW, imgH
    );
    gdImageDestroy(base_image);


    // Return/Write the GD image resource of the final (full scale) image 
    if(tgxf_session_debug_image_type) {
      snprintf(debug_filename, 32, "TGXf-output-%04d.jpg", tgxf_session_debug_image_counter);
    } else {
      snprintf(debug_filename, 32, "TGXf-output-%04d.png", tgxf_session_debug_image_counter);
    }
    fp = fopen(debug_filename, "wb");
    if(tgxf_session_debug_image_type) {
      gdImageJpeg(target_image, fp, 100);
    } else {
      gdImagePng(target_image, fp);
    }
    fclose(fp);
    gdImageDestroy(target_image);
    tgxf_session_debug_image_counter++;
#endif
  }

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

  return data;
}


static void usage(int help) {
  fprintf(stderr, "ThruGlassXfer Linux Transmit Reference Code\n\n");

  if(help) {
    fprintf(stderr,
      "Usage: tgxf-transmit [OPTIONS]...\n"
      "  -h, --help     Display this help message.\n"
      "  -i FILENAME, --input=FILENAME\n"
      "                 File to encode and transmit.\n"
      "  -a, --ascii    Encode to ASCII output (default).\n"
#ifndef ANSIONLY
      "  -g, --graphic  Encode to Graphic output.\n"
      "  -p, --png      Graphic in PNG format (default=JPG).\n"
#endif
      "  -v {1,2,8,15}, --version={1,2,8,15}\n"
      "                 QRcode symbol version to use. (default=8)\n"
      "  -f {1,2,5,8,10}, --fps={1,2,5,8,10}\n"
      "                 QRcode symbols to display per second. (default=5)\n\n"
    );
  }
}


// Main Program Begins Here
int main(int argc, char **argv) {
  static const struct option options[] = {
    {"help" ,   no_argument, NULL, 'h'},
    {"ascii",   no_argument, NULL, 'a'},
#ifndef ANSIONLY
    {"graphic", no_argument, NULL, 'g'},
    {"png",     no_argument, NULL, 'p'},
#endif
    {"input",   required_argument, NULL, 'i'},
    {"version", required_argument, NULL, 'v'},
    {"fps",     required_argument, NULL, 'f'},
    {NULL, 0, NULL, 0}
  };
#ifdef ANSIONLY
  static char *optstring = "hai:v:f:";
#else
  static char *optstring = "hagpi:v:f:";
#endif
  int opt;
  int val;
  int in_ascii;
  unsigned int frame_rounding_correction;
  unsigned int frame_bytes_version[64];
  unsigned int read_bytes;
  int mode_ecc_level;
  int mode_pixel_size;
  int mode_margin_size;
  unsigned int max_blocks;
  unsigned int block_idx;
  int read_delta;
  FILE *fp;
  unsigned char * tgxf_packet;
  unsigned int duration;
  char * basename;
  unsigned char * raw_data;


  in_ascii = 1;
  tgxf_session_debug_image_type = 1;
  tgxf_session_qrcode_version = 8;
  tgxf_session_qrcode_fps = 5;
  tgxf_session_filepath = NULL;

  while((opt = getopt_long(argc, argv, optstring, options, NULL)) != -1) {
    switch(opt) {
      case 'h':
        usage(1);
        exit(0);
        break;
#ifndef ANSIONLY
      case 'g':
        in_ascii = 0;
        break;
      case 'p':
        tgxf_session_debug_image_type = 0;
        break;
#endif
      case 'a':                         // Text mode output
        in_ascii = 1;
        break;
      case 'i':
        tgxf_session_filepath = optarg;
        break;
      case 'v':                         // 1 (21x21), 2 (25x25), 8 (49x49), 15 (77x77)
        val = atoi(optarg);
        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;
    }
  }

  if(argc == 1) {
    usage(1);
    exit(0);
  }

  // Validate user input or Default it
  if(!tgxf_session_filepath ||
    (tgxf_session_qrcode_version != 1 && tgxf_session_qrcode_version != 2 &&
     tgxf_session_qrcode_version != 8 && tgxf_session_qrcode_version != 15) ||
    (tgxf_session_qrcode_fps != 1 && tgxf_session_qrcode_fps != 2 &&
     tgxf_session_qrcode_fps != 5 && tgxf_session_qrcode_fps != 8 &&
     tgxf_session_qrcode_fps != 10)
  ) {
    fprintf(stderr, "No input filepath provided, or version or FPS is incorrect.\n");
    exit(-1);
  }

  // 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;

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

  // File names
/*
  $user_output_file = "TGXf-v" . $tgxf_session_qrcode_version . "-" .
    $tgxf_session_qrcode_fps . "fps-" . $mode_pixel_size . "px.gif";
*/
  basename = get_basename_for_file(tgxf_session_filepath);

  // 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;
  tgxf_session_file_size = get_size_for_file(tgxf_session_filepath);
  tgxf_session_total_crc32 = get_crc_for_file(tgxf_session_filepath);

  // Initialisation
  tgxf_session_frame_count = 0;
  if((tgxf_packet = (unsigned char *)malloc(tgxf_session_qrcode_bytes)) == NULL) {
    fprintf(stderr, "ERROR(TGXf): unable to allocate frame buffer\n");
    return -1;
  }
  if((raw_data = (unsigned char *)malloc(read_bytes)) == NULL) {
    fprintf(stderr, "ERROR(TGXf): unable to allocate raw data buffer\n");
    return -1;
  }

  // Text Setup
  if(in_ascii) {
    printf("\033[0;30;47m");            // White on Black
    printf("\033[2J");                  // Clear screen
    printf("\033[0;0H");
    printf("\n");
    sleep(2);                           // Let camera contrast settle
  }

  // TGXf CONTROL -> START -> FILENAME
  memset(tgxf_packet, 0, tgxf_session_qrcode_bytes);
  build_control_frame(
    tgxf_packet, 
    TGXF_CONTROL_TYPE_START,
    TGXF_CONTROL_SUBTYPE_START_FILENAME,
    basename, strlen(basename));
  render_frame(tgxf_packet, tgxf_session_qrcode_bytes, tgxf_session_qrcode_version,
    mode_ecc_level, mode_pixel_size, mode_margin_size, in_ascii);
  if(in_ascii) // Remove if graphic images are displayed real-time
    usleep(duration * 10000);

  // TGXf CONTROL -> START -> FILESIZE
  memset(tgxf_packet, 0, tgxf_session_qrcode_bytes);
  build_control_frame(
    tgxf_packet, 
    TGXF_CONTROL_TYPE_START,
    TGXF_CONTROL_SUBTYPE_START_FILESIZE,
    &tgxf_session_file_size, 3);
  render_frame(tgxf_packet, tgxf_session_qrcode_bytes, tgxf_session_qrcode_version,
    mode_ecc_level, mode_pixel_size, mode_margin_size, in_ascii);
  if(in_ascii) // Remove if graphic images are displayed real-time
    usleep(duration * 10000);

  // TGXf CONTROL -> START -> QRCODE_BYTES
  memset(tgxf_packet, 0, tgxf_session_qrcode_bytes);
  build_control_frame(
    tgxf_packet, 
    TGXF_CONTROL_TYPE_START,
    TGXF_CONTROL_SUBTYPE_START_QRCODE_BYTES,
    &read_bytes, 3);
  render_frame(tgxf_packet, tgxf_session_qrcode_bytes, tgxf_session_qrcode_version,
    mode_ecc_level, mode_pixel_size, mode_margin_size, in_ascii);
  if(in_ascii) // Remove if graphic images are displayed real-time
    usleep(duration * 10000);

  // TGXf CONTROL -> START -> QRCODE_FPS
  memset(tgxf_packet, 0, tgxf_session_qrcode_bytes);
  build_control_frame(
    tgxf_packet, 
    TGXF_CONTROL_TYPE_START,
    TGXF_CONTROL_SUBTYPE_START_QRCODE_FPS,
    &tgxf_session_qrcode_fps, 3);
  render_frame(tgxf_packet, tgxf_session_qrcode_bytes, tgxf_session_qrcode_version,
    mode_ecc_level, mode_pixel_size, mode_margin_size, in_ascii);
  if(in_ascii) // Remove if graphic images are displayed real-time
    usleep(duration * 10000);

  if((fp = fopen(tgxf_session_filepath, "rb")) != NULL) {
    // TGXf DATA (one data frame per loop iteration)
    max_blocks = ceil((float)tgxf_session_file_size / (float)read_bytes);
    for(block_idx = 0; block_idx < max_blocks; block_idx++) {
      // last block an odd size?
      read_delta = tgxf_session_file_size - (block_idx * read_bytes);
      if(read_delta > 0 &&
        read_delta < read_bytes &&
        block_idx == (max_blocks - 1)) {
          read_bytes = read_delta;
      }
      if(fread(raw_data, read_bytes, 1, fp)) {
        memset(tgxf_packet, 0, tgxf_session_qrcode_bytes);
        build_data_frame(tgxf_packet, tgxf_session_frame_count, raw_data, read_bytes);
        render_frame(tgxf_packet, tgxf_session_qrcode_bytes, tgxf_session_qrcode_version,
          mode_ecc_level, mode_pixel_size, mode_margin_size, in_ascii);
        if(in_ascii) // Remove if graphic images are displayed real-time
          usleep(duration * 10000);
      }
      tgxf_session_frame_count++;
      if(tgxf_session_frame_count > 15)
        tgxf_session_frame_count = 0;
    }
    fclose(fp);
  }

  // TGXf CONTROL -> STOP -> COMPLETE
  memset(tgxf_packet, 0, tgxf_session_qrcode_bytes);
  build_control_frame(
    tgxf_packet, 
    TGXF_CONTROL_TYPE_STOP,
    TGXF_CONTROL_SUBTYPE_STOP_COMPLETE,
    &tgxf_session_total_crc32, 5);
  render_frame(tgxf_packet, tgxf_session_qrcode_bytes, tgxf_session_qrcode_version,
    mode_ecc_level, mode_pixel_size, mode_margin_size, in_ascii);
  if(in_ascii) // Remove if graphic images are displayed real-time
    usleep(duration * 10000);

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

  free(raw_data);
  free(tgxf_packet);

  return 0;
}

