/**
 * \file cimdithal.cpp
 * \brief implementation of the CimditHAL class
 * \author GrumpyDeveloper (Sascha Nitsch)
 * \copyright 2022 Sascha Nitsch
 * Licensed under MIT license
 *
 */
 // our defines
 #include "defines.h"
 
// library includes
#include <Adafruit_MCP23X17.h>

// own includes
#include "cimdithal.h"

uint32_t CimditHAL::m_millis = 0;

CimditHAL::CimditHAL() {
  m_nextKeyScan = 0;
  m_nextAnalogRead = 0;
  m_keyRow = 0;
  m_analogNum = 0;
  m_analogChanged = 0;
  for (uint8_t i = 0; i < 8; ++i) {
    m_currentButtons[i] = 0xff;
    m_buttonsChanged[i] = 0;
  }
  m_rotChanged = 0;
}

void CimditHAL::begin() {
  pinMode(ROT_INT, INPUT);
  pinMode(ANALOG_MUX0, OUTPUT);
  pinMode(ANALOG_MUX1, OUTPUT);
  pinMode(ANALOG_MUX2, OUTPUT);
  pinMode(ANALOG_MUX3, OUTPUT);
  m_keyMatrix.begin_I2C(0x20);
  m_rotEncoders.begin_I2C(0x21);
  // combine the inputs to a single interrupt pin
  m_rotEncoders.setupInterrupts(true, false, HIGH);
  for (uint8_t i = 0; i < 8; ++i) {
    // set key matrix row to output
    m_keyMatrix.pinMode(i, OUTPUT);
    // set encoders to input and setup interrupt to fire on change
    m_rotEncoders.pinMode(i, INPUT_PULLUP);
    m_rotEncoders.setupInterruptPin(i, CHANGE);
    m_currentButtons[i] = 0xff;
  }
  for (uint8_t i = 8; i < 16; ++i) {
    // set key matrix rows to input
    m_keyMatrix.pinMode(i, INPUT_PULLUP);
    // set encoders to input and setup interrupt to fire on change
    m_rotEncoders.pinMode(i, INPUT_PULLUP);
    m_rotEncoders.setupInterruptPin(i, CHANGE);
  }
  m_keyMatrix.writeGPIO(0xff, 0);

  // read in encoder values
  uint16_t rot = m_rotEncoders.readGPIOAB();
  for (uint8_t i = 0; i < 8; ++i) {
    m_rotaryEncoders[i].update((rot & 1) != 0, (rot & 2) != 0);
    rot >>= 2;
  }

  // read in key matrix values
  for (uint8_t i = 0 ; i < 8; ++i) {
    m_keyMatrix.writeGPIO(0xff ^ (1 << i), 0);
    delay(10);
    m_currentButtons[i] = m_keyMatrix.readGPIO(1);
  }

  // read in analog values
  for (uint8_t i = 0 ; i < 16; ++i) {
    digitalWrite(ANALOG_MUX3, i & 8);
    digitalWrite(ANALOG_MUX2, i & 4);
    digitalWrite(ANALOG_MUX1, i & 2);
    digitalWrite(ANALOG_MUX0, i & 1);
    delay(10);  // give the muxer a chance
    m_currentAnalogValues[i] = analogRead(ANALOG_IN);
  }
  m_millis = millis();
}

void CimditHAL::readFromHardware(bool interrupt) {
  bool rotChanged = interrupt;  // if true, we return after doing the rotary steps
  while (interrupt || digitalRead(ROT_INT)) {
    rotChanged = true;
    interrupt = false;
    uint16_t rot = m_rotEncoders.readGPIOAB();
    for (uint8_t i = 0; i < 8; ++i) {
      m_rotChanged |= m_rotaryEncoders[i].update((rot & 1) != 0, (rot & 2) != 0) << i;
      rot = rot >> 2;
    }
  }
  // quick return if rotary encoder changed
  if (rotChanged) return;

  m_millis = millis();
  // uint32_t rollover every 92 days
  if (m_millis < ROLLOVER_INTERVAL) {
    m_nextAnalogRead = 0;
    m_nextKeyScan = 0;
  }
  if (m_millis > m_nextAnalogRead) {  // only scan analog every ANALOG_INTERVAL ms
    m_nextAnalogRead = m_millis + ANALOG_INTERVAL;
    // save old state
    int16_t lastAnalogValue = m_currentAnalogValues[m_analogNum];
    // scan analog value
    m_currentAnalogValues[m_analogNum] = analogRead(ANALOG_IN);
    // set changed bits
    if (lastAnalogValue != m_currentAnalogValues[m_analogNum]) {
    //if (abs(lastAnalogValue - m_currentAnalogValues[m_analogNum]) > 1) {
      m_analogChanged |= 1 << m_analogNum;
    }
     // set mux value for next round, wrap to 0 at 16
    m_analogNum = (m_analogNum + 1) & 15;
    // set output
    digitalWrite(ANALOG_MUX3, m_analogNum & 8);
    digitalWrite(ANALOG_MUX2, m_analogNum & 4);
    digitalWrite(ANALOG_MUX1, m_analogNum & 2);
    digitalWrite(ANALOG_MUX0, m_analogNum & 1);
  }

  // scan key matrix
  if (m_millis > m_nextKeyScan) {  // only scan analog every KEYSCAN_INTERVAL ms
    m_nextKeyScan = m_millis + KEYSCAN_INTERVAL;
    // save old state
    uint8_t lastButtons = m_currentButtons[m_keyRow];
    // read column bits
    m_currentButtons[m_keyRow] = m_keyMatrix.readGPIO(1);
    // set changed bits
    m_buttonsChanged[m_keyRow] |= lastButtons ^ m_currentButtons[m_keyRow];
    // set row bit wrap 8 to 0
    m_keyRow = (m_keyRow + 1) & 7;
    m_keyMatrix.writeGPIO(0xff ^ (1 << m_keyRow), 0);
  }
}

uint8_t CimditHAL::rotaryChanged() {
  uint8_t ret = m_rotChanged;
  m_rotChanged = 0;
  return ret;
}

uint16_t CimditHAL::analogChanged() {
  uint16_t ret = m_analogChanged;
  m_analogChanged = 0;
  return ret;
}

uint64_t CimditHAL::buttonsChanged() {
  uint64_t ret = 0;
  for (int8_t i = 7; i >= 0; --i) {
    ret = (ret << 8) + m_buttonsChanged[i];
    m_buttonsChanged[i] = 0;
  }
  return ret;
}

int8_t CimditHAL::getEncoder(uint8_t num) {
  return m_rotaryEncoders[num].getDelta();
}

uint16_t CimditHAL::getAnalog(uint8_t num) const {
  return m_currentAnalogValues[num];
}

bool CimditHAL::getButton(uint8_t num) const {
  return !((m_currentButtons[num >> 3] >> (num & 7)) & 1);
}
