/*
 * Copyright: (c) 2009 Neil Stockbridge
 * License: GNU GPL v2 (see License.txt)
 */

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/delay.h>

#include "usbdrv.h"


#define  allow_interrupts()  sei()
#define  false  0
#define  true   1
typedef  int8_t  bool;


// ------------------------------------------------------- serial communications

#define  BAUD    57600
#define  MYUBRR  ((F_CPU/16/BAUD)-1)

// possible values for UCSR0A
#define  NORMAL_TX_SPEED  0
#define  DOUBLE_TX_SPEED  1

#define  POINT_TO_POINT_MODE  0
#define  BUS_MODE             1

// possible values for UCSR0B

// possible values for UCSR0C
#define  ASYNCHRONOUS_USART  0
#define  SYNCHRONOUS_USART   1
#define  MASTER_SPI          3

#define  NO_PARITY           0
#define  EVEN_PARITY         2
#define  ODD_PARITY          3

#define  ONE_STOP_BIT        0
#define  TWO_STOP_BITS       1

#define  FIVE_DATA_BITS      0
#define  SIX_DATA_BITS       1
#define  SEVEN_DATA_BITS     2
#define  EIGHT_DATA_BITS     3
#define  NINE_DATA_BITS      7

void static configure_serial_link()
{
  // set the baud rate
  UBRR0H = MYUBRR >> 8;
  UBRR0L = MYUBRR & 0xff;

  // configure the UART for point-to-point communications at single speed
  UCSR0A = (NORMAL_TX_SPEED << U2X0) | (POINT_TO_POINT_MODE << MPCM0);

  // enable the receiver and the transmitter
  UCSR0B = (1 << RXCIE0) | (1 << RXEN0) | (1 << TXEN0) | ((EIGHT_DATA_BITS >> 2) << UCSZ02);

  // set the frame format: 8 data bits, no parity, 1 stop bit
  UCSR0C = (ASYNCHRONOUS_USART << UMSEL00) |
           (NO_PARITY << UPM00) |
           (ONE_STOP_BIT << USBS0) |
           ((EIGHT_DATA_BITS & 0x3) << UCSZ00);
}

/*
void static send_char( char c)
{
  if ('\n' == c) {
    send_char('\r');
  }

  // wait for the transmit buffer to be ready
  loop_until_bit_is_set( UCSR0A, UDRE0);

  UDR0 = c;
}

char hex[] = "0123456789abcdef";
*/

// invoked when a character has been received via the serial communications link
//
void static char_received( char c);


SIGNAL( SIG_USART_RECV)
{
  char_received( UDR0);
}


// ----------------------------------------------------------------- application

char PROGMEM  usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = { /* USB report descriptor */
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x06,                    // USAGE (Keyboard)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
    0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
    0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x08,                    //   REPORT_COUNT (8)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x25, 0x65,                    //   LOGICAL_MAXIMUM (101)
    0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
    0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)
    0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
    0xc0                           // END_COLLECTION
};
/* We use a simplifed keyboard report descriptor which does not support the
 * boot protocol. We don't allow setting status LEDs and we only allow one
 * simultaneous key press (except modifiers). We can therefore use short
 * 2 byte input reports.
 * The report descriptor has been created with usb.org's "HID Descriptor Tool"
 * which can be downloaded from http://www.usb.org/developers/hidpage/.
 * Redundant entries (such as LOGICAL_MINIMUM and USAGE_PAGE) have been omitted
 * for the second INPUT item.
 */

/* Keyboard usage values, see usb.org's HID-usage-tables document, chapter
 * 10 Keyboard/Keypad Page for more codes.
 */
#define MOD_CONTROL_LEFT    (1<<0)
#define MOD_SHIFT_LEFT      (1<<1)
#define MOD_ALT_LEFT        (1<<2)
#define MOD_GUI_LEFT        (1<<3)
#define MOD_CONTROL_RIGHT   (1<<4)
#define MOD_SHIFT_RIGHT     (1<<5)
#define MOD_ALT_RIGHT       (1<<6)
#define MOD_GUI_RIGHT       (1<<7)

