/***********************************************************************
 *
 *                        254/395 V A L V E S
 *                     HID Keyboard for ZDSimulator
 *
 *             with the Arduino Leonardo (Arduino Pro Micro)
 *
 **********************************************************************/

#include <Keyboard.h>
/*--------------------------------------------------------
  "THE BEER-WARE LICENSE" (Revision 42):
  Alex Kostyuk wrote this code. As long as you retain this
  notice you can do whatever you want with this stuff.
  If we meet some day, and you think this stuff is worth it,
  you can buy me a beer in return.
----------------------------------------------------------*/

/*
Dirty hack is needed in file
  <installation_folder>\Arduino\libraries\Keyboard\src\Keyboard.cpp

//////////////////////////////
//   Before
//////////////////////////////
#define SHIFT 0x80
const uint8_t _asciimap[128] =
{
  0x00,             // NUL
  0x00,             // SOH
  0x00,             // STX
  0x00,             // ETX
  0x00,             // EOT
  0x00,             // ENQ
  0x00,             // ACK
  0x00,             // BEL

//////////////////////////////
//   After
//////////////////////////////
#define SHIFT 0x80
const uint8_t _asciimap[128] =
{
  0x00,             // NUL
  0x65,             //    (VK_APPS)          0x01
  0x2D,             // -  (VK_OEM_MINUS)     0x02
  0x2E,             // +  (VK_OEM_PLUS)      0x03
  0x31,             // |  (VK_OEM_5)         0x04
  0x2A,             // BS (VK_BACK)          0x05
  0x00,             // ACK
  0x00,             // BEL

Codes taken from http://www.usb.org/developers/hidpage/Hut1_12v2.pdf
Table 12: Keyboard/Keypad Page

0x65,     //    (VK_APPS)          0x01
0x65 is a code we need to send to make the Windows to issue the VK_APPS keyboard event.
VK_APPS - event code in Windows when the code 0x65 sent by USB HID.
0x01 is a code (index) which Keyboard.press(code)/Keyboard.release(code) eats to translate in 0x65.
*/


#define KEY_APPLICATION_MENU  0x01
#define FIFO_SIZE 16

typedef struct
{
    int   push_indx;
    int   pop_indx;
    unsigned char buff[FIFO_SIZE];
} SIMPLE_FIFO;

SIMPLE_FIFO fifo;

char fifo_push(SIMPLE_FIFO *fifo, unsigned char data);
char fifo_pop(SIMPLE_FIFO *fifo, unsigned char *data);
void fifo_init(SIMPLE_FIFO *fifo);
void keyboard_state_machine(void);


void setup()
{
    bits_init();
    fifo_init(&fifo);
    Keyboard.begin();
    pinMode(13, OUTPUT);
}

void loop(void)
{
    discrete_task();
    keyboard_state_machine();
}


//-------------------------------------------
//               Discrete task
//-------------------------------------------
#define BITS_MAXIMUM   14 /*ITEMS_MAX*/
int shift = 0;

#define FLAG_INVERTED   (1<<0)
#define FLAG_ENABLED    (1<<1)

enum
{
    PUSH_BUTTON = 0,
    TOGGLE_SWITCH,
    ROTARY_SWITCH_POS,
    ENCODER_A,
    ENCODER_B,
    SHIFT
};

typedef struct
{
    int counter;
    char signal;
    int  max;
    char evcode;
    char flag;
    char mode;
    char pin;
    char key_macros[6];
} BITS_DESCRIPTION;

BITS_DESCRIPTION  bits[BITS_MAXIMUM];

#define input_init(x,y,z,m,km)   do {\
bits[x].counter = 0;\
bits[x].signal = 0;\
bits[x].max = y;\
bits[x].flag = (FLAG_ENABLED | FLAG_INVERTED);\
bits[x].pin = z;\
pinMode(z,INPUT_PULLUP);\
bits[x].mode = m;\
strcpy(bits[x].key_macros,km);\
} while(0)

void bits_init(void)
{
    int i;
    memset(bits,0,sizeof(bits));

#define DEBOUNCE_DELAY   100
    input_init(0,DEBOUNCE_DELAY,2,   ROTARY_SWITCH_POS, "\x01\x31");
    input_init(1,DEBOUNCE_DELAY,3,   ROTARY_SWITCH_POS, "\x01\x32");
    input_init(2,DEBOUNCE_DELAY,4,   ROTARY_SWITCH_POS, "\x01\x33");
    input_init(3,DEBOUNCE_DELAY,5,   ROTARY_SWITCH_POS, "\x01\x34");
    input_init(4,DEBOUNCE_DELAY,A0,  ROTARY_SWITCH_POS, "\x01\x37");
    input_init(5,DEBOUNCE_DELAY,A1,  ROTARY_SWITCH_POS, "\x01\x38");
    input_init(6,DEBOUNCE_DELAY,A2,  ROTARY_SWITCH_POS, "\x01\x39");

    input_init(7,DEBOUNCE_DELAY,6,   PUSH_BUTTON,      "[");
    input_init(8,DEBOUNCE_DELAY,7,   ROTARY_SWITCH_POS,"\x01\x30");
    input_init(9,DEBOUNCE_DELAY,8,   ROTARY_SWITCH_POS,"\x01\x02");
    input_init(10,DEBOUNCE_DELAY,9,  ROTARY_SWITCH_POS,"\x01\x03");
    input_init(11,DEBOUNCE_DELAY,10, ROTARY_SWITCH_POS,"\x01\x04");
    input_init(12,DEBOUNCE_DELAY,11, ROTARY_SWITCH_POS,"\x01\x05");
    input_init(13,DEBOUNCE_DELAY,12, ROTARY_SWITCH_POS,"\x01\x05");
}

