#include <Arduino.h>

#include "PCKeyboard.h"

#define _REG_VER 0x01 // fw version
#define _REG_CFG 0x02 // config
#define _REG_INT 0x03 // interrupt status
#define _REG_KEY 0x04 // key status
#define _REG_BKL 0x05 // backlight
#define _REG_DEB 0x06 // debounce cfg
#define _REG_FRQ 0x07 // poll freq cfg
#define _REG_RST 0x08 // reset
#define _REG_FIF 0x09 // fifo
#define _REG_BK2 0x0A // backlight 2
#define _REG_BAT 0x0B // battery
#define _REG_DIR 0x0C // gpio direction
#define _REG_PUE 0x0D // gpio input pull enable
#define _REG_PUD 0x0E // gpio input pull direction
#define _REG_GIO 0x0F // gpio value
#define _REG_GIC 0x10 // gpio interrupt config
#define _REG_GIN 0x11 // gpio interrupt status

#define _WRITE_MASK (1 << 7)

#define CFG_OVERFLOW_ON  (1 << 0)
#define CFG_OVERFLOW_INT (1 << 1)
#define CFG_CAPSLOCK_INT (1 << 2)
#define CFG_NUMLOCK_INT  (1 << 3)
#define CFG_KEY_INT      (1 << 4)
#define CFG_PANIC_INT    (1 << 5)

#define INT_OVERFLOW     (1 << 0)
#define INT_CAPSLOCK     (1 << 1)
#define INT_NUMLOCK      (1 << 2)
#define INT_KEY          (1 << 3)
#define INT_PANIC        (1 << 4)

#define KEY_CAPSLOCK     (1 << 5)
#define KEY_NUMLOCK      (1 << 6)
#define KEY_COUNT_MASK   (0x1F)

#define DIR_OUTPUT       0
#define DIR_INPUT        1

#define PUD_DOWN         0
#define PUD_UP           1

PCKeyboard::PCKeyboard()
    : m_wire(nullptr)
{

}

void PCKeyboard::begin(uint8_t addr, TwoWire *wire)
{
    m_addr = addr;
    m_wire = wire;

	/*
	m_wire->setSDA(4);//i2c0, GP4
	m_wire->setSCL(5);//i2c0 GP5
	*/
    m_wire->begin();
	//m_wire->setClock(10000); // important
    reset();
}

void PCKeyboard::reset()
{
    m_wire->beginTransmission(m_addr);
    m_wire->write(_REG_RST);
    m_wire->endTransmission();

    delay(100);
}

void PCKeyboard::attachInterrupt(uint8_t pin, void (*func)()) const
{
    ::pinMode(pin, INPUT_PULLUP);
    ::attachInterrupt(digitalPinToInterrupt(pin), func, RISING);
}

void PCKeyboard::detachInterrupt(uint8_t pin) const
{
    ::detachInterrupt(pin);
}

void PCKeyboard::clearInterruptStatus()
{
    writeRegister(_REG_INT, 0x00);
}

uint16_t PCKeyboard::status() const
{
    return readRegister16(_REG_KEY);
}

uint8_t PCKeyboard::keyCount() const
{
    return status() & KEY_COUNT_MASK;
}

PCKeyboard::KeyEvent PCKeyboard::keyEvent() const
{
    KeyEvent event = { .key = '\0', .state = StateIdle };

    if (keyCount() == 0)
        return event;

    const uint16_t buf = readRegister16(_REG_FIF);
    event.key = buf >> 8;
    event.state = KeyState(buf & 0xFF);

    return event;
}

float PCKeyboard::backlight() const
{
    return readRegister8(_REG_BKL) / 255.0f;
}

void PCKeyboard::setBacklight(float value)
{
    writeRegister(_REG_BKL, value * 255);
}

float PCKeyboard::backlight2() const
{
    return readRegister8(_REG_BK2) / 255.0f;
}

void PCKeyboard::setBacklight2(float value)
{
    writeRegister(_REG_BK2, value * 255);
}

void PCKeyboard::pinMode(uint8_t pin, uint8_t mode)
{
    if (pin > 7)
        return;

    if (mode == INPUT || mode == INPUT_PULLUP) {
        updateRegisterBit(_REG_DIR, pin, DIR_INPUT);
    } else if (mode == OUTPUT) {
        updateRegisterBit(_REG_DIR, pin, DIR_OUTPUT);
    }
}

void PCKeyboard::digitalWrite(uint8_t pin, uint8_t val)
{
    if (pin > 7)
        return;

    if (readRegisterBit(_REG_DIR, pin) == DIR_INPUT) {
        updateRegisterBit(_REG_PUD, pin, val == LOW ? PUD_DOWN : PUD_UP);
    } else {
        updateRegisterBit(_REG_GIO, pin, val == HIGH);
    }
}

int PCKeyboard::digitalRead(uint8_t pin)
{
    if (pin > 7)
        return LOW;

    if (readRegisterBit(_REG_GIO, pin))
        return HIGH;

    return LOW;
}

uint8_t PCKeyboard::readRegister8(uint8_t reg) const
{
    m_wire->beginTransmission(m_addr);
    m_wire->write(reg);
    m_wire->endTransmission();

    m_wire->requestFrom(m_addr, 1);
    if (m_wire->available() < 1)
        return 0;

    return m_wire->read();
}

uint16_t PCKeyboard::readRegister16(uint8_t reg) const
{
    m_wire->beginTransmission(m_addr);
    m_wire->write(reg);
    m_wire->endTransmission();

    m_wire->requestFrom(m_addr, 2);
    if (m_wire->available() < 2)
        return 0;

    const uint8_t low = m_wire->read();
    const uint8_t high = m_wire->read();

    return (high << 8) | low;
}

uint8_t PCKeyboard::readRegisterBit(uint8_t reg, uint8_t bit)
{
    return bitRead(readRegister8(reg), bit);
}

void PCKeyboard::writeRegister(uint8_t reg, uint8_t value)
{
    uint8_t data[2];
    data[0] = reg | _WRITE_MASK;
    data[1] = value;

    m_wire->beginTransmission(m_addr);
    m_wire->write(data, sizeof(uint8_t) * 2);
    m_wire->endTransmission();
}

void PCKeyboard::updateRegisterBit(uint8_t reg, uint8_t bit, uint8_t value)
{
    uint8_t oldValue = readRegister8(reg);
    uint8_t newValue = oldValue;

    bitWrite(newValue, bit, value);

    if (newValue != oldValue)
        writeRegister(reg, newValue);
}
