/**
 * \file firmware/modelmayhem.c
 * \brief Hardware specific part for IBM Model M keyboard, modified to be the
 * ultimate geek keyboard. :-)
 * \author Ronald Schaten <ronald@schatenseite.de>
 * \version $Id$
 *
 * License: GNU GPL v2 (see License.txt)
 */

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <string.h>
#include <stdio.h>

#include "keycodes.h"
#include "tools.h"
#include "modelinterface.h"

/* ----------------------- hardware I/O abstraction ------------------------ */

#define PORTCOLUMNS PORTB  ///< port on which we read the state of the columns
#define PINCOLUMNS  PINB   ///< port on which we read the state of the columns
#define DDRCOLUMNS  DDRB   ///< port on which we read the state of the columns
#define PORTROWS1   PORTA  ///< first port connected to the matrix rows
#define PINROWS1    PINA   ///< first port connected to the matrix rows
#define DDRROWS1    DDRA   ///< first port connected to the matrix rows
#define PORTROWS2   PORTC  ///< second port connected to the matrix rows
#define PINROWS2    PINC   ///< second port connected to the matrix rows
#define DDRROWS2    DDRC   ///< second port connected to the matrix rows

#define PORTLEDS    PORTD  ///< port on which the LEDs are connected
#define PINLEDS     PIND   ///< port on which the LEDs are connected
#define DDRLEDS     DDRD   ///< port on which the LEDs are connected
#define LEDCOMP     PIND1  ///< address of the compose LED
#define LEDNUM      PIND3  ///< address of the num-lock LED
#define LEDCAPS     PIND6  ///< address of the caps-lock LED
#define LEDSCROLL   PIND7  ///< address of the scroll-lock LED

uint8_t curmatrix[16];  ///< contains current state of the keyboard
uint8_t oldmatrix[16];  ///< contains old state of the keyboard
uint8_t ghostmatrix[16]; ///< contains pressed keys that belong to a ghost-key situation
uint8_t backlight = 0;

void hardwareInit(void) {
    // column-port is input
    PORTCOLUMNS = 0xff;
    DDRCOLUMNS  = 0x00;

    // row-ports are output
    PORTROWS1   = 0xff;
    DDRROWS1    = 0x00;
    PORTROWS2   = 0xff;
    DDRROWS2    = 0x00;

    // port D contains USB (D0, D2),
    //                 LEDs (D4, D5, D6)
    //             and Jumpers (D1, D3, D7),
    // so we call it PORTD instead of PORTJUMPERS or PORTLEDS
    PORTD       = 0xfa; // 1111 1010: no pull-ups on USB- and LED-lines
    DDRD        = 0xff; // 1111 1111: USB reset condition, LED-lines off
                        // USB Reset by device only required on Watchdog Reset
    _delay_us(11);      // delay >10ms for USB reset
    DDRD        = 0xfa; // 1111 1010: remove USB reset condition

    // configure timer 0 for a rate of 12M/(1024 * 256) = 45.78Hz (~22ms)
    TCCR0 = 5;          // timer 0 prescaler: 1024

    // blink, to indicate power-on
    PORTLEDS &= ~((1 << LEDNUM) | (1 << LEDCAPS) | (1 << LEDSCROLL) | (1 << LEDCOMP));
    _delay_ms(50);
    PORTLEDS |= ((1 << LEDNUM) | (1 << LEDCAPS) | (1 << LEDSCROLL) | (1 << LEDCOMP));

    // initialize timer for pulse width modulation on backlight LED ports
    // WGM13=1, WGM12=1, WGM11=1, WGM10=0 -> PWM mode 14
    // COM1A1=1, COM1B1=1 -> OC1A and OC1B are set at the bottom and cleared on
    //                       when the counter matches OCR1A or OCR1B
    // CS10=1 -> prescaler 1
    TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11);
    TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);

    // the pwm counter counts from 0 to this value
    ICR1 = 0xffff;

    // both LEDs are switched off on startup
    OCR1A = 0xffff; // red
    OCR1B = 0xffff; // green
}

/**
 * Print the current state of the keyboard in a readable form. This function
 * is used for debug-purposes only.
 */
