#include "BTSerial.h"
#include <esp_task_wdt.h>
#include <ArduinoJson.h>
#include <FS.h>
#include <LittleFS.h>
#include <NimBLEDevice.h>
#include <string>
#include "command_processor.h"
#include "led_strip.h"
#include "global.h"
#include "JsonConstrain.h"
#include "my_buzzer.h"
#include "common/led_animation.h"


static const char* tag = "ble";
TaskHandle_t BTSerial_Task_Handle;
bool BTDeviceConnected = false;

BLEServer *pServer = NULL;
BLECharacteristic * pTxCharacteristic;
BLECharacteristic * pRxCharacteristic;
bool oldDeviceConnected = false;
uint8_t txValue = 0;

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID_DEF           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX_DEF "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX_DEF "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

String BLEDeviceName;
String BLEKey;
String SERVICE_UUID;       
String CHARACTERISTIC_UUID_RX; 
String CHARACTERISTIC_UUID_TX;

#define replyActive true


void Init_BTSerial(void)
{
  File file = LittleFS.open("/cfg/ble.json");
  if(!file){
    ESP_LOGE(tag, "Error opening ble.json...");
  }
  else{ 
    JsonDocument doc;
    DeserializationError error = deserializeJson(doc, file);
    file.close();
    if(error){ ESP_LOGE(tag, "ble.json deserialize error!.."); return;}

    JsonObject bleJson = doc.as<JsonObject>();

   // if(jsonConstrainBool(bleJson, "en", false)){
      SERVICE_UUID = jsonConstrainString(tag, bleJson, "service-uuid", SERVICE_UUID_DEF);
      ESP_LOGD(tag, "SERVICE_UUID: %s", SERVICE_UUID.c_str());

      CHARACTERISTIC_UUID_RX = jsonConstrainString(tag, bleJson, "char-uuid-rx", CHARACTERISTIC_UUID_RX_DEF);
      ESP_LOGD(tag, "Char UUID RX: %s", CHARACTERISTIC_UUID_RX.c_str());

      CHARACTERISTIC_UUID_TX = jsonConstrainString(tag, bleJson, "char-uuid-tx", CHARACTERISTIC_UUID_TX_DEF);
      ESP_LOGD(tag, "Char UUID TX: %s", CHARACTERISTIC_UUID_TX.c_str());

      BLEDeviceName = jsonConstrainString(tag, bleJson, "device-name", "ATA_COMM");
      String hexStr = String(chipInfo.macByte[0], HEX);
      hexStr.toUpperCase();
      BLEDeviceName += '_'; 
      BLEDeviceName += hexStr;

      BLEKey = jsonConstrainString(tag, bleJson, "key", "123456");
      int core = jsonConstrain<int>(tag, bleJson, "core", 0, 1, 0);  
      ESP_LOGD(tag, "BLE SSID: %s, key: %s, core: %d", BLEDeviceName.c_str(),BLEKey.c_str(), core);

      xTaskCreatePinnedToCore(BTSerial_Task, "BTSerial_Task", 12000, NULL, 1, &BTSerial_Task_Handle, core);
    //}
  }
}

/**  None of these are required as they will be handled by the library with defaults. **
 **                       Remove as you see fit for your needs                        */  
class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      BTDeviceConnected = true;
      //BLEDevice::startAdvertising();//adding this line allows for multiple simultaneous BLE connections
    };
    /*
    void onConnect(BLEServer* pServer, BLEClient* pClient) {
      BTDeviceConnected = true;
      BLEAddress connectedAddress = pClient->getPeerAddress();
      Log.traceln("Client connected: %s", connectedAddress.toString().c_str());
    }
    */

    void onDisconnect(BLEServer* pServer) {
      BTDeviceConnected = false;
    }
  /***************** New - Security handled here ********************
  ****** Note: these are the same return values as defaults ********/
    uint32_t onPassKeyRequest(){
      ESP_LOGD(tag, "Server PassKeyRequest");
      return 123456; 
    }

    bool onConfirmPIN(uint32_t pass_key){
      ESP_LOGD(tag, "The passkey YES/NO number: %d", pass_key);
      return true; 
    }

    void onAuthenticationComplete(ble_gap_conn_desc desc){
      ESP_LOGD(tag, "Starting BLE work!");
    }
  /*******************************************************************/
};

