/******************************************************************************
 *
 *  Copyright (C) 2014 Google, Inc.
 *  Copyright (C) 2018 The Linux Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/
#define LOG_TAG "device_iot_config"

#include "device/include/device_iot_config.h"

#include <bluetooth/log.h>
#include <com_android_bluetooth_flags.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <mutex>
#include <string>

#include "device_iot_config_int.h"
#include "internal_include/bt_target.h"
#include "osi/include/alarm.h"
#include "osi/include/allocator.h"
#include "osi/include/compat.h"
#include "osi/include/config.h"

enum ConfigSource device_iot_config_source = NOT_LOADED;

int device_iot_config_devices_loaded = -1;
char device_iot_config_time_created[TIME_STRING_LENGTH];

std::mutex config_lock;  // protects operations on |config|.
std::unique_ptr<config_t> config;
alarm_t* config_timer;

using namespace bluetooth;

bool device_iot_config_has_section(const std::string& section) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return false;
  }

  log::assert_that(config != NULL, "assert failed: config != NULL");

  std::unique_lock<std::mutex> lock(config_lock);
  return config_has_section(*config, section);
}

bool device_iot_config_exist(const std::string& section, const std::string& key) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return false;
  }

  log::assert_that(config != NULL, "assert failed: config != NULL");

  std::unique_lock<std::mutex> lock(config_lock);
  return config_has_key(*config, section, key);
}

bool device_iot_config_get_int(const std::string& section, const std::string& key, int& value) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return false;
  }

  log::assert_that(config != NULL, "assert failed: config != NULL");

  std::unique_lock<std::mutex> lock(config_lock);
  bool ret = config_has_key(*config, section, key);
  if (ret) {
    value = config_get_int(*config, section, key, value);
  }

  return ret;
}

bool device_iot_config_set_int(const std::string& section, const std::string& key, int value) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return false;
  }

  log::assert_that(config != NULL, "assert failed: config != NULL");

  std::unique_lock<std::mutex> lock(config_lock);
  char value_str[32] = {0};
  snprintf(value_str, sizeof(value_str), "%d", value);
  if (device_iot_config_has_key_value(section, key, value_str)) {
    return true;
  }

  config_set_string(config.get(), section, key, value_str);
  device_iot_config_save_async();

  return true;
}

bool device_iot_config_int_add_one(const std::string& section, const std::string& key) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return false;
  }

  log::assert_that(config != NULL, "assert failed: config != NULL");

  int result = 0;
  std::unique_lock<std::mutex> lock(config_lock);
  result = config_get_int(*config, section, key, result);
  if (result >= 0) {
    result += 1;
  } else {
    result = 0;
  }
  config_set_int(config.get(), section, key, result);
  device_iot_config_save_async();

  return true;
}

bool device_iot_config_get_hex(const std::string& section, const std::string& key, int& value) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return false;
  }

  log::assert_that(config != NULL, "assert failed: config != NULL");

  std::unique_lock<std::mutex> lock(config_lock);
  const std::string* stored_value = config_get_string(*config, section, key, NULL);
  if (!stored_value) {
    return false;
  }

  errno = 0;
  char* endptr = nullptr;
  int result = strtoul(stored_value->c_str(), &endptr, 16);
  if (stored_value->c_str() == endptr) {
    return false;
  }
  if (endptr == nullptr || endptr[0] != '\0') {
    return false;
  }
  if (errno) {
    return false;
  }

  value = result;
  return true;
}

bool device_iot_config_set_hex(const std::string& section, const std::string& key, int value,
                               int byte_num) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return false;
  }

  log::assert_that(config != NULL, "assert failed: config != NULL");

  char value_str[32] = {0};
  if (byte_num == 1) {
    snprintf(value_str, sizeof(value_str), "%02x", value);
  } else if (byte_num == 2) {
    snprintf(value_str, sizeof(value_str), "%04x", value);
  } else if (byte_num == 3) {
    snprintf(value_str, sizeof(value_str), "%06x", value);
  } else if (byte_num == 4) {
    snprintf(value_str, sizeof(value_str), "%08x", value);
  }

  std::unique_lock<std::mutex> lock(config_lock);
  if (device_iot_config_has_key_value(section, key, value_str)) {
    return true;
  }

  config_set_string(config.get(), section, key, value_str);
  device_iot_config_save_async();

  return true;
}

bool device_iot_config_set_hex_if_greater(const std::string& section, const std::string& key,
                                          int value, int byte_num) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return false;
  }

  int stored_value = 0;
  bool ret = device_iot_config_get_hex(section, key, stored_value);
  if (ret && stored_value >= value) {
    return true;
  }

  return device_iot_config_set_hex(section, key, value, byte_num);
}

bool device_iot_config_get_str(const std::string& section, const std::string& key, char* value,
                               int* size_bytes) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return false;
  }

  log::assert_that(config != NULL, "assert failed: config != NULL");
  log::assert_that(value != NULL, "assert failed: value != NULL");
  log::assert_that(size_bytes != NULL, "assert failed: size_bytes != NULL");

  std::unique_lock<std::mutex> lock(config_lock);
  const std::string* stored_value = config_get_string(*config, section, key, NULL);

  if (!stored_value) {
    return false;
  }

  osi_strlcpy(value, stored_value->c_str(), *size_bytes);
  *size_bytes = strlen(value) + 1;

  return true;
}

bool device_iot_config_set_str(const std::string& section, const std::string& key,
                               const std::string& value) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return false;
  }

  log::assert_that(config != NULL, "assert failed: config != NULL");

  std::unique_lock<std::mutex> lock(config_lock);
  if (device_iot_config_has_key_value(section, key, value)) {
    return true;
  }

  config_set_string(config.get(), section, key, value);
  device_iot_config_save_async();

  return true;
}

bool device_iot_config_get_bin(const std::string& section, const std::string& key, uint8_t* value,
                               size_t* length) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return false;
  }

  log::assert_that(config != NULL, "assert failed: config != NULL");
  log::assert_that(value != NULL, "assert failed: value != NULL");
  log::assert_that(length != NULL, "assert failed: length != NULL");

  std::unique_lock<std::mutex> lock(config_lock);
  const std::string* value_string = config_get_string(*config, section, key, NULL);

  if (!value_string) {
    return false;
  }

  const char* value_str = value_string->c_str();

  size_t value_len = strlen(value_str);
  if ((value_len % 2) != 0 || *length < (value_len / 2)) {
    return false;
  }

  for (size_t i = 0; i < value_len; ++i) {
    if (!isxdigit(value_str[i])) {
      return false;
    }
  }

  for (*length = 0; *value_str; value_str += 2, *length += 1) {
    errno = 0;
    char* endptr = nullptr;
    value[*length] = strtoul(value_str, &endptr, 16);
    if (value_str == endptr) {
      return false;
    }
    if (*endptr) {
      return false;
    }
    if (errno) {
      return false;
    }
  }

  return true;
}

size_t device_iot_config_get_bin_length(const std::string& section, const std::string& key) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return 0;
  }

  log::assert_that(config != NULL, "assert failed: config != NULL");

  std::unique_lock<std::mutex> lock(config_lock);
  const std::string* value_str = config_get_string(*config, section, key, NULL);

  if (!value_str) {
    return 0;
  }

  size_t value_len = strlen(value_str->c_str());
  return ((value_len % 2) != 0) ? 0 : (value_len / 2);
}

bool device_iot_config_set_bin(const std::string& section, const std::string& key,
                               const uint8_t* value, size_t length) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return false;
  }

  const char* lookup = "0123456789abcdef";

  log::assert_that(config != NULL, "assert failed: config != NULL");

  log::verbose("Key = {}", key);
  if (length > 0) {
    log::assert_that(value != NULL, "assert failed: value != NULL");
  }

  char* str = (char*)osi_calloc(length * 2 + 1);
  if (str == NULL) {
    log::error("Unable to allocate a str.");
    return false;
  }

  for (size_t i = 0; i < length; ++i) {
    str[(i * 2) + 0] = lookup[(value[i] >> 4) & 0x0F];
    str[(i * 2) + 1] = lookup[value[i] & 0x0F];
  }

  std::unique_lock<std::mutex> lock(config_lock);
  if (device_iot_config_has_key_value(section, key, str)) {
    osi_free(str);
    return true;
  }

  config_set_string(config.get(), section, key, str);
  device_iot_config_save_async();

  osi_free(str);
  return true;
}

bool device_iot_config_remove(const std::string& section, const std::string& key) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return false;
  }

  log::assert_that(config != NULL, "assert failed: config != NULL");

  std::unique_lock<std::mutex> lock(config_lock);
  return config_remove_key(config.get(), section, key);
}

void device_iot_config_flush(void) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return;
  }

  log::assert_that(config != NULL, "assert failed: config != NULL");
  log::assert_that(config_timer != NULL, "assert failed: config_timer != NULL");

  int event =
          alarm_is_scheduled(config_timer) ? IOT_CONFIG_SAVE_TIMER_FIRED_EVT : IOT_CONFIG_FLUSH_EVT;
  log::verbose("evt={}", event);
  alarm_cancel(config_timer);
  device_iot_config_write(event, NULL);
}

bool device_iot_config_clear(void) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return true;
  }

  log::assert_that(config != NULL, "assert failed: config != NULL");
  log::assert_that(config_timer != NULL, "assert failed: config_timer != NULL");

  log::info("");
  alarm_cancel(config_timer);

  std::unique_lock<std::mutex> lock(config_lock);
  config.reset();

  config = config_new_empty();
  if (config == NULL) {
    return false;
  }

  bool ret = config_save(*config, IOT_CONFIG_FILE_PATH);
  device_iot_config_source = RESET;
  return ret;
}

void device_debug_iot_config_dump(int fd) {
  if (!com::android::bluetooth::flags::device_iot_config_logging()) {
    return;
  }

  dprintf(fd, "\nBluetooth Iot Config:\n");

  dprintf(fd, "  Config Source: ");
  switch (device_iot_config_source) {
    case NOT_LOADED:
      dprintf(fd, "Not loaded\n");
      break;
    case ORIGINAL:
      dprintf(fd, "Original file\n");
      break;
    case BACKUP:
      dprintf(fd, "Backup file\n");
      break;
    case NEW_FILE:
      dprintf(fd, "New file\n");
      break;
    case RESET:
      dprintf(fd, "Reset file\n");
      break;
  }

  dprintf(fd, "  Devices loaded: %d\n", device_iot_config_devices_loaded);
  dprintf(fd, "  File created/tagged: %s\n", device_iot_config_time_created);
}
