/**
 * \file firmware/main.c
 * \brief Main functions for USB-keyboard
 * \author Ronald Schaten <ronald@schatenseite.de>
 * \version $Id$
 *
 * License: GNU GPL v2 (see License.txt)
 */

/**
 * \mainpage Dulcimer
 *
 * \section sec_intro Introduction
 *
 * A computer keyboard can be a very personal utensil. Especially if it is an
 * extraordinary well built one, like for example the IBM Model M. The Model M
 * design dates back to 1984, but it still has many fans even nowadays. It came
 * with the usual keyboard connectors. First the old 5-pin one, later a PS/2
 * plug. Unfortunately is that, at least to my knowledge, they never released a
 * version with USB.
 *
 * A friend of mine knew that I already had built other USB-devices, and one of
 * them even acted as a keyboard (it isn't really a keyboard, but that's a
 * different story... ;-) ). He is a big fan of the Model M, so he asked if I
 * could put new life in one of his old keyboards, which had a broken circuit
 * inside. And this is the result...
 *
 * \subsection sec_technique Hard- and Software
 *
 * The main part of a computer keyboard circuit is the key matrix. You can
 * imagine it as a number of keys, placed on a raster of horizontal (rows) and
 * vertical (columns) wires. In the case of a Model M keyboard, we have a
 * matrix of 8x16 lines. Eight columns in 16 rows, or the other way around,
 * depending on how you look at it. Each key is connected to one column and one
 * row. If you press the key, it will connect the column and the row on it's
 * crossing of the lines.
 *
 * Connected to this matrix is a keyboard controller. That's a chip with a
 * number of I/O-lines to detect the state of the matrix, and on the other side
 * an interface that enables it to talk to the computer. Oh, and not to forget:
 * it also has three output lines to drive the LEDs for Num-, Caps- and
 * Scroll-lock.
 *
 * What I did in this project is, that I dumped the keyboard controller chip
 * and its circuit, and replaced it by an ATmega32 and my own circuit. The
 * ATmega scans the matrix for keyboard activity, controls the LEDs and talks
 * to the computer.
 *
 * For further convenience, I added a boot-loader. With that, it is possible to
 * update the keyboard's firmware without disassembling it, and without the
 * need for a dedicated programmer.
 *
 * \subsection sec_models Models
 *
 * Till now, I modified the following keyboard models:
 *  - IBM Model M: The initial project, hardware as described above. This
 *    keyboard has a 8x16-matrix, all the lines can be connected directly to
 *    the controller.
 *  - Sun Type 5: Another classic. A nice model with many keys. Some of them
 *    are placed a bit uncommon, but if you think about it the layout is great.
 *    This model was never intended to work with a PC, it has some kind of
 *    proprietary connector. The contained matrix has 13 columns and 22 rows,
 *    which is too big for a naked Mega32. So I had to make a circuit with some
 *    shift registers.
 *  - IBM Host Keyboard: Like the Model M, but with 24 function keys on the top
 *    and 10 additional keys on the left. Many keys are labeled quite strange,
 *    but I guess that's common in the host world.
 *
 * Included in the package are circuits for both models, and a firmware that
 * can be compiled separately for each one.
 *
 * \subsection sec_hardware Other hardware?
 *
 * As mentioned, the controller in this project is just connected to an
 * ordinary keyboard matrix. You can find this kind of matrix in all kinds of
 * keyboards, from key-telephones over good old hardware like the Commodore
 * C=64 or the Schneider CPC, keyboards with non-PC-connectors like -- as
 * mentioned -- those made by Sun, to modern hardware that could need a few
 * more features.
 *
 * \subsection sec_features Features
 *
 * At the moment, the keyboard should be able to do everything that the average
 * off-the-shelf-keyboard can do. But there are many features that are
 * possible, regarding the fact that the ATmega32 is absolutely bored till now.
 * You can think of 'magic keystrokes' that turn some hidden features on or
 * off, like for example:
 *  - send complete phrases on one keystroke
 *  - 'autofire' feature on keys that don't repeat normally, for example Alt+F4
 *  - change keyboard layout without reconfiguring the computer
 *  - turn bouncing keys on or off, to annoy other people using your computer
 *  - random caps lock function
 *  - use arrow keys as mouse, without having to include a special driver in
 *    the OS.
 *
 * With a little tweaking on the hardware side, there should be even more
 * possibilities:
 *  - turn the oldtimer-keyboard into a supermodern wireless bluetooth one
 *  - implement keylogger-funktionality, using for example an SD-card
 *  - include an USB-hub into the keyboard
 *
 * If you are just a little like me, it won't take you much brainstorming to
 * come up with own useful -- or even better: useless -- ideas. ;-)
 *
 * \section sec_install Building and installing
 *
 * Both, the bootloader and firmware are simply built with "make". You may need
 * to customize both makefiles to fit to your system and to the keyboard model
 * you want to run the firmware on. If you don't want to add new features, you
 * don't need to build the software yourself. You can use the hex-files
 * included in this package.
 *
 * \subsection sec_boot Bootloader
 *
 * I used the USBaspLoader from Objective Development, the same guys that wrote
 * the AVR-USB-driver: http://www.obdev.at/products/avrusb/usbasploader.html
 *
 * The reason why I chose this over some other available USB-bootloaders is,
 * that this one emulates a common ISP-programmer that is supported by avrdude.
 * In this way, the same program can be used to program the chip that is used
 * without a bootloader.
 *
 * To prepare the ATmega32, you have to connect it to your computer with the
 * ISP-programmer of your choice and modify the makefile according to that.
 * Then you enter the bootloader-directory and enter the following line:
 *
 * \code
 * make fuse && make flash && make lock
 * \endcode
 * 
 * With 'fuse' you prepare the fuse-bits of your AVR, 'flash' transfers the
 * bootloader to the device and 'lock' prevents you from overwriting the
 * bootloader. Don't fear the locking: you can always reset it with your
 * ordinary programmer. In fact, it is disabled in the moment you use your
 * ordinary programmer to reflash the device, even without any special
 * parameters. The locking only affects the bootloader behavior.
 *
 * Afterwards you can put the programmer back into the toolbox, you won't need
 * it from here on.
 *
 * When you plug in the device while holding the D-key (D is for Dulcimer)
 * pressed, the keyboard indicates that it would like to get a new firmware by
 * fading all LEDs on and off. That firmware will be flashed over the normal
 * USB-cable that the keyboard is connected with.
 *
 * \subsection sec_fw Firmware
 *
 * If you intend to recompile the firmware yourself, you will need avr-gcc and
 * avr-libc (a C-library for the AVR controller). Please read the instructions
 * at http://www.nongnu.org/avr-libc/user-manual/install_tools.html for how to
 * install the GNU toolchain (avr-gcc, assembler, linker etc.) and avr-libc.
 *
 * Once you have the GNU toolchain for AVR microcontrollers installed, you can
 * run "make" in the subdirectory "firmware".
 *
 * Afterwards -- or if you decided not to compile the firmware yourself -- you
 * can flash it to the device:
 *
 * \code
 * make program
 * \endcode
 *
 * Remember that you have to start the bootloader at first: unplug the
 * keyboard, hold the minus-key on the number-keypad pressed and replug it. If
 * the modified keyboard is the only one within reach: good luck! ;-)
 *
 * \section sec_usage Usage
 *
 * Connect the keyboard to the USB-port. All LED should flash up to indicate
 * that the device is initialized.
 *
 * Then you can use the keyboard as always. If additional features get
 * implemented, you will be able to use them in their respective ways.
 *
 * \section sec_drawbacks Drawbacks
 *
 * Let me know if you find any. :-)
 *
 * \section sec_files Files in the distribution
 *
 * - \e Readme.txt: Documentation, created from the htmldoc-directory.
 * - \e firmware: Source code of the controller firmware.
 * - \e firmware/usbdrv: USB driver -- See Readme.txt in this directory for
 *   info.
 * - \e bootloader: The USBaspLoader, properly configured for this project. I
 *   only modified the bootloaderconfig.h and the Makefile.
 * - \e USBaspLoader.2008-02-05.tar.gz: The unmodified bootloader sources, for
 *   reference.
 * - \e circuit_ibm_model_m: Circuit diagrams for the IBM Model M, in PDF and
 *   KiCAD format. KiCAD is a free schematic- and layout-tool, you can learn
 *   more about it at its homepage:
 *   http://www.lis.inpg.fr/realise_au_lis/kicad/
 * - \e circuit_sun_type_5: Circuit diagrams for the Sun Type 5 keyboard.
 * - \e License.txt: Public license for all contents of this project, except
 *   for the USB driver. Look in firmware/usbdrv/License.txt for further info.
 * - \e Changelog.txt: Logfile documenting changes in soft-, firm- and
 *   hardware.
 * - \e refman.pdf: Full documentation of the software.
 *
 * \section sec_thanks Thanks!
 *
 * I'd like to thank <b>Objective Development</b> for the possibility to use
 * their driver for my project. In fact, this project wouldn't exist without
 * the driver.
 *
 * I took great inspiration from <b>Spaceman Spiff</b>'s c64key, this software
 * is based on his ideas.
 *
 * Further credits go to <b>xleave</b>, who etched the PCB for me, and also
 * answered many stupid questions about electronics I had during the last few
 * years.
 *
 * Once again, <b>Thomas Stegemann</b> supported this project. Ghost key
 * prevention and the possibility to work on different models are his work.
 *
 * And of course I'd like to thank <b>FaUl</b> of the Chaostreff Dortmund who
 * gave me the idea for this project.
 *
 * \section sec_license About the license
 *
 * My work - all contents except for the USB driver - is licensed under the GNU
 * General Public License (GPL). A copy of the GPL is included in License.txt.
 * The driver itself is licensed under a special license by Objective
 * Development. See firmware/usbdrv/License.txt for further info.
 *
 * <b>(c) 2008 by Ronald Schaten - http://www.schatenseite.de</b>
 */

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

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