#define MAX_PACKET_PARAMS 8
int packet_data[MAX_PACKET_PARAMS];
int paramCount = 0;
class MyCallbacks: public BLECharacteristicCallbacks 
{
  void onWrite(BLECharacteristic *pCharacteristic) 
  {
    std::string rxValue = pCharacteristic->getValue(); 

    ESP_LOGD(tag, "raw: %s", rxValue.c_str());
    if(!rxValue.empty() && ((rxValue[0] == '$') || rxValue[0] == '%')){
      extractCommand(&rxValue[0], packet_data, &paramCount); // delimited command

      // Call Animation Index Update
      if(packet_data[0] == 100){
        int cntDown = 0;
        if(packet_data[2]){
          cntDown = packet_data[2];
        }
        animProps.event[packet_data[1]].countDown = cntDown;
        animProps.event[packet_data[1]].type = EV_NORMAL;
        PostNewEvent(animProps.event[packet_data[1]]);
      }else{
        // TODO: Process other Bluetooth commands
      }

      if(replyActive){
        rxValue[0] = '%';
        pTxCharacteristic->setValue(rxValue);
        pTxCharacteristic->notify();
      }
      // print params
      ESP_LOGD(tag, "packet: %c%d, %d", rxValue[0], packet_data[0], packet_data[1]);
    }
  }
};

void extractCommand(char* packet, int* data, int* count) 
{
  int base = 10;
  //if(*packet == '#'){
  //  base = 16;
  //}
  packet++; 

  char* token = strtok(packet, ",\n");
  int8_t index = 0;

  while (token != NULL && index < MAX_PACKET_PARAMS) {
    data[index] = strtol(token, NULL, base);
    index++;
    token = strtok(NULL, ",\n");
  }
  *count = index;
}

void BTSerial_Task(void *parameters){
  vTaskDelay(1000);
  // Extend watchdog timer
  esp_task_wdt_init(5, false);

  // Create the BLE Device.
  BLEDevice::init(BLEDeviceName.c_str());

  // Create the BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService((const char*)SERVICE_UUID.c_str());

  // Create a BLE Characteristic                          
  pTxCharacteristic = pService->createCharacteristic( (const char*)CHARACTERISTIC_UUID_TX.c_str(), NIMBLE_PROPERTY::NOTIFY );
  /***************************************************   
   NOTE: DO NOT create a 2902 descriptor, it will be created auto.. if notifications 
   or indications are enabled on a characteristic.
   
   pCharacteristic->addDescriptor(new BLE2902());
  ****************************************************/                
  pRxCharacteristic = pService->createCharacteristic( (const char*)CHARACTERISTIC_UUID_RX.c_str(), NIMBLE_PROPERTY::WRITE );
  pRxCharacteristic->setCallbacks(new MyCallbacks());
  // Start the service
  pService->start();

  // Start advertising
  vTaskDelay(100);
  pServer->getAdvertising()->start();
  ESP_LOGV(tag, "Waiting for a client...");
  ANIMATION_EVENT newEvent;
  for(;;){
    //if (BTDeviceConnected) {
      //pTxCharacteristic->setValue(&txValue, 1);
      //pTxCharacteristic->notify();
      //txValue++;
      //vTaskDelay(100); // bluetooth stack will go into congestion, if too many packets are sent
    //}

    // disconnecting
    if (!BTDeviceConnected && oldDeviceConnected) {
      vTaskDelay(750); // give the bluetooth stack the chance to get things ready
      pServer->startAdvertising(); // restart advertising
      oldDeviceConnected = BTDeviceConnected;
      newEvent.animIndex = POP_BLE_DISC;
      newEvent.type = EV_INJECT;
      PostNewEvent(newEvent); 
      ESP_LOGI(tag, "Client disconnected...");
      ESP_LOGI(tag, "Advertising again...");
      Buzzer_Play_Tune(TUNE_BLE_DISCONNECTED);
    }

    // connecting
    if (BTDeviceConnected && !oldDeviceConnected) {
      // do stuff here on connecting
      oldDeviceConnected = BTDeviceConnected;
      newEvent.animIndex = POP_BLE_CONN;
      newEvent.type = EV_INJECT;
      PostNewEvent(newEvent); 
      ESP_LOGI(tag, "Client connected...");
      Buzzer_Play_Tune(TUNE_BLE_CONNECTED);
    }

    vTaskDelay(200);
  }

}