typedef enum
{
  KEY_NONE,      // 0
  KEY_ERR_ROLL,  // 1
  KEY_POST_FAIL,
  KEY_ERR_UNDEF,
  KEY_A,         // 4
  KEY_B,
  KEY_C,
  KEY_D,
  KEY_E,
  KEY_F,
  KEY_G,
  KEY_H,
  KEY_I,
  KEY_J,
  KEY_K,
  KEY_L,
  KEY_M,
  KEY_N,
  KEY_O,
  KEY_P,
  KEY_Q,
  KEY_R,
  KEY_S,
  KEY_T,
  KEY_U,
  KEY_V,
  KEY_W,
  KEY_X,
  KEY_Y,
  KEY_Z,
  KEY_1,
  KEY_2,
  KEY_3,
  KEY_4,
  KEY_5,
  KEY_6,
  KEY_7,
  KEY_8,
  KEY_9,
  KEY_0,
  KEY_ENTER,
  KEY_ESCAPE,
  KEY_DELETE,
  KEY_TAB,
  KEY_SPACE,
  KEY_MINUS,
  KEY_EQUALS,
  KEY_BRACKET_LEFT,
  KEY_BRACKET_RIGHT,
  KEY_US_BACKSLASH,
  KEY_HASH,
  KEY_SEMICOLON,
  KEY_APOSTROPHE,
  KEY_GRAVE,
  KEY_COMMA,
  KEY_DOT,
  KEY_SLASH,
  KEY_CAPS_LOCK,
  KEY_F1,
  KEY_F2,
  KEY_F3,
  KEY_F4,
  KEY_F5,
  KEY_F6,
  KEY_F7,
  KEY_F8,
  KEY_F9,
  KEY_F10,
  KEY_F11,
  KEY_F12,
  KEY_PRINT_SCREEN,
  KEY_SCROLL_LOCK,
  KEY_PAUSE,
  KEY_INSERT,
  KEY_HOME,
  KEY_PAGE_UP,
  KEY_DELETE_FORWARD,
  KEY_END,
  KEY_PAGE_DOWN,
  KEY_RIGHT_ARROW,
  KEY_LEFT_ARROW,
  KEY_DOWN_ARROW,
  KEY_UP_ARROW,
  KEY_NUM_LOCK,
  KEY_PAD_SLASH,
  KEY_PAD_STAR,
  KEY_PAD_MINUS,
  KEY_PAD_PLUS,
  KEY_PAD_ENTER,
  KEY_PAD_1,
  KEY_PAD_2,
  KEY_PAD_3,
  KEY_PAD_4,
  KEY_PAD_5,
  KEY_PAD_6,
  KEY_PAD_7,
  KEY_PAD_8,
  KEY_PAD_9,
  KEY_PAD_0,
  KEY_PAD_DOT,
  KEY_BACKSLASH,
  KEY_APPLICATION,
  KEY_POWER,
  // get more codes on page 53 (chapter 10) of "HID Usage Tables"
}
UsbKeyCode;

/*
Dec Hex    Dec Hex    Dec Hex  Dec Hex  Dec Hex  Dec Hex   Dec Hex   Dec Hex  
  0 00 NUL  16 10 DLE  32 20    48 30 0  64 40 @  80 50 P   96 60 `  112 70 p
  1 01 SOH  17 11 DC1  33 21 !  49 31 1  65 41 A  81 51 Q   97 61 a  113 71 q
  2 02 STX  18 12 DC2  34 22 "  50 32 2  66 42 B  82 52 R   98 62 b  114 72 r
  3 03 ETX  19 13 DC3  35 23 #  51 33 3  67 43 C  83 53 S   99 63 c  115 73 s
  4 04 EOT  20 14 DC4  36 24 $  52 34 4  68 44 D  84 54 T  100 64 d  116 74 t
  5 05 ENQ  21 15 NAK  37 25 %  53 35 5  69 45 E  85 55 U  101 65 e  117 75 u
  6 06 ACK  22 16 SYN  38 26 &  54 36 6  70 46 F  86 56 V  102 66 f  118 76 v
  7 07 BEL  23 17 ETB  39 27 '  55 37 7  71 47 G  87 57 W  103 67 g  119 77 w
  8 08 BS   24 18 CAN  40 28 (  56 38 8  72 48 H  88 58 X  104 68 h  120 78 x
  9 09 HT   25 19 EM   41 29 )  57 39 9  73 49 I  89 59 Y  105 69 i  121 79 y
 10 0A LF   26 1A SUB  42 2A *  58 3A :  74 4A J  90 5A Z  106 6A j  122 7A z
 11 0B VT   27 1B ESC  43 2B +  59 3B ;  75 4B K  91 5B [  107 6B k  123 7B {
 12 0C FF   28 1C FS   44 2C ,  60 3C <  76 4C L  92 5C \  108 6C l  124 7C |
 13 0D CR   29 1D GS   45 2D -  61 3D =  77 4D M  93 5D ]  109 6D m  125 7D }
 14 0E SO   30 1E RS   46 2E .  62 3E >  78 4E N  94 5E ^  110 6E n  126 7E ~
 15 0F SI   31 1F US   47 2F /  63 3F ?  79 4F O  95 5F _  111 6F o  127 7F DEL
*/