/* ------------------------------------------------------------------------- */
/* ----------------------------- USB interface ----------------------------- */
/* ------------------------------------------------------------------------- */

static uint8_t reportBuffer[8]; ///< buffer for HID reports
static uint8_t oldReportBuffer[8]; ///< buffer for the last sent HID report
static uint8_t idleRate;        ///< in 4ms units
static uint8_t protocolVer = 1; ///< 0 = boot protocol, 1 = report protocol
uint8_t expectReport = 0;       ///< flag to indicate if we expect an USB-report

/** USB report descriptor (length is defined in usbconfig.h). The report
 * descriptor has been created with usb.org's "HID Descriptor Tool" which can
 * be downloaded from http://www.usb.org/developers/hidpage/ (it's an .exe, but
 * it even runs under Wine).
 */
char PROGMEM usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = {
    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)
    0x81, 0x03,   //   INPUT (Cnst,Var,Abs)
    0x95, 0x05,   //   REPORT_COUNT (5)
    0x75, 0x01,   //   REPORT_SIZE (1)
    0x05, 0x08,   //   USAGE_PAGE (LEDs)
    0x19, 0x01,   //   USAGE_MINIMUM (Num Lock)
    0x29, 0x05,   //   USAGE_MAXIMUM (Kana)
    0x91, 0x02,   //   OUTPUT (Data,Var,Abs)
    0x95, 0x01,   //   REPORT_COUNT (1)
    0x75, 0x03,   //   REPORT_SIZE (3)
    0x91, 0x03,   //   OUTPUT (Cnst,Var,Abs)
    0x95, 0x06,   //   REPORT_COUNT (6)
    0x75, 0x08,   //   REPORT_SIZE (8)
    0x15, 0x00,   //   LOGICAL_MINIMUM (0)
    0x25, 0xff,   //   LOGICAL_MAXIMUM (255)
    0x05, 0x07,   //   USAGE_PAGE (Keyboard)
    0x19, 0x00,   //   USAGE_MINIMUM (Reserved (no event indicated))
    0x29, 0xff,   //   USAGE_MAXIMUM (255)
    0x81, 0x00,   //   INPUT (Data,Ary,Abs)
    0xc0          // END_COLLECTION
};

