/**
 * \file cimdit.ino
 * \brief main functions
 * \author GrumpyDeveloper (Sascha Nitsch)
 * \copyright 2022 Sascha Nitsch
 * Licensed under MIT license
 *
 */
// defines
#include "defines.h"

// library includes
#include <HID-Project.h>
#ifdef HAVE_DISPLAY
#include "cimditssd1306.h"
#endif

// own includes
#include "cimdithal.h"
#include "cimditprofile.h"

/// our hardware abstraction layer
CimditHAL hal;

/// flag if the rotary interrupt has been triggered
volatile bool outstandingRotInterrupt = false;

/// interrupt service routine for rotatry change interrupt
void rotInt() {
  outstandingRotInterrupt = true;
}

/// next update time in ms
uint32_t nextUpdate = 0;

#ifdef HAVE_DISPLAY
/// our display
CimditSSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_ADDRESS);
#endif

// our profile handling class
CimditProfile profile;

/// initial setup
void setup() {
  Serial.begin(115200);
  hal.begin();
  attachInterrupt(digitalPinToInterrupt(7), rotInt, RISING);
  // initialize USB related things
  Keyboard.begin();
  Mouse.begin();
  Gamepad.begin();
  Wire.begin();
  Wire.setClock(400000);
#ifdef HAVE_DISPLAY
  // init display
  display.begin();
  display.clearDisplay();
  display.setTextSize(2,2);
  display.setCursor(28, 8);
  display.write("cimdit");
  display.display();
#endif
  profile.begin();
}

/// protection string
const char protectString[] = "cimdit:";

/// current position in protection string
uint8_t protectPos = 0;

/// main loop
void loop() {
  hal.readFromHardware(outstandingRotInterrupt);
  if (outstandingRotInterrupt) {
    outstandingRotInterrupt = false;
    return;
  }
  profile.tick();
  if (Serial.available()) {
    // incoming message
    uint8_t in;
    while ((in = Serial.read()) != 255) {
      if (protectString[protectPos++] != in) {
        protectPos = 0;
        continue;
      }
      if (protectPos == sizeof(protectString) - 1) { // good, something for us
        protectPos = 0;
        // every line has to start with cimdit: or it will be ignored
        uint8_t cmd = Serial.read();
        switch (cmd) {
          case 'd':
            profile.userDisplay();
            break;
          case 'p': { // print something on display
            uint8_t timeout = 0;
            // read number for time to display
            do {
              cmd = Serial.read();
              if (cmd != ',') {
                timeout = timeout * 10 + cmd - '0';
              }
            } while (cmd != ',');
            char tmp[43] = {0};
            uint8_t pos = 0;
            // now we expect the string, terminated by \n
            do {
              cmd = Serial.read();
              if (cmd != '\n') {
                tmp[pos++] = cmd;
              }
            } while (cmd != '\n' && pos < 43 && cmd != 255);
            profile.setUserString(timeout, tmp);
          }
          break;
          case 'f': // read flash
          profile.printFlash();
          break;
        case 'F': // write flash
          profile.writeFlash();
          break;
        }
      }
    }
  }
  if (hal.m_millis < nextUpdate && hal.m_millis > ROLLOVER_INTERVAL) return;
  nextUpdate = hal.m_millis + UPDATE_INTERVAL;
  uint8_t rotaryChanged = hal.rotaryChanged();
  if (rotaryChanged) {
    for (uint8_t i = 0; i < 8; ++i) {
      if (rotaryChanged & 1) {
        int8_t delta = hal.getEncoder(i);
        profile.rotaryAction(i, delta);
      }
      rotaryChanged >>= 1;
    }
  }
  uint16_t analogChanged = hal.analogChanged();
  if (analogChanged) {
    for (uint8_t i = 0; i < 4; ++i) {
      if (analogChanged & 1) {
        uint16_t analog = hal.getAnalog(i);
        profile.axisAction(i, analog);
      }
      analogChanged >>= 1;
    }
    Gamepad.write();
  }
  uint64_t buttonsChanged = hal.buttonsChanged();
  if (buttonsChanged) {
    for (uint8_t i = 0; i < 64; ++i) {
      if (buttonsChanged & 0xFF) { // quickcheck for 8 bits at a time
        if (buttonsChanged & 1) {
          bool pressed = hal.getButton(i);
          profile.buttonAction(i, pressed);
        }
        buttonsChanged >>= 1;
      } else {
        buttonsChanged >>= 8;
        i += 7;
      }
    }
  }
}
