/**
 * \file commandhandler.c
 * \brief TODO
 * \author Ronald Schaten <ronald@schatenseite.de>
 * \version $Id$
 *
 * License: TODO
 */

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

// TODO comment

typedef enum {
    standard,
    commandmode,
    macrocommand,
    macrodefinition,
    macrodelete,
    complexcommand
} Commandstate;

static Commandstate commandstate = standard;

#define MACROCOUNT 10
#define MACROLENGTH 20

// macrodefinitions[][0] is the macro-key
Key macrodefinitions[MACROCOUNT][MACROLENGTH + 1];
uint8_t macrocountindex = 0;
uint8_t macrodefinitionindex = 0;


uint8_t getMacroindex(Key macrokey) {
    for (uint8_t i = 0; i < MACROCOUNT; i++) {
        if ((macrodefinitions[i][0].mode == macrokey.mode) && (macrodefinitions[i][0].key == macrokey.key)) {
            return i;
        }
    }
    return MACROCOUNT;
}

void deleteMacroindex(Key macrokey) {
    for (uint8_t i = 0; i < MACROCOUNT; i++) {
        if ((macrodefinitions[i][0].mode == macrokey.mode) && (macrodefinitions[i][0].key == macrokey.key)) {
            macrodefinitions[i][0].mode = MOD_NONE;
            macrodefinitions[i][0].key = KEY_Reserved;
            break;
        }
    }
}

void sendMacro(uint8_t index) {
    for (uint8_t i = 0; i < MACROLENGTH; i++) {
        if(macrodefinitions[index][i+1].key != KEY_Reserved) {
            sendKey(macrodefinitions[index][i+1]);
        } else {
            break;
        }
    }
}

uint8_t checkmacrodefinition(uint8_t* reportbuffer) {
    if ((reportbuffer[2] != KEY_Reserved) && (reportbuffer[3] == KEY_Reserved)) {
        Key macrokey;
        macrokey.mode = reportbuffer[0];
        macrokey.key = reportbuffer[2];
        return getMacroindex(macrokey);
    }
    return MACROCOUNT;
}

uint8_t setMacroindex(Key macrokey) {
    for (uint8_t i = 0; i < MACROCOUNT; i++) {
        if ((macrodefinitions[i][0].mode == macrokey.mode) && (macrodefinitions[i][0].key == macrokey.key)) {
            macrodefinitions[i][0].mode = MOD_NONE;
            macrodefinitions[i][0].key = KEY_Reserved;
            break;
        }
    }
    for (uint8_t i = 0; i < MACROCOUNT; i++) {
        if ((macrodefinitions[i][0].mode == MOD_NONE) && (macrodefinitions[i][0].key == KEY_Reserved)) {
            macrodefinitions[i][0].mode = macrokey.mode;
            macrodefinitions[i][0].key = macrokey.key;
            return i;
        }
    }
    return MACROCOUNT;
}

uint8_t iskey(uint8_t* reportbuffer, uint8_t modifier, uint8_t key) {
    if ((reportbuffer[0] == modifier) && (reportbuffer[2] == key) && (reportbuffer[3] == KEY_Reserved)) {
        return 1;
    } else {
        return 0;
    }
}

uint8_t ishotkey(uint8_t* reportbuffer) {
    return iskey(reportbuffer, (MOD_SHIFT_LEFT | MOD_SHIFT_RIGHT), KEY_D);
}

void keypressed(uint8_t* reportbuffer, uint8_t size, uint16_t tickcounter) {
    switch (commandstate) {
        case commandmode:
            if (iskey(reportbuffer, (MOD_SHIFT_LEFT | MOD_SHIFT_RIGHT), KEY_Reserved) ||
                iskey(reportbuffer, MOD_SHIFT_LEFT, KEY_Reserved) ||
                iskey(reportbuffer, MOD_SHIFT_RIGHT, KEY_Reserved) ||
                iskey(reportbuffer, MOD_NONE, KEY_Reserved)) {
                // ignore pressed shift-keys, probably still pressed from hot
                // key usage
            } else if (iskey(reportbuffer, MOD_NONE, KEY_T)) {
                // toggle function
                toggle();
                commandstate = standard;
            } else if (iskey(reportbuffer, MOD_NONE, KEY_M)) {
                // record macro
                commandstate = macrocommand;
            } else if (iskey(reportbuffer, MOD_SHIFT_LEFT, KEY_M) || iskey(reportbuffer, MOD_SHIFT_RIGHT, KEY_M)) {
                // delete macro
                commandstate = macrodelete;
            } else {
                // command not recognized, return to standard mode
                commandstate = standard;
            }
            break;
        case macrocommand:
            if (reportbuffer[2] == KEY_Reserved) {
                // just modifier pressed => ignore
            } else {
                Key macrokey;
                macrokey.mode = reportbuffer[0];
                macrokey.key = reportbuffer[2];
                macrocountindex = setMacroindex(macrokey);
                if (macrocountindex == MACROCOUNT) {
                    // no space left
                    commandstate = standard;
                } else {
                    commandstate = macrodefinition;
                    macrodefinitionindex = 0;
                }
            }
            break;
        case macrodefinition:
            if (ishotkey(reportbuffer)) {
                // macro definition complete
                macrodefinitions[macrocountindex][macrodefinitionindex + 1].mode = MOD_NONE;
                macrodefinitions[macrocountindex][macrodefinitionindex + 1].key = KEY_Reserved;
                commandstate = standard;
            } else if (reportbuffer[2] == KEY_Reserved) {
                // just modifier pressed => ignore
            } else {
                macrodefinitions[macrocountindex][macrodefinitionindex + 1].mode = reportbuffer[0];
                macrodefinitions[macrocountindex][macrodefinitionindex + 1].key = reportbuffer[2];
                macrodefinitionindex++;
                if (macrodefinitionindex == MACROLENGTH) {
                    // no space left in this macro
                    commandstate = standard;
                }
            }
            break;
        case macrodelete:
            if (reportbuffer[2] == KEY_Reserved) {
                // just modifier pressed => ignore
            } else {
                Key macrokey;
                macrokey.mode = reportbuffer[0];
                macrokey.key = reportbuffer[2];
                deleteMacroindex(macrokey);
                commandstate = standard;
            }
            break;
        case complexcommand:
            break;
        default:
            if (ishotkey(reportbuffer)) {
                commandstate = commandmode;
            } else if ((macrocountindex = checkmacrodefinition(reportbuffer)) != MACROCOUNT) {
                sendMacro(macrocountindex);
            } else {
                usbSendReportBuffer(reportbuffer, size);
            }
    }
}