/**
 * This function is called whenever we receive a setup request via USB.
 * \param data[8] eight bytes of data we received
 * \return number of bytes to use, or 0xff if usbFunctionWrite() should be
 * called
 */
uint8_t usbFunctionSetup(uint8_t data[8]) {
    usbRequest_t *rq = (void *)data;
    usbMsgPtr = reportBuffer;
    if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
        // class request type
        if (rq->bRequest == USBRQ_HID_GET_REPORT) {
            // wValue: ReportType (highbyte), ReportID (lowbyte)
            // we only have one report type, so don't look at wValue
            return sizeof(reportBuffer);
        } else if (rq->bRequest == USBRQ_HID_SET_REPORT) {
            if (rq->wLength.word == 1) {
                // We expect one byte reports
                expectReport = 1;
                return 0xff; // Call usbFunctionWrite with data
            }
        } else if (rq->bRequest == USBRQ_HID_GET_IDLE) {
            usbMsgPtr = &idleRate;
            return 1;
        } else if (rq->bRequest == USBRQ_HID_SET_IDLE) {
            idleRate = rq->wValue.bytes[1];
        } else if (rq->bRequest == USBRQ_HID_GET_PROTOCOL) {
            if (rq->wValue.bytes[1] < 1) {
                protocolVer = rq->wValue.bytes[1];
            }
        } else if(rq->bRequest == USBRQ_HID_SET_PROTOCOL) {
            usbMsgPtr = &protocolVer;
            return 1;
        }
    } else {
        // no vendor specific requests implemented
    }
    return 0;
}

