
#include "keyboard.h"

// commonly used variables for all types of keyboards

DFuncParPtr keyfuncs[2*KBD_NUMKEYS];
int keyfuncparams[2*KBD_NUMKEYS];

// commonly used functions for all types of keyboard

//
// set a function and a parameter to be passed for a keyboard function
// 
extern void setKeyFunction(int functionNumber, DFuncParPtr func, int functionParameter, String info) {
  keyfuncs[functionNumber] = func;
  keyfuncparams[functionNumber] = functionParameter;
  SERIAL_PRINTF("setKeyFunction.functionNumber=%d.function=0x%08X.funcParam=%d.info=%s\n",functionNumber,func,functionParameter,info.c_str());
}

#ifdef KBD_GEN

#ifdef KBD_DIG
#error wrong keyboard type
#endif

#ifdef KBD_I2C
// definitions for keyboard matrix interface using I2C expander
// mask to OR value to be written to port expander to set scan columns
#define COLSCANORMASK    (0xF0)
// address of the PCF8574 port expander, this value is for a PCF8574 with A0/A1/A2 control pins all connected to GND
#define PCF8574_ADDR     (0x20)
// number of bits the scan column pattern should be pushed to the left before writing if, 0 means the lower 4 bits are used
#define COLSCANPOSOFFSET (0)
// number of bits the return row pattern is pushed to the left when read, 4 means the rows occupy bits 4..7 of the byte read from the port expander
#define ROWRETURNPOSOFFSET (4)
#endif

#define FUNC_A  129
#define FUNC_B  130
#define FUNC_C  131
#define FUNC_D  132

byte keystate[ROWS][COLS];      // final (debounced) key state, one byte for each key
byte keydebstate[ROWS][COLS];   // debugging key state
byte curScanCol;                // current key column being scanned

const byte keys[ROWS][COLS] = { {'1', '2', '3', FUNC_A},           // ROW 0
                                {'4', '5', '6', FUNC_B},           // ROW 1
                                {'7', '8', '9', FUNC_C},           // ROW 2
                                {'*', '0', '#', FUNC_D} };         // ROW 3
                                                                                                                                                                                                                                                                                                      
DFuncParPtr keyfuncs[5] = { (DFuncParPtr) 0, (DFuncParPtr) 0, (DFuncParPtr) 0,   (DFuncParPtr) 0,   (DFuncParPtr) 0 };
int keyfuncparams[5]    = { 0,               FUNC_A,          FUNC_B,            FUNC_C,            FUNC_D          };

//
// set scan column lines so that <activeCol(0..3)> is active (LOW) and all others are inactive (HIGH)
//
void setScanLines(byte activeCol) {
#ifdef KBD_I2C
  Wire.beginTransmission(PCF8574_ADDR);
  Wire.write(COLSCANORMASK | (0xff^(1<<(activeCol+COLSCANPOSOFFSET))));  // upper 4 bits are HIGH == return row lines used as input with pullup, lower 4 bits are driving the scan columns
  Wire.endTransmission();
#endif
#ifdef KBD_DIG
#error wrong keyboard type
#endif
}  

/* *****************************************************************************
 *		FUNCTION:	initKeyboard
 */
void initKeyboard() {
  curScanCol = 0;
  for (int r = 0; r<ROWS; r++) {
#ifdef KBD_DIG
#error wrong keyboard type
#endif
    for (int c = 0; c<COLS; c++) {
      keystate[r][c] = 0;
      keydebstate[r][c] = 0;
    }
  }
  setScanLines(curScanCol);
#if defined(BUTTON_EVENT)
  registerTicker(scanKeyboard,BUTTON_EVENT);
  SERIAL_PRINTF("Keyboard GEN initialized, BUTTON_EVENT is " BUTTON_EVENT "\n");
#else
  addLoopScanner(20,scanKeyboard,0,"keyboardScanner");
  SERIAL_PRINTF("Keyboard GEN initialized\n");
#endif
}

/*  ----------------------------------------------------------------------------------------------
 *    FUNCTION:         keyboard scanner
 *    DESCRIPTION:      this is called periodically by loopScanner
 *                      each time, it:
 *                      - reads the current key row return values
 *                      - does debouncing on contact closure: a closed key is detected only if 2 consecutive scan cycles detects LOW value on key row return line
 *                      - detects any key changes (close<->open); in case of a key change:
 *                      -- determines the function to be called configured in keyfuncs[] and invokes the function with the parameter configured in keyfuncparams[]
 *                      - sets up the key column scan lines to the next column (the row values to be read at the next invocation)
 */