void printMatrix(void) {
    for (uint8_t i = 0; i <= 15; i++) {
        char buffer[10];
        /*
        sprintf(buffer, "%d%d%d%d%d%d%d%d.",
                (curmatrix[i] & (1 << 0) ? 1 : 0),
                (curmatrix[i] & (1 << 1) ? 1 : 0),
                (curmatrix[i] & (1 << 2) ? 1 : 0),
                (curmatrix[i] & (1 << 3) ? 1 : 0),
                (curmatrix[i] & (1 << 4) ? 1 : 0),
                (curmatrix[i] & (1 << 5) ? 1 : 0),
                (curmatrix[i] & (1 << 6) ? 1 : 0),
                (curmatrix[i] & (1 << 7) ? 1 : 0));
                */
        sprintf(buffer, "%2x", curmatrix[i]);
        sendString(buffer);
        if (i == 7) {
            sendString(":");
        } else {
            sendString(".");
        }
    }
    sendString("---");
}

void toggle(void) {
    // switch backlight on or off
    if (backlight == 0) {
        backlight = 1;
    } else {
        backlight = 0;
        OCR1A = 0xffff;
        OCR1B = 0xffff;
    }
}

void setSpeed(uint8_t speed) {
    if (backlight == 1) {
        OCR1A = 0xffff - ((256 - speed) * (256 - speed) - 1);
        OCR1B = 0xffff - ((speed + 1) * (speed + 1) - 1);
    }
}

void setLeds(uint8_t LEDstate) {
    if (LEDstate & LED_NUM) { // light up num lock
        PORTLEDS &= ~(1 << LEDNUM);
    } else {
        PORTLEDS |= (1 << LEDNUM);
    }
    if (LEDstate & LED_CAPS) { // light up caps lock
        PORTLEDS &= ~(1 << LEDCAPS);
    } else {
        PORTLEDS |= (1 << LEDCAPS);
    }
    if (LEDstate & LED_SCROLL) { // light up scroll lock
        PORTLEDS &= ~(1 << LEDSCROLL);
    } else {
        PORTLEDS |= (1 << LEDSCROLL);
    }
    if (LEDstate & LED_COMPOSE) { // light up compose
        PORTLEDS &= ~(1 << LEDCOMP);
    } else {
        PORTLEDS |= (1 << LEDCOMP);
    }
}

/**
 * The keymatrix-array contains positions of keys in the matrix. Here you can
 * see which row is connected to which column when a key is pressed. This array
 * probably has to be modified if this firmware is ported to a different
 * keyboard.
 * \sa modmatrix
 */
const uint8_t PROGMEM keymatrix[16][8] = {
  // 0              1             2                  3                4               5              6               7
    {KEY_Reserved,  KEY_Reserved, KEY_Reserved,      KEY_Reserved,    KEY_Reserved,   KEY_Reserved,  KEY_Reserved,   KEY_Reserved   }, //  0
    {KEY_Reserved,  KEY_Reserved, KEY_Reserved,      KEY_Reserved,    KEY_Reserved,   KEY_Reserved,  KEY_Reserved,   KEY_Reserved   }, //  1
    {KEY_ESCAPE,    KEY_Tab,      KEY_grave,         KEY_1,           KEY_Q,          KEY_A,         KEY_Z,          KEY_Reserved   }, //  2
    {KEY_Euro,      KEY_capslock, KEY_F1,            KEY_2,           KEY_W,          KEY_S,         KEY_X,          KEY_Reserved   }, //  3
    {KEY_F4,        KEY_F3,       KEY_F2,            KEY_3,           KEY_E,          KEY_D,         KEY_C,          KEY_Reserved   }, //  4
    {KEY_G,         KEY_T,        KEY_5,             KEY_4,           KEY_R,          KEY_F,         KEY_V,          KEY_B          }, //  5
    {KEY_F5,        KEY_DELETE,   KEY_F9,            KEY_F10,         KEY_Reserved,   KEY_Reserved,  KEY_Return,     KEY_Spacebar   }, //  6
    {KEY_H,         KEY_Y,        KEY_6,             KEY_7,           KEY_U,          KEY_J,         KEY_M,          KEY_N          }, //  7
    {KEY_F6,        KEY_rbracket, KEY_equals,        KEY_8,           KEY_I,          KEY_K,         KEY_comma,      KEY_Reserved   }, //  8
    {KEY_Reserved,  KEY_F7,       KEY_F8,            KEY_9,           KEY_O,          KEY_L,         KEY_dot,        KEY_Reserved   }, //  9
    {KEY_apostroph, KEY_lbracket, KEY_minus,         KEY_0,           KEY_P,          KEY_semicolon, KEY_hash,       KEY_slash      }, // 10
    {KEY_Reserved,  KEY_KP4,      KEY_DeleteForward, KEY_F11,         KEY_KP7,        KEY_KP1,       KEY_NumLock,    KEY_DownArrow  }, // 11
    {KEY_KP0,       KEY_KP5,      KEY_Insert,        KEY_F12,         KEY_KP8,        KEY_KP2,       KEY_KPslash,    KEY_RightArrow }, // 12
    {KEY_KPcomma,   KEY_KP6,      KEY_PageUp,        KEY_PageDown,    KEY_KP9,        KEY_KP3,       KEY_KPasterisk, KEY_KPminus    }, // 13
    {KEY_UpArrow,   KEY_Reserved, KEY_Home,          KEY_End,         KEY_KPplus,     KEY_KPenter,   KEY_Pause,      KEY_LeftArrow  }, // 14
    {KEY_Reserved,  KEY_Reserved, KEY_Reserved,      KEY_PrintScreen, KEY_ScrollLock, KEY_Reserved,  KEY_Reserved,   KEY_Reserved   }, // 15
};

