#include <config.h>
#include <axp20x.h>
#include <Arduino_LoRaWAN_ttn.h>
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <TinyGPS++.h>
#include <OneButton.h>

class cLoRaWAN : public Arduino_LoRaWAN_ttn
{
public:
  cLoRaWAN(){};

protected:
  virtual bool GetOtaaProvisioningInfo(Arduino_LoRaWAN::OtaaProvisioningInfo *) override;
  virtual void NetSaveFCntUp(uint32_t uFCntUp) override;
  virtual void NetSaveFCntDown(uint32_t uFCntDown) override;
  virtual void NetSaveSessionInfo(const SessionInfo &Info, const uint8_t *pExtraInfo, size_t nExtraInfo) override;
};

bool cLoRaWAN::GetOtaaProvisioningInfo(
    OtaaProvisioningInfo *pInfo)
{
  static const uint8_t DEVEUI[8] = TTN_DEVEUI;
  static const uint8_t APPEUI[8] = TTN_APPEUI;
  static const uint8_t APPKEY[16] = TTN_APPKEY;

  if (pInfo)
  {
    memcpy(pInfo->AppKey, APPKEY, sizeof(APPKEY));
    memcpy(pInfo->DevEUI, DEVEUI, sizeof(DEVEUI));
    memcpy(pInfo->AppEUI, APPEUI, sizeof(APPEUI));
  }
  return true;
}

void cLoRaWAN::NetSaveFCntDown(uint32_t uFCntDown)
{
  // save uFcntDown somwwhere
}

void cLoRaWAN::NetSaveFCntUp(uint32_t uFCntUp)
{
  // save uFCntUp somewhere
}

void cLoRaWAN::NetSaveSessionInfo(
    const SessionInfo &Info,
    const uint8_t *pExtraInfo,
    size_t nExtraInfo)
{
  // save Info somewhere.
}

const cLoRaWAN::lmic_pinmap pinMap = {
    .nss = 18,
    .rxtx = cLoRaWAN::lmic_pinmap::LMIC_UNUSED_PIN,
    .rst = 23,
    .dio = {26, 33, cLoRaWAN::lmic_pinmap::LMIC_UNUSED_PIN},
};

AXP20X_Class axp;
HardwareSerial GPS(1);
TinyGPSPlus gps;
cLoRaWAN LoRaWAN{};
OneButton btn = OneButton(BUTTON_PIN, true, true);

static uint8_t mydata[] = "Hello, world!";
static osjob_t sendjob;
const unsigned TX_INTERVAL = 60;

void setupPower()
{
  if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS))
  {
    Serial.println("AXP192 Begin PASS");
  }
  else
  {
    Serial.println("AXP192 Begin FAIL");
  }
  axp.setPowerOutPut(AXP192_DCDC1, AXP202_OFF); // OLED  : off
  axp.setPowerOutPut(AXP192_DCDC2, AXP202_OFF); // N/C   : off
  axp.setPowerOutPut(AXP192_DCDC3, AXP202_ON);  // ESP32 : on
  axp.setPowerOutPut(AXP192_LDO2,  AXP202_ON);  // LORA  : on
  axp.setPowerOutPut(AXP192_LDO3,  AXP202_ON);  // GPS   : on
  axp.setDCDC3Voltage(3300);                    // ESP32 : 3.3V
  axp.setLDO2Voltage(3300);                     // LORA  : 3.3V
  axp.setLDO3Voltage(3300);                     // GPS   : 3.3V
  axp.setChgLEDMode(AXP20X_LED_OFF);
}

void setupGps()
{
  GPS.begin(9600, SERIAL_8N1, 34, 12); // IO34: RX, IO12: TX
}

void printStatus()
{
  axp.setChgLEDMode(AXP20X_LED_LOW_LEVEL);
  while (GPS.available())
  {
    gps.encode(GPS.read());
  }
  Serial.println("##############################");
  Serial.println("--- Power ---");
  Serial.printf("DCDC1/OLED Status   : %s\n", axp.isDCDC1Enable() ? "enabled" : "disabled");
  Serial.printf("DCDC1/OLED Voltage  : %g V\n", (float)axp.getDCDC1Voltage() / 1000);
  Serial.printf("DCDC2/N/C Status    : %s\n", axp.isDCDC2Enable() ? "enabled" : "disabled");
  Serial.printf("DCDC2/N/C Voltage   : %g V\n", (float)axp.getDCDC2Voltage() / 1000);
  Serial.printf("DCDC3/ESP32 Status  : %s\n", axp.isDCDC3Enable() ? "enabled" : "disabled");
  Serial.printf("DCDC3/ESP32 Voltage : %g V\n", (float)axp.getDCDC3Voltage() / 1000);
  Serial.printf("LDO2/LoRa Status    : %s\n", axp.isLDO2Enable() ? "enabled" : "disabled");
  Serial.printf("LDO2/LoRa Voltage   : %g V\n", (float)axp.getLDO2Voltage() / 1000);
  Serial.printf("LDO3/GPS Status     : %s\n", axp.isLDO3Enable() ? "enabled" : "disabled");
  Serial.printf("LDO3/GPS Voltage    : %g V\n", (float)axp.getLDO3Voltage() / 1000);
  Serial.println("--- Battery ---");
  Serial.printf("Battery Connected: %s\n", axp.isBatteryConnect() ? "true" : "false");
  if (axp.isBatteryConnect())
  {
    Serial.printf("Battery Percentage : %d %%\n", axp.getBattPercentage());
    Serial.printf("Battery Voltage    : %g V\n", axp.getBattVoltage() / 1000);
    Serial.printf("Charging Enabled   : %s\n", axp.isChargeingEnable() ? "true" : "false");
    Serial.printf("Battery Charging   : %s\n", axp.isChargeing() ? "true" : "false");
    Serial.printf("Set Charge Current : %g mA\n", axp.getSettingChargeCurrent());
  }
  Serial.println("--- Chip ---");
  Serial.printf("Chip Temp : %.1f C°\n", axp.getTemp() / 10);
  Serial.println("--- GPS ---");
  Serial.print("Latitude   : ");
  Serial.println(gps.location.lat(), 5);
  Serial.print("Longitude  : ");
  Serial.println(gps.location.lng(), 4);
  Serial.print("Satellites : ");
  Serial.println(gps.satellites.value());
  Serial.print("Altitude   : ");
  Serial.print(gps.altitude.feet() / 3.2808);
  Serial.println("M");
  Serial.print("Time       : ");
  Serial.print(gps.time.hour());
  Serial.print(":");
  Serial.print(gps.time.minute());
  Serial.print(":");
  Serial.println(gps.time.second());
  Serial.print("Speed      : ");
  Serial.println(gps.speed.kmph());
  Serial.println("##############################");
  axp.setChgLEDMode(AXP20X_LED_OFF);
}

