/* 

                                   _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 

*/ 

// 
// Change the mac_address in this program accordingly
// 

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>


#define DATA_MODE 0
#define CONTROL_MODE 1

int wire_packets = 0;


// Serial source from wallyk;
//   http://stackoverflow.com/questions/6947413/how-to-open-read-and-write-from-serial-port-in-c
int
set_interface_attribs(int fd, int speed, int parity) {
  struct termios tty;

  memset (&tty, 0, sizeof tty);
  if(tcgetattr (fd, &tty) != 0) {
    printf("Error: %d from tcgetattr", errno);
    return -1;
  }

  cfsetospeed(&tty, speed);
  cfsetispeed(&tty, speed);

  tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;  // 8-bit chars
    // disable IGNBRK for mismatched speed tests; otherwise receive break
    // as \000 chars
  tty.c_iflag &= ~IGNBRK;                 // ignore break signal
  tty.c_lflag = 0;                        // no signaling chars, no echo,
                                          // no canonical processing
  tty.c_oflag = 0;                        // no remapping, no delays
  tty.c_cc[VMIN]  = 0;                    // read doesn't block
  tty.c_cc[VTIME] = 5;                    // 0.5 seconds read timeout

  tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl

  tty.c_cflag |= (CLOCAL | CREAD);        // ignore modem controls,
                                          // enable reading
  tty.c_cflag &= ~(PARENB | PARODD);      // shut off parity
  tty.c_cflag |= parity;
  tty.c_cflag &= ~CSTOPB;
  tty.c_cflag &= ~CRTSCTS;

  if(tcsetattr(fd, TCSANOW, &tty) != 0) {
    printf("Error: %d from tcsetattr", errno);
    return -1;
  }

  tcflush(fd, TCIOFLUSH);                 // flush the port

  return 0;
}


void
set_blocking (int fd, int should_block) {
  struct termios tty;

  memset(&tty, 0, sizeof tty);
  if(tcgetattr (fd, &tty) != 0) {
    printf("Error: %d from tggetattr", errno);
    return;
  }

  tty.c_cc[VMIN]  = should_block ? 1 : 0;
  tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

  if(tcsetattr (fd, TCSANOW, &tty) != 0)
    printf("Error: %d setting term attributes", errno);
}


int
setup_port(char * serial_dev) {
  int fd;

  fd = open(serial_dev, O_RDWR|O_NOCTTY|O_SYNC);
  if(fd < 0) {
    printf("Error: %d opening %s: %s", errno, serial_dev, strerror (errno));
    return -1;
  }
  set_interface_attribs(fd, B38400, 0);  // set speed to 115,200 bps, 8n1 (no parity)
  set_blocking(fd, 1);                    // set blocking

  return fd;
}


int
data_byte(int fd) {
  unsigned char octet;

  octet = 32;
  if(write(fd, &octet, 1) < 0) {
    printf("Error: failed to write octet to socket\n");
    return -1;
  }

  return 0;
}


int
stop_byte(int fd) {
  unsigned char octet;

  octet = '.';
  if(write(fd, &octet, 1) < 0) {
    printf("Error: failed to write octet to socket\n");
    return -1;
  }

  return 0;
}


// 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
dedupe_key_buffer(unsigned char * keys, unsigned int max_offset) {
  int reference_pos;
  int compare_pos;

  for(reference_pos = max_offset; reference_pos > 0; reference_pos--) {
    for(compare_pos = reference_pos - 1; 
      compare_pos >= 0; compare_pos--) {
        if(keys[compare_pos] == keys[reference_pos]) {
          keys[reference_pos] = compare_pos + 0x10;
          break;
        }
    }
  }

  return 0;
}


unsigned int
pack_byte(unsigned char * keys, unsigned int offset, unsigned char b) {
  unsigned char nibble;
  // Keyboard codes for 0-9 and a-f
  unsigned char nibbler[] = { 0x27, 0x1e, 0x1f, 0x20,
                              0x21, 0x22, 0x23, 0x24,
                              0x25, 0x26, 0x04, 0x05,
                              0x06, 0x07, 0x08, 0x09 };

  nibble = (b>>4) & 0xf;
  keys[offset] = nibbler[nibble];
  offset++;
  nibble = b & 0xf;
  keys[offset] = nibbler[nibble];
  offset++;
  
  return offset;
}


int
send_keyboard_packet(int fd, unsigned int mode, unsigned char * keys, unsigned int max_offset) {
  unsigned char data_buffer;
  unsigned char keyboard_packet[7];
  unsigned int idx;


  // Build 7 byte packet
  if(mode == DATA_MODE)
    keyboard_packet[0] = 0x2c; // space
  if(mode == CONTROL_MODE)
    keyboard_packet[0] = 0x36; // ,
  dedupe_key_buffer(keys, max_offset);
  for(idx = 0; idx <= max_offset; idx++) {
    keyboard_packet[idx + 1] = keys[idx];
  }
    
  if(write(fd, keyboard_packet, max_offset + 2) < 0) {
    printf("Error: failed to write octet to fd\n");
    return -1;
  }
  wire_packets++;

  if(wire_packets == 4) {
    data_buffer = 0;
    if(read(fd, &data_buffer, 1) != 1) {
      printf("Error: failed to read octet from fd\n");
      return -2;
    }
    if(data_buffer != 1) {
      printf("Error: non-ack data read from fd [%d]\n", data_buffer);
      return -3;
    }
    wire_packets = 0;
  }

  return 0;
}


void
close_port(int fd) {

  close(fd);

  return;
}


int
main(void) {
  char * serial_dev = "/dev/ttyACM0";
  char filepath[32] = "test.bin";

  int serial_fd;
  unsigned char data_buffer[1];
  unsigned char *data_buffer_p;
  int data_buffer_size = 1;
  unsigned int read_len;
  FILE *fp;
  unsigned int offset;
  unsigned char keys[6];

  int i = 0;

  serial_fd = setup_port(serial_dev);
  if(serial_fd < 0) {
    printf("Error: failed to establish fd\n");
    return -1;
  }

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

  sleep(2);

  data_buffer_p = data_buffer;
  offset = 0;
  while((read_len = fread(data_buffer_p, 1, data_buffer_size, fp)) > 0) {
    // have byte, pack it
    offset = pack_byte(keys, offset, data_buffer[0]);
    if(offset == 6) { // or filepos == file bytes
      // have full packet, send it.
      send_keyboard_packet(serial_fd, DATA_MODE, keys, offset - 1);
      offset = 0;
    }
  }
  if(offset) {
    printf("got %d bytes left\n", offset);
    send_keyboard_packet(serial_fd, DATA_MODE, keys, offset - 1);
  }

  close_port(serial_fd);

  return 0;
}
