#include "ble_connect/ble_connect.h"

#define B0 0x5a
#define B1 0x68
#define B2 0x5b
#define B3 0xb6

MCRYPT mcrpt;
GMainLoop *loop;
gatt_connection_t *connection;
const int data_length = 16;
uint8_t key[16] = {B0, 00, B1, 21, B2, 00, B3, 12,
                   B2, 00, B1, 68, B0, 00, B1, 88};
static uint8_t data[16];
static uuid_t g_uuid;

static uint8_t raw_data[72];

struct insight_data *data_buffer;
int data_buffer_length;
int data_index;

int status = 0;
volatile int write_to_buffer = 0;

pthread_t thread;

double raw(uint8_t h, uint8_t l) {
  return (((double)h - 128) * 32.82051289 + (double)l * -0.128205128205129 +
          4201.02564096001);
}

void notification_handler(const uuid_t *uuid, const uint8_t *val,
                          size_t values_length, void *user_data) {

  if (write_to_buffer == 0 || write_to_buffer == 2)
    return;

  timespec_get(&data_buffer[data_index].ts, TIME_UTC);

  mdecrypt_generic(mcrpt, (void *)val, data_length);

  for (int i = 0; i < 9; i++) {
    for (int k = 7; k >= 0; k--)
      raw_data[i * 8 + (7 - k)] = (val[i] >> k) & 1;
  }

  for (int i = 0; i < 5; i++) {
    uint8_t h = 0, l = 0;

    int j = -1;
    while ((++j) < 8)
      h = (h << 1) | ((uint8_t)(raw_data[(14 * i) + j]));
    j--;
    while ((++j) < 14)
      l = (l << 1) | ((uint8_t)(raw_data[(14 * i) + j]));

    data_buffer[data_index].uV[i] = raw(h, l);
  }

  data_index++;

  if (data_index == data_buffer_length)
    write_to_buffer = 2;
}

int insight_write_to_buffer(int len, struct insight_data *buf) {
  if (status != 2)
    return status;
  data_buffer = buf;
  data_buffer_length = len;
  data_index = 0;
  write_to_buffer = 1;
  return 0;
}

int insight_stop_write_to_buffer() {
  write_to_buffer = 0;
  return data_index;
}

int insight_buffer_status() { return write_to_buffer; }

void run() {
  g_main_loop_run(loop);
  pthread_exit(NULL);
}

int insight_init(const char *a, const char *b) {

  if (status != 0)
    return 1;

  mcrpt = mcrypt_module_open(MCRYPT_RIJNDAEL_128, NULL, MCRYPT_ECB, NULL);
  mcrypt_generic_init(mcrpt, (void *)key, data_length, NULL);

  if (gattlib_string_to_uuid(b, strlen(b) + 1, &g_uuid) < 0) {
    return 2;
  }

  connection = gattlib_connect(NULL, a, BDADDR_LE_PUBLIC, BT_SEC_LOW, 0, 0);

  if (connection == NULL) {
    return 3;
  }

  gattlib_register_notification(connection, notification_handler, NULL);
  loop = g_main_loop_new(NULL, 0);

  status = 1;

  return 0;
}

int insight_start_notif() {

  if (status != 1)
    return 2;

  write_to_buffer = 0;
  int ret = gattlib_notification_start(connection, &g_uuid);

  if (ret)
    return ret;

  pthread_create(&thread, NULL, (void *(*)(void *))run, NULL);

  while (!g_main_loop_is_running(loop))
    sleep(0);

  status = 2;
  return 0;
}

int insight_stop_notif() {
  if (status != 2)
    return 1;

  write_to_buffer = 0;

  g_main_loop_quit(loop);
  gattlib_notification_stop(connection, &g_uuid);
  pthread_join(thread, NULL);

  status = 1;
  return 0;
}

int insight_destroy() {
  if (status != 1)
    return status;

  g_main_loop_unref(loop);
  gattlib_disconnect(connection);
  mcrypt_generic_deinit(mcrpt);
  mcrypt_module_close(mcrpt);
  status = 0;
  return 0;
}

int insight_get_status() { return status; }

int insight_version() { return 0; }