static void handleClick() {
  printStatus();
}

void do_send(osjob_t *j)
{
  // Check if there is not a current TX/RX job running
  if (LMIC.opmode & OP_TXRXPEND)
  {
    Serial.println(F("OP_TXRXPEND, not sending"));
  }
  else
  {
    // Prepare upstream data transmission at the next possible time.
    LMIC_setTxData2(1, mydata, sizeof(mydata) - 1, 0);
    Serial.println(F("Packet queued"));
  }
  // Next TX is scheduled after TX_COMPLETE event.
}

void onEvent(ev_t ev)
{
  Serial.print(os_getTime());
  Serial.print(": ");
  switch (ev)
  {
  case EV_SCAN_TIMEOUT:
    Serial.println(F("EV_SCAN_TIMEOUT"));
    break;
  case EV_BEACON_FOUND:
    Serial.println(F("EV_BEACON_FOUND"));
    break;
  case EV_BEACON_MISSED:
    Serial.println(F("EV_BEACON_MISSED"));
    break;
  case EV_BEACON_TRACKED:
    Serial.println(F("EV_BEACON_TRACKED"));
    break;
  case EV_JOINING:
    Serial.println(F("EV_JOINING"));
    // TTN uses SF9 for its RX2 window.
    LMIC.dn2Dr = EU868_DR_SF9;
    break;
  case EV_JOINED:
    Serial.println(F("EV_JOINED"));
    {
      u4_t netid = 0;
      devaddr_t devaddr = 0;
      u1_t nwkKey[16];
      u1_t artKey[16];
      LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
      Serial.print("netid: ");
      Serial.println(netid, DEC);
      Serial.print("devaddr: ");
      Serial.println(devaddr, HEX);
      Serial.print("artKey: ");
      for (int i = 0; i < sizeof(artKey); ++i)
      {
        Serial.print(artKey[i], HEX);
      }
      Serial.println("");
      Serial.print("nwkKey: ");
      for (int i = 0; i < sizeof(nwkKey); ++i)
      {
        Serial.print(nwkKey[i], HEX);
      }
      Serial.println("");

      LMIC_setSeqnoUp(140);
    }
    break;
  case EV_RFU1:
    Serial.println(F("EV_RFU1"));
    break;
  case EV_JOIN_FAILED:
    Serial.println(F("EV_JOIN_FAILED"));
    break;
  case EV_REJOIN_FAILED:
    Serial.println(F("EV_REJOIN_FAILED"));
    break;
    break;
  case EV_TXCOMPLETE:
    Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
    if (LMIC.txrxFlags & TXRX_ACK)
      Serial.println(F("Received ack"));
    if (LMIC.dataLen)
    {
      Serial.println(F("Received "));
      Serial.println(LMIC.dataLen);
      Serial.println(F(" bytes of payload"));
    }
    // Schedule next transmission
    os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
    break;
  case EV_LOST_TSYNC:
    Serial.println(F("EV_LOST_TSYNC"));
    break;
  case EV_RESET:
    Serial.println(F("EV_RESET"));
    break;
  case EV_RXCOMPLETE:
    // data received in ping slot
    Serial.println(F("EV_RXCOMPLETE"));
    break;
  case EV_LINK_DEAD:
    Serial.println(F("EV_LINK_DEAD"));
    break;
  case EV_LINK_ALIVE:
    Serial.println(F("EV_LINK_ALIVE"));
    break;
  case EV_SCAN_FOUND:
    Serial.println(F("EV_SCAN_FOUND"));
    break;
  case EV_TXSTART:
    Serial.println(F("EV_TXSTART"));
    break;
  case EV_TXCANCELED:
    Serial.println(F("EV_TXCANCELLED"));
    break;
  case EV_JOIN_TXCOMPLETE:
    Serial.println(F("EV_JOIN_TXCOMPLETE: no JoinAccept"));
    break;
  default:
    Serial.println(F("Unknown event " + (unsigned)ev));
    break;
  }
}

void setup()
{
  Serial.begin(115200);
  Wire.begin(21, 22);
  setupPower();
  setupGps();
  LoRaWAN.begin(pinMap);
  btn.attachClick(handleClick);
  printStatus();
  do_send(&sendjob);
}

void loop()
{
  LoRaWAN.loop();
  btn.tick();
}