/**********************************************************************
 *
 *  Copyright 2017 The Android Open Source Project
 *  Copyright 2015 Intel Corporation
 *
 *  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.
 *
 **********************************************************************/
#include <base/bind.h>
#include <base/location.h>
#include <base/logging.h>
#include <base/threading/thread.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#include <sys/ioctl.h>
#include <sys/socket.h>

#include "buffer_allocator.h"
#include "hci_internals.h"
#include "hci_layer.h"
#include "osi/include/compat.h"
#include "osi/include/log.h"
#include "osi/include/osi.h"
#include "osi/include/properties.h"

using base::Thread;

#define BTPROTO_HCI 1
#define HCI_CHANNEL_USER 1
#define HCI_CHANNEL_CONTROL 3
#define HCI_DEV_NONE 0xffff

#define RFKILL_TYPE_BLUETOOTH 2
#define RFKILL_OP_CHANGE_ALL 3

#define MGMT_OP_INDEX_LIST 0x0003
#define MGMT_EV_INDEX_ADDED 0x0004
#define MGMT_EV_COMMAND_COMP 0x0001
#define MGMT_EV_SIZE_MAX 1024
#define MGMT_EV_POLL_TIMEOUT 3000 /* 3000ms */

struct sockaddr_hci {
  sa_family_t hci_family;
  unsigned short hci_dev;
  unsigned short hci_channel;
};

struct rfkill_event {
  uint32_t idx;
  uint8_t type;
  uint8_t op;
  uint8_t soft, hard;
} __attribute__((packed));

struct mgmt_pkt {
  uint16_t opcode;
  uint16_t index;
  uint16_t len;
  uint8_t data[MGMT_EV_SIZE_MAX];
} __attribute__((packed));

struct mgmt_event_read_index {
  uint16_t cc_opcode;
  uint8_t status;
  uint16_t num_intf;
  uint16_t index[0];
} __attribute__((packed));

enum HciPacketType {
  HCI_PACKET_TYPE_UNKNOWN = 0,
  HCI_PACKET_TYPE_COMMAND = 1,
  HCI_PACKET_TYPE_ACL_DATA = 2,
  HCI_PACKET_TYPE_SCO_DATA = 3,
  HCI_PACKET_TYPE_EVENT = 4
};

extern void initialization_complete();
extern void hci_event_received(const base::Location& from_here, BT_HDR* packet);
extern void acl_event_received(BT_HDR* packet);
extern void sco_data_received(BT_HDR* packet);

static int bt_vendor_fd = -1;
static int hci_interface;
static int rfkill_en;
static int wait_hcidev(void);
static int rfkill(int block);

int reader_thread_ctrl_fd = -1;
Thread* reader_thread = NULL;

void monitor_socket(int ctrl_fd, int fd) {
  const allocator_t* buffer_allocator = buffer_allocator_get_interface();
  const size_t buf_size = 2000;
  uint8_t buf[buf_size];
  ssize_t len = read(fd, buf, buf_size);

  while (len > 0) {
    if (len == buf_size)
      LOG(FATAL) << "This packet filled buffer, if it have continuation we "
                    "don't know how to merge it, increase buffer size!";

    uint8_t type = buf[0];

    size_t packet_size = buf_size + BT_HDR_SIZE;
    BT_HDR* packet =
        reinterpret_cast<BT_HDR*>(buffer_allocator->alloc(packet_size));
    packet->offset = 0;
    packet->layer_specific = 0;
    packet->len = len - 1;
    memcpy(packet->data, buf + 1, len - 1);

    switch (type) {
      case HCI_PACKET_TYPE_COMMAND:
        packet->event = MSG_HC_TO_STACK_HCI_EVT;
        hci_event_received(FROM_HERE, packet);
        break;
      case HCI_PACKET_TYPE_ACL_DATA:
        packet->event = MSG_HC_TO_STACK_HCI_ACL;
        acl_event_received(packet);
        break;
      case HCI_PACKET_TYPE_SCO_DATA:
        packet->event = MSG_HC_TO_STACK_HCI_SCO;
        sco_data_received(packet);
        break;
      case HCI_PACKET_TYPE_EVENT:
        packet->event = MSG_HC_TO_STACK_HCI_EVT;
        hci_event_received(FROM_HERE, packet);
        break;
      default:
        LOG(FATAL) << "Unexpected event type: " << +type;
        break;
    }

    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(ctrl_fd, &fds);
    FD_SET(fd, &fds);
    int res = select(std::max(fd, ctrl_fd) + 1, &fds, NULL, NULL, NULL);
    if (res <= 0) LOG(INFO) << "Nothing more to read";

    if (FD_ISSET(ctrl_fd, &fds)) {
      LOG(INFO) << "exitting";
      return;
    }

    len = read(fd, buf, buf_size);
  }
}

/* TODO: should thread the device waiting and return immedialty */
void hci_initialize() {
  LOG(INFO) << __func__;

  char prop_value[PROPERTY_VALUE_MAX];
  osi_property_get("bluetooth.interface", prop_value, "0");

  errno = 0;
  if (memcmp(prop_value, "hci", 3))
    hci_interface = strtol(prop_value, NULL, 10);
  else
    hci_interface = strtol(prop_value + 3, NULL, 10);
  if (errno) hci_interface = 0;

  LOG(INFO) << "Using interface hci" << +hci_interface;

  osi_property_get("bluetooth.rfkill", prop_value, "1");

  rfkill_en = atoi(prop_value);
  if (rfkill_en) {
    rfkill(0);
  }

  int fd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
  CHECK(fd >= 0) << "socket create error" << strerror(errno);

  bt_vendor_fd = fd;

  if (wait_hcidev()) {
    LOG(FATAL) << "HCI interface hci" << +hci_interface << " not found";
  }

  struct sockaddr_hci addr;
  memset(&addr, 0, sizeof(addr));
  addr.hci_family = AF_BLUETOOTH;
  addr.hci_dev = hci_interface;
  addr.hci_channel = HCI_CHANNEL_USER;
  if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
    PLOG(FATAL) << "socket bind error";
  }

  int sv[2];
  if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) {
    PLOG(FATAL) << "socketpair failed";
  }

  reader_thread_ctrl_fd = sv[0];
  reader_thread = new Thread("hci_sock_reader");
  reader_thread->Start();
  reader_thread->task_runner()->PostTask(
      FROM_HERE, base::Bind(&monitor_socket, sv[1], bt_vendor_fd));

  LOG(INFO) << "HCI device ready";
  initialization_complete();
}

