#include <Arduino.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <SPI.h>
#include <Wire.h>
#include <EEPROM.h>
#include <LoRaWAN.h>

// OLED
#include <U8x8lib.h>
U8X8_SSD1306_128X64_NONAME_HW_I2C oled(U8X8_PIN_NONE, PIN_PB0, PIN_PB1);

// GPS
#include <gps.h>
Gps gps;
bool hasFix = false;
bool oldFix = false;

// Button Handling
#include <Button2.h>
Button2 btn = Button2(PIN_PC2);
bool adhocsend = false;

// Modes for Display Menus and Navigation
enum Modes {
  M_NORMAL,
  M_SETUP
};

enum Settings {
  S_NONE,
  S_INTERVAL,
  S_KEYSET,
  S_EXIT
};

Modes amode = M_NORMAL; // Active Mode
Modes omode = M_NORMAL; // Previous Mode
Settings cset = S_INTERVAL; // Current Setting
Settings aset = S_NONE; // Selected Setting

#define EEPROM_KEYSET_ADR 0
#define EEPROM_INTERVAL_ADR 1

// LoRa
#include "config.h" // Contains LoRa ABP Keys
#define DIO0 PIN_PA4
#define NSS  PIN_PA5
RFM95 rfm(DIO0, NSS);
LoRaWAN lora = LoRaWAN(rfm);
uint16_t Frame_Counter_Tx = 0x0001;

// Some Status Variables
uint8_t interval = 20; // Sending Interval in Settings
uint8_t packets  = 0;  // Sent LoRa Packets
uint8_t keyset   = 1;  // LoRa KeySet
String statusmsg = "";

uint8_t loraBuffer[9]; //  Lora Data Packet
uint32_t lastmillis;

// Supply Voltage
uint16_t readSupplyVoltage() {
  uint16_t temp = 0;
  analogReference(VDD);
  VREF.CTRLA = VREF_ADC0REFSEL_1V5_gc;
  ADC0.CTRLD = ADC_INITDLY_DLY256_gc;
  ADC0_CTRLB = ADC_SAMPNUM_ACC64_gc;
  uint16_t reading = analogRead(ADC_INTREF);
  temp = reading / 64;
  uint32_t intermediate = 1534500;
  reading = 0;
  reading = intermediate / temp;
  return reading;
}

void loraKeySetup(uint8_t keyset) {
  if (keyset == 1)
    lora.setKeys(NwkSkey_1, AppSkey_1, DevAddr_1);
  if (keyset == 2)
    lora.setKeys(NwkSkey_2, AppSkey_2, DevAddr_2);
}

// Button Handler Function
void handler(Button2& btn) {
  switch (btn.getClickType()) {
    case SINGLE_CLICK:
      if (amode == M_SETUP) {
        if (aset == S_INTERVAL) {
          interval = interval += 10;
          if (interval > 30)
            interval = 10;
        } else if (aset == S_KEYSET) {
          keyset = keyset + 1;
          if  (keyset > 2)
            keyset = 1;
          loraKeySetup(keyset);
        } else {
          switch (cset) {
            case S_INTERVAL:
              cset = S_KEYSET;
              break;
            case S_KEYSET:
              cset = S_EXIT;
              break;
            case S_EXIT:
              cset = S_INTERVAL;
              break;
          }
        }
      } else if (amode == M_NORMAL) {
        statusmsg = "Adhoc Request";
        adhocsend = true;
      }
      break;
    case LONG_CLICK:
      switch (amode) {
        case M_NORMAL:
          amode = M_SETUP;
          cset = S_INTERVAL;
          break;
        case M_SETUP:
          if (cset == S_EXIT) {
            aset = S_NONE;
            amode = M_NORMAL;
            EEPROM.update(EEPROM_KEYSET_ADR, keyset);
            EEPROM.update(EEPROM_INTERVAL_ADR, interval);
          } else if (aset == S_NONE) {
            aset = cset;
          } else {
            aset = S_NONE;
          }
          break;
      }
      break;
  }
}