#define  SHIFT  0x80
#define  CTRL   0x80

// this is a lookup table from ASCII to USB HID key code
// NOTE: these codes are for a 102 key keyboard.  Some adjustment may be
// required for a 101 key keyboard
//
uint8_t PROGMEM  keycode[128] =
{
  KEY_NONE,
  CTRL+KEY_A,     // 0x01
  CTRL+KEY_B,
  CTRL+KEY_C,
  CTRL+KEY_D,
  CTRL+KEY_E,
  CTRL+KEY_F,
  CTRL+KEY_G,
  CTRL+KEY_H,
  KEY_TAB,        // 0x09
  CTRL+KEY_J,
  CTRL+KEY_K,
  CTRL+KEY_L,
  KEY_ENTER,
  CTRL+KEY_N,
  CTRL+KEY_O,
  CTRL+KEY_P,
  CTRL+KEY_Q,
  CTRL+KEY_R,
  CTRL+KEY_S,
  CTRL+KEY_T,
  CTRL+KEY_U,
  CTRL+KEY_V,
  CTRL+KEY_W,
  CTRL+KEY_X,
  CTRL+KEY_Y,
  CTRL+KEY_Z,     // 0x1a
  KEY_NONE,       // ESC, the first part of an ANSI CSI
  KEY_ERR_UNDEF,  // 0x1c
  KEY_ERR_UNDEF,
  KEY_ERR_UNDEF,
  KEY_ERR_UNDEF,  // 0x1f
  KEY_SPACE,
  SHIFT+KEY_1,
  SHIFT+KEY_2,
  KEY_HASH,
  SHIFT+KEY_4,
  SHIFT+KEY_5,
  SHIFT+KEY_7,
  KEY_APOSTROPHE,
  SHIFT+KEY_9,
  SHIFT+KEY_0,
  SHIFT+KEY_8,
  SHIFT+KEY_EQUALS,
  KEY_COMMA,
  KEY_MINUS,
  KEY_DOT,
  KEY_SLASH,
  KEY_0,
  KEY_1,
  KEY_2,
  KEY_3,
  KEY_4,
  KEY_5,
  KEY_6,
  KEY_7,
  KEY_8,
  KEY_9,
  SHIFT+KEY_SEMICOLON,   // 0x3a
  KEY_SEMICOLON,
  SHIFT+KEY_COMMA,
  KEY_EQUALS,
  SHIFT+KEY_DOT,
  SHIFT+KEY_SLASH,
  SHIFT+KEY_APOSTROPHE,  // 0x40
  SHIFT+KEY_A,
  SHIFT+KEY_B,
  SHIFT+KEY_C,
  SHIFT+KEY_D,
  SHIFT+KEY_E,
  SHIFT+KEY_F,
  SHIFT+KEY_G,
  SHIFT+KEY_H,
  SHIFT+KEY_I,
  SHIFT+KEY_J,
  SHIFT+KEY_K,
  SHIFT+KEY_L,
  SHIFT+KEY_M,
  SHIFT+KEY_N,
  SHIFT+KEY_O,
  SHIFT+KEY_P,
  SHIFT+KEY_Q,
  SHIFT+KEY_R,
  SHIFT+KEY_S,
  SHIFT+KEY_T,
  SHIFT+KEY_U,
  SHIFT+KEY_V,
  SHIFT+KEY_W,
  SHIFT+KEY_X,
  SHIFT+KEY_Y,
  SHIFT+KEY_Z,
  KEY_BRACKET_LEFT,      // 0x5b
  KEY_BACKSLASH,
  KEY_BRACKET_RIGHT,
  SHIFT+KEY_6,
  SHIFT+KEY_MINUS,
  KEY_GRAVE,             // 0x60
  KEY_A,
  KEY_B,
  KEY_C,
  KEY_D,
  KEY_E,
  KEY_F,
  KEY_G,
  KEY_H,
  KEY_I,
  KEY_J,
  KEY_K,
  KEY_L,
  KEY_M,
  KEY_N,
  KEY_O,
  KEY_P,
  KEY_Q,
  KEY_R,
  KEY_S,
  KEY_T,
  KEY_U,
  KEY_V,
  KEY_W,
  KEY_X,
  KEY_Y,
  KEY_Z,
  SHIFT+KEY_BRACKET_LEFT, // 0x7b
  SHIFT+KEY_BACKSLASH,
  SHIFT+KEY_BRACKET_RIGHT,
  SHIFT+KEY_HASH,
  KEY_DELETE,
};