void discrete_task( void)
{
    int bit,flag;
    int bit_state;
    int i;
    static int pos_254_prev = 0;
    static int pos_395_prev = 0;
    static char ini_flag = 0;
    unsigned char *p,inc_sym;

    if(ini_flag == 0)
    {
        for(i=0; i<7; i++)
        {
            if(digitalRead(bits[i].pin) == 0)
                pos_395_prev = i;
        }

        for(; i<14; i++)
        {
            if(digitalRead(bits[i].pin) == 0)
                pos_254_prev = i;
        }

        ini_flag = 1;
    }


    for(i=0; i<BITS_MAXIMUM; i++)
    {

        if(bits[i].flag & FLAG_ENABLED)
        {
            bit_state = digitalRead(bits[i].pin);
            flag = bits[i].flag;
            bit = (flag & FLAG_INVERTED) ^ bit_state;

            if(bit)
            {
                if(bits[i].counter < bits[i].max)
                {
                    if(++(bits[i].counter) >= bits[i].max)
                    {
                        if(bits[i].signal == 0)
                        {
                            //front __0__/--1--
                            switch(bits[i].mode)
                            {
                            case PUSH_BUTTON:

                                if(i>=0 && i<=6)
                                {
                                    pos_395_prev = i;
                                }

                                if(i>=7 && i<=13)
                                {
                                    pos_254_prev = i;
                                }


                                p = bits[i].key_macros;
                                while(*p)
                                {
                                    Keyboard.press(*p++);
                                }
                                break;

                            case ROTARY_SWITCH_POS:
                                if(i>=0 && i<=6)
                                {
                                    if(pos_395_prev < i)
                                    {
                                        inc_sym = '\'';
                                    }
                                    else if(pos_395_prev > i)
                                    {
                                        inc_sym = ';';
                                    }

                                    pos_395_prev = i;

                                    if(i == 3 || i == 4 )
                                    {
                                        fifo_push(&fifo, inc_sym);
                                        break;
                                    }
                                }

                                if(i>=7 && i<=13)
                                {
                                    if(pos_254_prev < i)
                                    {
                                        inc_sym = ']';
                                    }
                                    else if(pos_254_prev > i)
                                    {
                                        inc_sym = '[';
                                    }

                                    pos_254_prev = i;

                                    if(i == 11 )
                                    {
                                        fifo_push(&fifo, inc_sym);
                                        break;
                                    }
                                }

                                p = bits[i].key_macros;
                                while(*p)
                                {
                                    fifo_push(&fifo, *p++);
                                }

                                break;
                            }
                            bits[i].signal = 1;
                        }

                    }
                }
            }
            else
            {
                if(bits[i].counter > 0)
                {
                    if(--(bits[i].counter) <= 0)
                    {
                        if(bits[i].signal)
                        {
                            // edge  --1--\___0___
                            switch(bits[i].mode)
                            {
                            case PUSH_BUTTON:
                                Keyboard.releaseAll();
                                break;
                            }
                            bits[i].signal = 0;
                        }
                    }
                }
            }
        }
    }
}


enum
{
    KE_IDLE = 0,
    KE_COLLECT_MACROS,
    KE_SHIFT_DELAY,
    KE_KEY_DELAY
};

char is_shift_key(unsigned char key)
{
    switch(key)
    {
    case KEY_APPLICATION_MENU:

        return 1;
    }
    return 0;
}


void keyboard_state_machine(void)
{
    static char state = KE_COLLECT_MACROS;
    static int timer;
    unsigned char key;

    switch(state)
    {

    case KE_COLLECT_MACROS:
        digitalWrite(13, LOW);
        if(fifo_pop(&fifo, &key))
        {
            if(!is_shift_key(key))
            {
                timer = 750;
                state = KE_KEY_DELAY;
                digitalWrite(13, HIGH);
                Keyboard.press(key);
            }
            else
            {
                timer =350;
                state = KE_SHIFT_DELAY;
                Keyboard.press(key);
            }
        }

        break;
    case KE_SHIFT_DELAY:
        if(timer)
        {
            if(--timer == 0)
            {
                state = KE_COLLECT_MACROS;
            }
        }
        break;

    case KE_KEY_DELAY:
        if(timer)
        {
            if(--timer == 0)
            {
                Keyboard.releaseAll();
                state = KE_COLLECT_MACROS;
            }
        }
        break;
    }

}

void fifo_init(SIMPLE_FIFO *fifo)
{
    fifo->push_indx = 0;
    fifo->pop_indx = 0;
}

char fifo_push(SIMPLE_FIFO *fifo, unsigned char data)
{
    int indx;
    indx = fifo->push_indx + 1;
    indx = indx % FIFO_SIZE;

    if (indx == fifo->pop_indx)
        return 0; // full
    fifo->buff[indx] = data;
    fifo->push_indx = indx;
    return 1;
}

char fifo_pop(SIMPLE_FIFO *fifo, unsigned char *data)
{
    int indx;
    if (fifo->push_indx == fifo->pop_indx)
        return 0; // empty
    indx = fifo->pop_indx + 1;
    indx = indx % FIFO_SIZE;
    *data = fifo->buff[indx];
    fifo->pop_indx = indx;
    return 1;
}