/**
 * The write function is called when LEDs should be set. Normally, we get only
 * one byte that contains info about the LED states.
 * \param data pointer to received data
 * \param len number ob bytes received
 * \return 0x01
 */
uint8_t usbFunctionWrite(uchar *data, uchar len) {
    if (expectReport && (len == 1)) {
        setLeds(data[0]);
    }
    expectReport = 0;
    return 0x01;
}

/**
 * Send a report buffer to the computer.
 * \param reportbuffer report buffer
 */
void usbSendReportBuffer(uint8_t* reportbuffer, uint8_t size) {
    while (!usbInterruptIsReady()) {
        usbPoll();
    }
    usbSetInterrupt(reportbuffer, size); // send
}

/**
 * Send a single report to the computer. This function is not used during
 * normal typing, it is only used to send non-pressed keys to simulate input.
 * \param mode modifier-byte
 * \param key key-code
 */
void usbSendReport(uint8_t mode, uint8_t key) {
    // buffer for HID reports. we use a private one, so nobody gets disturbed
    uint8_t repBuffer[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
    repBuffer[0] = mode;
    repBuffer[2] = key;
    wdt_reset();
    usbSendReportBuffer(repBuffer, sizeof(repBuffer));
}

/* ------------------------------------------------------------------------- */

/**
 * Convert an ASCII-character to the corresponding key-code and modifier-code
 * combination.
 * \param character ASCII-character to convert
 * \return structure containing the combination
 */
Key charToKey(char character) {
    Key key;
    // initialize with reserved values
    key.mode = MOD_NONE;
    key.key = KEY_Reserved;
    if ((character >= 'a') && (character <= 'z')) {
        // a..z
        key.key = (character - 'a') + 0x04;
    } else if ((character >= 'A') && (character <= 'Z')) {
        // A..Z
        key.mode = MOD_SHIFT_LEFT;
        key.key = (character - 'A') + 0x04;
    } else if ((character >= '1') && (character <= '9')) {
        // 1..9
        key.key = (character - '1') + 0x1E;
    }
    // we can't map the other characters directly, so we switch...
    switch (character) {
        case '0':
            key.key = KEY_0; break;
        case '!':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_1; break;
        /*
        case '@':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_2; break;
        case '#':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_3; break;
        */
        case '$':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_4; break;
        case '%':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_5; break;
        case '^':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_6; break;
        case '&':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_7; break;
        case '*':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_8; break;
        case '(':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_9; break;
        case ')':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_0; break;
        case ' ':
            key.key = KEY_Spacebar; break;
        case '-':
            key.key = KEY_minus; break;
        case '_':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_minus; break;
        case '=':
            key.key = KEY_equals; break;
        case '+':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_equals; break;
        case '[':
            key.key = KEY_lbracket; break;
        case '{':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_lbracket; break;
        case ']':
            key.key = KEY_rbracket; break;
        case '}':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_rbracket; break;
        case '\\':
            key.key = KEY_backslash; break;
        case '|':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_backslash; break;
        case '#':
            key.key = KEY_hash; break;
        case '@':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_hash; break;
        case ';':
            key.key = KEY_semicolon; break;
        case ':':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_semicolon; break;
        case '\'':
            key.key = KEY_apostroph; break;
        case '"':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_apostroph; break;
        case '`':
            key.key = KEY_grave; break;
        case '~':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_grave; break;
        case ',':
            key.key = KEY_comma; break;
        case '<':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_comma; break;
        case '.':
            key.key = KEY_dot; break;
        case '>':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_dot; break;
        case '/':
            key.key = KEY_slash; break;
        case '?':
            key.mode = MOD_SHIFT_LEFT;
            key.key = KEY_slash; break;
        case '\n':
            key.key = KEY_Return; break;
    }
    if (key.key == KEY_Reserved) {
        // still reserved? WTF? return question mark...
        key.mode = MOD_SHIFT_LEFT;
        key.key = KEY_slash;
    }
    return key;
} 

/**
 * Send a key to the computer, followed by the release of all keys. This can be
 * used repetitively to send a string.
 * \param keytosend key structure to send
 */
void sendKey(Key keytosend) {
    usbSendReport(keytosend.mode, keytosend.key);
    usbSendReport(0, 0);
}

/**
 * Send a string to the computer. This function converts each character of an
 * ASCII-string to a key-structure and uses sendKey() to send it.
 * \param string string to send
 */
void sendString(char* string) {
    for (uint8_t i = 0; i < strlen(string); i++) {
        Key key = charToKey(string[i]);
        sendKey(key);
    }
}

/**
 * Calculate an average value for the speed the keys are pressed in. The speed
 * value is sent to the keyboard interface, the keyboard decides what to do
 * with it.
 * \param updates number of updates since the last call (should be in the range
 * 0-1)
 */
void calculateSpeed(uint8_t updates) {
    static uint8_t callcounter = 0;
    static uint8_t speed = 0;
    callcounter++;
    if (updates > 0) {
        // a key has been pressed
        if (speed < 251) {
            // it can get hotter...
            if (callcounter < 8) {
                speed += 4;
            } else {
                speed += 2;
            }
            setSpeed(speed);
        } else if (speed < 255) {
            speed = 255;
            setSpeed(speed);
        }
        callcounter = 0;
    } else {
        // no key pressed...
        if (callcounter == 16) {
            // ... for a long time, decrease speed value
            if (speed > 8) {
                speed -= 8;
                setSpeed(speed);
            } else if (speed > 0) {
                speed = 0;
                setSpeed(speed);
            }
            callcounter = 0;
        }
    }
}

/**
 * Main function, containing the main loop that manages timer- and
 * USB-functionality.
 * /return the obligatory integer that nobody cares about...
 */
int main(void) {
    uint8_t updateNeeded = 0;
    //uint8_t idleCounter = 0;
    uint8_t updates = 0;
    uint16_t tickcounter = 0;
    wdt_enable(WDTO_2S);
    hardwareInit();
    usbInit();
    sei();

    memset(oldReportBuffer, 0, sizeof(oldReportBuffer)); // clear old report buffer

    scankeys(reportBuffer, oldReportBuffer, sizeof(reportBuffer));
    while (1) {
        // main event loop
        wdt_reset();
        usbPoll();

        updateNeeded = scankeys(reportBuffer, oldReportBuffer, sizeof(reportBuffer)); // changes?
        if (updateNeeded) {
            updates++;
        }

        // check timer if we need periodic reports
        if (TIFR & (1 << TOV0)) {
            TIFR = (1 << TOV0); // reset flag
            if (tickcounter < UINT16_MAX) {
                tickcounter++;
            }
            calculateSpeed(updates);
            updates = 0;
            /*
            if (idleRate != 0) { // do we need periodic reports?
                if(idleCounter > 4){ // yes, but not yet
                    idleCounter -= 5; // 22ms in units of 4ms
                } else { // yes, it is time now
                    updateNeeded = 1;
                    idleCounter = idleRate;
                }
            }
            */
        }
        // if an update is needed, send the report
        if (updateNeeded) {
            updateNeeded = 0;
            keypressed(reportBuffer, sizeof(reportBuffer), tickcounter);
            tickcounter = 0;
            memcpy(oldReportBuffer, reportBuffer, sizeof(oldReportBuffer));
        }
    }
    return 0;
}

/* ------------------------------------------------------------------------- */