// Draw the Display
void updateDisplay() {
  uint16_t buffer[6];
  gps.gdisplay(buffer);
  
  oled.home();
  oled.setFont(u8x8_font_victoriabold8_r);
  
  if (amode != omode) {
    oled.clear();
    omode = amode;
  }

  if (amode == M_SETUP) {
    oled.println ("<< SETUP >>");
    oled.println ("");
    char tmp = ' ';

    tmp = (cset == S_INTERVAL) ? '>' : ' ';
    oled.print(tmp);
    tmp = (aset == S_INTERVAL) ? '>' : ' ';
    oled.print(tmp);
    oled.print("Interval: ");
    oled.println(interval);
    
    tmp = (cset == S_KEYSET) ? '>' : ' ';
    oled.print(tmp);
    tmp = (aset == S_KEYSET) ? '>' : ' ';
    oled.print(tmp);
    oled.print("ABP KeySet: ");
    oled.println(keyset);
    tmp = (cset == S_EXIT) ? '>' : ' ';
    oled.print(tmp);
    oled.println(" Exit Setup");

  } else if (amode == M_NORMAL) {
    if (hasFix) {
      if (hasFix != oldFix){
        oled.clear();
        oldFix = hasFix;
      }
      oled.print("HDOP:   ");
      oled.println(buffer[0]);
      oled.print("Sats:   ");
      oled.print(buffer[1]);
      oled.print("/");
      oled.println(buffer[2]);
      oled.print("Int:    ");
      oled.println(interval);
      oled.print("Packet: ");
      oled.println(packets);
      oled.print("Alt:    ");
      oled.println(buffer[3]);
      oled.print("Speed:  ");
      oled.println(buffer[4]);
      oled.print("Bat:    ");
      oled.println((float)readSupplyVoltage()/1000);
    } else {
      if (hasFix != oldFix){
        oled.clear();
        oldFix = hasFix;
      }
      oled.println("NO GPS FIX");
      oled.print("Sats:   ");
      oled.print(buffer[1]);
      oled.print("/");
      oled.println(buffer[2]);
      oled.print("Int:    ");
      oled.println(interval);
      oled.print("Packet: ");
      oled.println(packets);
      oled.print("Bat:    ");
      oled.println((float)readSupplyVoltage()/1000);
    }
    oled.print(statusmsg);
    oled.println("                    ");
  }
}

// Setup
void setup() {
  // GPS Setup
  Serial.begin(9600);

  // Button
  btn.setLongClickTime(1000);
  btn.setClickHandler(handler);
  btn.setLongClickHandler(handler);

  // OLED Setup
  Wire.begin();
  Wire.setClock(400000L);
  oled.begin();
  oled.setPowerSave(0);
  oled.setFlipMode(1);
  oled.clear();
  
  // Get Settings from EEPROM

  EEPROM.get(EEPROM_KEYSET_ADR, keyset);
  if (keyset > 2) keyset = 1;
  if (keyset < 1) keyset = 1;
  EEPROM.get(EEPROM_INTERVAL_ADR, interval);
  if (interval < 10 || interval > 30) interval = 20;

  // Lora Initialization
  rfm.init();
  loraKeySetup(keyset);
}

// Main Loop
void loop() {
  hasFix = gps.checkGpsFix();
  uint32_t curmillis = millis();
  if (hasFix && (((uint32_t)(curmillis - lastmillis) >= interval*1000) || adhocsend)) {
    if (adhocsend) {
      statusmsg = "Sending (Adhoc)";
    } else {
      statusmsg = "Sending";
    }
    updateDisplay();
    adhocsend = false;
    
    gps.buildPacket(loraBuffer);
    lora.Send_Data((unsigned char *)&loraBuffer, sizeof(loraBuffer), Frame_Counter_Tx, SF7BW125, 0x01);
    packets++;
    Frame_Counter_Tx++;
    lastmillis = millis();
  } else {
    updateDisplay();
  }
  statusmsg = "";
  btn.loop();
}