void hci_close() {
  LOG(INFO) << __func__;

  if (bt_vendor_fd != -1) {
    close(bt_vendor_fd);
    bt_vendor_fd = -1;
  }

  if (reader_thread_ctrl_fd != -1) {
    uint8_t msg[] = {1};
    send(reader_thread_ctrl_fd, msg, sizeof(msg), 0);
    reader_thread_ctrl_fd = -1;
  }

  if (reader_thread != NULL) {
    reader_thread->Stop();
    delete reader_thread;
    reader_thread = NULL;
  }

  rfkill(1);
}

void hci_transmit(BT_HDR* packet) {
  uint8_t type = 0;

  CHECK(bt_vendor_fd != -1);

  uint16_t event = packet->event & MSG_EVT_MASK;
  switch (event & MSG_EVT_MASK) {
    case MSG_STACK_TO_HC_HCI_CMD:
      type = 1;
      break;
    case MSG_STACK_TO_HC_HCI_ACL:
      type = 2;
      break;
    case MSG_STACK_TO_HC_HCI_SCO:
      type = 3;
      break;
    default:
      LOG(FATAL) << "Unknown packet type " << event;
      break;
  }

  uint8_t* addr = packet->data + packet->offset - 1;
  uint8_t store = *addr;
  *addr = type;
  size_t ret = write(bt_vendor_fd, addr, packet->len + 1);

  *(addr) = store;

  if (ret != packet->len + 1) LOG(ERROR) << "Should have send whole packet";

  if (ret == -1) PLOG(FATAL) << "write failed";
}

static int wait_hcidev(void) {
  struct sockaddr_hci addr;
  struct pollfd fds[1];
  struct mgmt_pkt ev;
  int fd;
  int ret = 0;

  LOG(INFO) << __func__;

  fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
  if (fd < 0) {
    PLOG(ERROR) << "Bluetooth socket error";
    return -1;
  }

  memset(&addr, 0, sizeof(addr));
  addr.hci_family = AF_BLUETOOTH;
  addr.hci_dev = HCI_DEV_NONE;
  addr.hci_channel = HCI_CHANNEL_CONTROL;

  if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
    PLOG(ERROR) << "HCI Channel Control";
    close(fd);
    return -1;
  }

  fds[0].fd = fd;
  fds[0].events = POLLIN;

  /* Read Controller Index List Command */
  ev.opcode = MGMT_OP_INDEX_LIST;
  ev.index = HCI_DEV_NONE;
  ev.len = 0;

  ssize_t wrote;
  OSI_NO_INTR(wrote = write(fd, &ev, 6));
  if (wrote != 6) {
    PLOG(ERROR) << "Unable to write mgmt command";
    ret = -1;
    goto end;
  }

  while (1) {
    int n;
    OSI_NO_INTR(n = poll(fds, 1, MGMT_EV_POLL_TIMEOUT));
    if (n == -1) {
      PLOG(ERROR) << "Poll error";
      ret = -1;
      break;
    } else if (n == 0) {
      LOG(ERROR) << "Timeout, no HCI device detected";
      ret = -1;
      break;
    }

    if (fds[0].revents & POLLIN) {
      OSI_NO_INTR(n = read(fd, &ev, sizeof(struct mgmt_pkt)));
      if (n < 0) {
        PLOG(ERROR) << "Error reading control channel";
        ret = -1;
        break;
      }

      if (ev.opcode == MGMT_EV_INDEX_ADDED && ev.index == hci_interface) {
        goto end;
      } else if (ev.opcode == MGMT_EV_COMMAND_COMP) {
        struct mgmt_event_read_index* cc;
        int i;

        cc = (struct mgmt_event_read_index*)ev.data;

        if (cc->cc_opcode != MGMT_OP_INDEX_LIST || cc->status != 0) continue;

        for (i = 0; i < cc->num_intf; i++) {
          if (cc->index[i] == hci_interface) goto end;
        }
      }
    }
  }

end:
  close(fd);
  return ret;
}

static int rfkill(int block) {
  struct rfkill_event event;
  int fd;

  LOG(INFO) << __func__;

  fd = open("/dev/rfkill", O_WRONLY);
  if (fd < 0) {
    LOG(ERROR) << "Unable to open /dev/rfkill";
    return -1;
  }

  memset(&event, 0, sizeof(struct rfkill_event));
  event.op = RFKILL_OP_CHANGE_ALL;
  event.type = RFKILL_TYPE_BLUETOOTH;
  event.hard = block;
  event.soft = block;

  ssize_t len;
  OSI_NO_INTR(len = write(fd, &event, sizeof(event)));
  if (len < 0) {
    LOG(ERROR) << "Failed to change rfkill state";
    close(fd);
    return 1;
  }

  close(fd);
  return 0;
}

int hci_open_firmware_log_file() { return INVALID_FD; }

void hci_close_firmware_log_file(int fd) {}

void hci_log_firmware_debug_packet(int fd, BT_HDR* packet) {}