void scanKeyboard(int para) {
  byte scval;                              // value returned from ROW line
  byte dval;                               // debounced value
#ifdef KBD_I2C
  byte wireval;                            // byte read from the port expander
  byte a;
#endif
  int keynum;
  int keyevent;
  DFuncParPtr func;
#ifdef KBD_I2C
  Wire.requestFrom(PCF8574_ADDR,1,true);
  if ((a = Wire.available())!=1) {
    // SERIAL_PRINTF("scanKeyboard.ERROR: there should be 1 byte available, but there are %d available\n",a);
    wireval = 0xff;
  } else {
    wireval = Wire.read();                                    // I2C-key matrix: read 4 return row lines in one operation and keep them in <wireval>
  }
#endif
  for (int r = 0; r<ROWS; r++) {
#ifdef KBD_I2C
    scval = (wireval & (1<<(r+ROWRETURNPOSOFFSET)))!=0?0:1;   // I2C-key-matrix: get single return row line status from previously read <wireval>
#endif
#ifdef KBD_DIG
    scval = (digitalRead(rowPins[r])==HIGH?0:1);              // GPIO-key-matrix: read each return row line separately directly from its pin
#endif
    dval = scval & keydebstate[r][curScanCol];
    keydebstate[r][curScanCol] = scval;
    if (dval!=keystate[r][curScanCol]) {                      // key state has changed
      keystate[r][curScanCol] = dval;
      keynum = curScanCol + (r*KBD_ROWMULTIPLIER);
      keyevent = keynum+((dval>0)?0:KDB_RELEASEOFFSET);
#if defined(BUTTON_EVENT)
      putEventQueueEntry(BUTTON_EVENT,keyevent);
#else
      func = keyfuncs[keyevent];
      if (func!=NULL) {
        func(keyfuncparams[keyevent]);
      }
#endif
    }
  }
  curScanCol++;
  if (curScanCol>=COLS) {
    curScanCol = 0;
  } 
  setScanLines(curScanCol);
}

// end of KBD_GEN
#endif

//
// ---------- KBD_RBPXPLR variant -----------------------------------------------------------------------------
//

#ifdef KBD_RBPXPLR

//
// button variables
//
const int buttonPorts[KBD_NUMKEYS] = { BUTTON_UP_PIN, BUTTON_RIGHT_PIN, BUTTON_DOWN_PIN, BUTTON_LEFT_PIN };

int buttonState[KBD_NUMKEYS];
int buttonOnTimes[KBD_NUMKEYS];

//
// button functions
//

void scanKeyboard(int para) {
  int portState;
  for (int i=0; i<KBD_NUMKEYS; i++) {
    portState = digitalRead(buttonPorts[i]);
    if (buttonState[i]==BUTTON_RELEASE) {
      if (portState==BUTTON_PRESS) {
        buttonState[i] = BUTTON_DEBOUNCE;
        buttonOnTimes[i] = 0;
      }
    } else if (buttonState[i]==BUTTON_DEBOUNCE) {
      if (portState==BUTTON_PRESS) {
        buttonOnTimes[i]++;
        if (buttonOnTimes[i]>BUTTON_DEBOUNCE_TIME) {
          buttonState[i] = BUTTON_PRESS;
          // SERIAL_PRINTF("keyboard: key number %d pressed\n",i);
#if defined(BUTTON_EVENT)
          putEventQueueEntry(BUTTON_EVENT,i);
#else
          if (keyfuncs[i]!=NULL) {
            keyfuncs[i](keyfuncparams[i]);
          }
#endif
        }
      } else {
        buttonOnTimes[i] = 0;
        buttonState[i] = BUTTON_RELEASE;
      }
    } else { // buttonState is BUTTON_PRESS
      if (portState==BUTTON_RELEASE) {
        buttonState[i] = BUTTON_RELEASE;
        // SERIAL_PRINTF("keyboard: key number %d released\n",i);
#if defined(BUTTON_EVENT)
        putEventQueueEntry(BUTTON_EVENT,i+KBD_RELEASEOFFSET);
#else
        if (keyfuncs[i+KBD_RELEASEOFFSET]!=NULL) {
          keyfuncs[i+KBD_RELEASEOFFSET](keyfuncparams[i+KBD_RELEASEOFFSET]);
        }
#endif
      }
    }
  }
}

void initKeyboard() {
  String buttonStates = "";
  for (int i=0; i<KBD_NUMKEYS; i++) {
    pinMode(buttonPorts[i],BUTTON_INIT_MODE);
    keyfuncs[i] = NULL;
    keyfuncs[i+KBD_RELEASEOFFSET] = NULL;
    keyfuncparams[i] = 0;
    keyfuncparams[i+KBD_RELEASEOFFSET] = 0;
    buttonState[i] = digitalRead(buttonPorts[i]);
    if (i>0) {
      buttonStates += ",";
    }
    buttonStates += String(((buttonState[i]==BUTTON_PRESS)?"PRESSED":"RELEASED"));
  }
#if defined(BUTTON_EVENT)
  registerTicker(scanKeyboard,BUTTON_EVENT);
  SERIAL_PRINTF("Keyboard RBPXPLR initialized for interrupt scanner, BUTTON_EVENT is %d, button states are: [%s]\n",BUTTON_EVENT,buttonStates.c_str());
#else
  addLoopScanner(1,scanKeyboard,0,"keyboardScanner");  
  SERIAL_PRINTF("Keyboard RBPXPLR initialized, button states are: [%s]\n",buttonStates.c_str());
#endif
}

// end of KBD_RBPXPLR
#endif

// end of module keyboard.cpp