/**
 * The modmatrix-array contains positions of the modifier-keys in the matrix.
 * It is built in the same way as the keymatrix-array.
 * \sa keymatrix
 */
const uint8_t PROGMEM modmatrix[16][8] = { // contains positions of modifiers in the matrix
  // 0             1               2                 3         4         5         6                  7
    {MOD_NONE,     MOD_NONE,       MOD_CONTROL_LEFT, MOD_NONE, MOD_NONE, MOD_NONE, MOD_CONTROL_RIGHT, MOD_NONE     }, //  0
    {MOD_NONE,     MOD_SHIFT_LEFT, MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_SHIFT_RIGHT,   MOD_NONE     }, //  1
    {MOD_NONE,     MOD_NONE,       MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE,          MOD_NONE     }, //  2
    {MOD_NONE,     MOD_NONE,       MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE,          MOD_NONE     }, //  3
    {MOD_NONE,     MOD_NONE,       MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE,          MOD_NONE     }, //  4
    {MOD_NONE,     MOD_NONE,       MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE,          MOD_NONE     }, //  5
    {MOD_NONE,     MOD_NONE,       MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE,          MOD_NONE     }, //  6
    {MOD_NONE,     MOD_NONE,       MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE,          MOD_NONE     }, //  7
    {MOD_NONE,     MOD_NONE,       MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE,          MOD_NONE     }, //  8
    {MOD_NONE,     MOD_NONE,       MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE,          MOD_NONE     }, //  9
    {MOD_NONE,     MOD_NONE,       MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE,          MOD_NONE     }, // 10
    {MOD_NONE,     MOD_NONE,       MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE,          MOD_NONE     }, // 11
    {MOD_NONE,     MOD_NONE,       MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE,          MOD_NONE     }, // 12
    {MOD_NONE,     MOD_NONE,       MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE,          MOD_NONE     }, // 13
    {MOD_NONE,     MOD_NONE,       MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE,          MOD_NONE     }, // 14
    {MOD_ALT_LEFT, MOD_NONE,       MOD_NONE,         MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE,          MOD_ALT_RIGHT}, // 15
};

/**
 * Checks if more than one bit in data is set.
 * \param data value to check
 * \return true if more than one bit is set
 */
static uint8_t bitcount2(uint16_t data) {
    data &= (data - 1);
    return data != 0;
}

/**
 * check if reportBuffer contains the key
 * \param buffer buffer to check
 * \param key key to search
 * \return 1 if buffer contains key, 0 otherwise
 */
static uint8_t bufferContains(uint8_t* buffer, uint8_t key) {
    for (uint8_t i = 2; i < sizeof(buffer); i++) {
        if (buffer[i] == key) {
            return 1;
        }
    }
    return 0;
}