uint8_t PROGMEM  arrow_code[] =
{
  KEY_UP_ARROW,
  KEY_DOWN_ARROW,
  KEY_RIGHT_ARROW,
  KEY_LEFT_ARROW,
};

uint8_t PROGMEM  private_sequence[] =
{
  KEY_DELETE_FORWARD,
  KEY_NONE,
  KEY_PAGE_UP,
  KEY_PAGE_DOWN,
  KEY_HOME,
  KEY_END,
};


uint8_t  report[2];      // for HID reports
uint8_t  idle_rate = 1;  // repeat rate in 4 ms units for keyboards, never used for mice


void static char_received( char ascii)
{
  bool static  escaped = false;

  // if this is the first character of an ANSI CSI..
  if ( 0x1b == ascii)
  {
    escaped = true;
    return;
  }

  //send_char(hex[ ascii >> 4]);
  //send_char(hex[ ascii & 0xf]);

  uint8_t  modifiers;
  uint8_t  key;

  // if the previous character was the first character of an ANSI CSI..
  if ( escaped)
  {
    // NOTE: this EXTREMELY crude handling of ANSI escape sequences is only for
    // handling the arrow keys, paging keys and so on.  sending other escape
    // sequences may result in fireworks

    // if the second character of the ANSI Control Sequence Introducer is
    // detected..
    if ( '[' == ascii)
    {
      // leave the "escaped" state alone
      return;
    }

    // care must be taken to avoid jamming in the "escaped" state
    escaped = false;

    if ('A' <= ascii  &&  ascii <= 'D')
    {
      key = pgm_read_byte( &arrow_code[ ascii - 'A']);
    }
    else if (0x33 <= ascii  &&  ascii <= 0x38)
    {
      key = pgm_read_byte( &private_sequence[ ascii - 0x33]);
      escaped = true;  // ignore the terminating '~' of the escape
    }
    else {
      return;
    }

    modifiers = 0x00;
  }
  else {
    // ignore any non-ASCII codes between 0x80 and 0xff
    if ( 0 < ascii)
    {
      uint8_t  keys = pgm_read_byte( &keycode[ (int16_t)ascii]);
      modifiers = keys & (SHIFT|CTRL) ? ( ascii < 0x20 ? MOD_CONTROL_LEFT : MOD_SHIFT_LEFT) : 0x00;
      key = keys & 0x7f;
    }
    else {
      return;
    }
  }

  report[0] = modifiers;
  report[1] = key;
}


uint8_t usbFunctionSetup( uint8_t data[8])
{
  usbRequest_t  *request = (void *)data;

  usbMsgPtr = report;

  if ( USBRQ_TYPE_CLASS == ( request->bmRequestType & USBRQ_TYPE_MASK))  // class request type
  {
    switch ( request->bRequest)
    {
      case USBRQ_HID_GET_REPORT:  // wValue: ReportType (highbyte), ReportID (lowbyte)
        // we only have one report type, so don't look at wValue
        return sizeof( report);
        break;

      case USBRQ_HID_GET_IDLE:
        usbMsgPtr = &idle_rate;
        return 1;

      case USBRQ_HID_SET_IDLE:
        idle_rate = request->wValue.bytes[1];
        break;

      default:
        // no vendor specific requests implemented
        break;
    }
  }

  return 0;
}


int main()
{
  configure_serial_link();

  usbInit();

  usbDeviceDisconnect();  // enforce re-enumeration, do this while interrupts are disabled!
  uint8_t  i = 0;
  while(--i){             // fake USB disconnect for > 250 ms
    wdt_reset();
    _delay_ms(1);
  }
  usbDeviceConnect();

  wdt_enable( WDTO_1S);

  allow_interrupts();

  // loop forever
  while ( true)
  {
    wdt_reset();

    usbPoll();

    if (usbInterruptIsReady())
    {
      usbSetInterrupt( report, sizeof(report));

      report[1] = KEY_NONE;
    }
  }
}

// -------------------------------------------------------------- 80 column rule