/**
 * Scan and debounce keypresses. This is the main worker function for normal
 * keyboard operation, the code contains lot of comments. Basically, it first
 * scans the keyboard state. If a change is detected, it initializes a counter
 * that is decreased each time this function is called. If the counter reaches
 * 1, that means that the same scan result has been scanned ten times in a row,
 * so we can be pretty sure that the keys are in a certain state (as in: not
 * bouncing). Then, the codes for keys and modifiers are searched from the two
 * arrays, the USB-message to send the state is prepared. The return value of
 * this function indicates if the message has to be sent.
 * \return flag to indicate whether something has changed
 */
uint8_t scankeys(uint8_t* reportBuffer, uint8_t* oldReportBuffer, uint8_t sizeOfReportBuffer) {
    static uint8_t debounce = 5;
    uint8_t retval = 0;
    for (uint8_t row = 0; row <= 15; row++) {
        if (row <= 7) {
            DDRROWS1  = (1 << row);
            PORTROWS1 = ~(1 << row);
            DDRROWS2  = 0x00;
            PORTROWS2 = 0xff;
        } else {
            DDRROWS1  = 0x00;
            PORTROWS1 = 0xff;
            // (15 - row) looks a bit weird, you would expect (row - 8) here.
            // This is because pins on PORTC are ordered in the other direction
            // than on PORTA. With (15 - row), we have the bytes in the
            // resulting matrix matching the pins of the keyboard connector.
            DDRROWS2  = (1 << (15 - row));
            PORTROWS2 = ~(1 << (15 - row));
        }
        _delay_us(30);
        uint8_t data = ~PINCOLUMNS;
        // check if we have to prevent ghost-keys
        uint16_t rows= (PINROWS1 << 8) | PINROWS2;
        if (bitcount2(~rows) && bitcount2(data)) {
            // ghost-key situation detected
            ghostmatrix[row] = data;
        } else {
            ghostmatrix[row] = 0x00;
        }
        if (data != curmatrix[row]) {
            // if a change was detected
            debounce = 10; // activate debounce counter
            curmatrix[row] = data; // and store the result
        }
    }
    if (debounce) {
        // Count down, but avoid underflow
        debounce--;
    }
    if (debounce == 1) {
        /*
        if (memcmp(oldmatrix, curmatrix, sizeof(curmatrix)) != 0) {
            printMatrix();
            memcpy(oldmatrix, curmatrix, sizeof(curmatrix));
        }
        */
        // debounce counter expired, create report
        uint8_t reportIndex = 2; // reportBuffer[0] contains modifiers
        memset(reportBuffer, 0, sizeOfReportBuffer); // clear report buffer
        for (uint8_t row = 0; row <= 15; row++) { // process all rows for key-codes
            uint8_t data = curmatrix[row]; // restore buffer
            if (data != 0xff) { // anything on this row? - optimization
                for (uint8_t col = 0; col <= 7; col++) { // check every bit on this row
                    uint8_t key, modifier, isghostkey;
                    if (data & (1 << col)) {
                        key = pgm_read_byte(&keymatrix[row][col]);
                        modifier = pgm_read_byte(&modmatrix[row][col]);
                        isghostkey = (ghostmatrix[row] & (1 << col)) != 0;
                    } else {
                        key = KEY_Reserved;
                        modifier = MOD_NONE;
                        isghostkey = 0x00;
                    }
                    if (key != KEY_Reserved) { // keycode should be added to report
                        if (reportIndex >= sizeOfReportBuffer) { // too many keycodes
                            if (!retval & 0x02) { // Only fill buffer once
                                memset(reportBuffer+2, KEY_ErrorRollOver, sizeOfReportBuffer-2);
                                retval |= 0x02; // continue decoding to get modifiers
                            }
                        } else {
                            if (isghostkey) {
                                // we're in a ghost-key situation
                                if (bufferContains(oldReportBuffer, key)) {
                                    // this key has been pressed before, so we still send it
                                    reportBuffer[reportIndex] = key; // set next available entry
                                    reportIndex++;
                                }
                            } else {
                                reportBuffer[reportIndex] = key; // set next available entry
                                reportIndex++;
                            }
                        }
                    }
                    if (modifier != MOD_NONE) { // modifier should be added to report
                        reportBuffer[0] |= modifier;
                    }
                }
            }
        }
        retval |= 0x01; // must have been a change at some point, since debounce is done
    }
    return retval;
}

