//
//  Copyright (C) 2015 Google, Inc.
//
//  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 "bt_bluetooth_host"

#include "service/ipc/linux_ipc_host.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>

#include <algorithm>

#include <base/base64.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>

#include "osi/include/log.h"
#include "osi/include/osi.h"
#include "service/adapter.h"

using bluetooth::Adapter;
using bluetooth::UUID;

using namespace bluetooth::gatt;

namespace {

// IPC API is according to:
// https://docs.google.com/document/d/1eRnku-jAyVU1wGJsLT2CzWi0-8bs2g49s1b3FR_GApM
const char kSetAdapterNameCommand[] = "set-device-name";
const char kCreateServiceCommand[] = "create-service";
const char kDestroyServiceCommand[] = "destroy-service";
const char kAddCharacteristicCommand[] = "add-characteristic";
const char kSetCharacteristicValueCommand[] = "set-characteristic-value";
const char kSetAdvertisementCommand[] = "set-advertisement";
const char kSetScanResponseCommand[] = "set-scan-response";
const char kStartServiceCommand[] = "start-service";
const char kStopServiceCommand[] = "stop-service";
const char kWriteCharacteristicCommand[] = "write-characteristic";

// Useful values for indexing LinuxIPCHost::pfds_
// Not super general considering that we should be able to support
// many GATT FDs owned by one LinuxIPCHost.
enum {
  kFdIpc = 0,
  kFdGatt = 1,
  kPossibleFds = 2,
};

bool TokenBool(const std::string& text) { return text == "true"; }

}  // namespace

namespace ipc {

LinuxIPCHost::LinuxIPCHost(int sockfd, Adapter* adapter)
    : adapter_(adapter), pfds_(1, {sockfd, POLLIN, 0}) {}

LinuxIPCHost::~LinuxIPCHost() { close(pfds_[0].fd); }

bool LinuxIPCHost::EventLoop() {
  while (true) {
    int status =
        TEMP_FAILURE_RETRY(ppoll(pfds_.data(), pfds_.size(), nullptr, nullptr));
    if (status < 1) {
      LOG_ERROR(LOG_TAG, "ppoll error");
      return false;
    }

    if (pfds_[kFdIpc].revents && !OnMessage()) {
      return false;
    }

    if (pfds_.size() == kPossibleFds && pfds_[kFdGatt].revents &&
        !OnGattWrite()) {
      return false;
    }
  }
  return true;
}

bool LinuxIPCHost::OnSetAdapterName(const std::string& name) {
  std::string decoded_data;
  base::Base64Decode(name, &decoded_data);
  return adapter_->SetName(decoded_data);
}

bool LinuxIPCHost::OnCreateService(const std::string& service_uuid) {
  gatt_servers_[service_uuid] = std::unique_ptr<Server>(new Server);

  int gattfd;
  bool status =
      gatt_servers_[service_uuid]->Initialize(UUID(service_uuid), &gattfd);
  if (!status) {
    LOG_ERROR(LOG_TAG, "Failed to initialize bluetooth");
    return false;
  }
  pfds_.resize(kPossibleFds);
  pfds_[kFdGatt] = {gattfd, POLLIN, 0};
  return true;
}

bool LinuxIPCHost::OnDestroyService(const std::string& service_uuid) {
  gatt_servers_.erase(service_uuid);
  close(pfds_[1].fd);
  pfds_.resize(1);
  return true;
}

bool LinuxIPCHost::OnAddCharacteristic(const std::string& service_uuid,
                                       const std::string& characteristic_uuid,
                                       const std::string& control_uuid,
                                       const std::string& options) {
  std::vector<std::string> option_tokens = base::SplitString(
      options, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);

  int properties_mask = 0;
  int permissions_mask = 0;

  if (std::find(option_tokens.begin(), option_tokens.end(), "notify") !=
      option_tokens.end()) {
    permissions_mask |= kPermissionRead;
    properties_mask |= kPropertyRead;
    properties_mask |= kPropertyNotify;
  }
  if (std::find(option_tokens.begin(), option_tokens.end(), "read") !=
      option_tokens.end()) {
    permissions_mask |= kPermissionRead;
    properties_mask |= kPropertyRead;
  }
  if (std::find(option_tokens.begin(), option_tokens.end(), "write") !=
      option_tokens.end()) {
    permissions_mask |= kPermissionWrite;
    properties_mask |= kPropertyWrite;
  }

  if (control_uuid.empty()) {
    gatt_servers_[service_uuid]->AddCharacteristic(
        UUID(characteristic_uuid), properties_mask, permissions_mask);
  } else {
    gatt_servers_[service_uuid]->AddBlob(UUID(characteristic_uuid),
                                         UUID(control_uuid), properties_mask,
                                         permissions_mask);
  }
  return true;
}

bool LinuxIPCHost::OnSetCharacteristicValue(
    const std::string& service_uuid, const std::string& characteristic_uuid,
    const std::string& value) {
  std::string decoded_data;
  base::Base64Decode(value, &decoded_data);
  std::vector<uint8_t> blob_data(decoded_data.begin(), decoded_data.end());
  gatt_servers_[service_uuid]->SetCharacteristicValue(UUID(characteristic_uuid),
                                                      blob_data);
  return true;
}

bool LinuxIPCHost::OnSetAdvertisement(const std::string& service_uuid,
                                      const std::string& advertise_uuids,
                                      const std::string& advertise_data,
                                      const std::string& manufacturer_data,
                                      const std::string& transmit_name) {
  LOG_INFO(LOG_TAG, "%s: service:%s uuids:%s data:%s", __func__,
           service_uuid.c_str(), advertise_uuids.c_str(),
           advertise_data.c_str());

  std::vector<std::string> advertise_uuid_tokens = base::SplitString(
      advertise_uuids, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);

  // string -> vector<UUID>
  std::vector<UUID> ids;
  for (const auto& uuid_token : advertise_uuid_tokens)
    ids.emplace_back(uuid_token);

  std::string decoded_data;
  base::Base64Decode(advertise_data, &decoded_data);
  std::vector<uint8_t> decoded_advertise_data(decoded_data.begin(),
                                              decoded_data.end());

  base::Base64Decode(manufacturer_data, &decoded_data);
  std::vector<uint8_t> decoded_manufacturer_data(decoded_data.begin(),
                                                 decoded_data.end());

  gatt_servers_[service_uuid]->SetAdvertisement(ids, decoded_advertise_data,
                                                decoded_manufacturer_data,
                                                TokenBool(transmit_name));
  return true;
}

bool LinuxIPCHost::OnSetScanResponse(const std::string& service_uuid,
                                     const std::string& scan_response_uuids,
                                     const std::string& scan_response_data,
                                     const std::string& manufacturer_data,
                                     const std::string& transmit_name) {
  std::vector<std::string> scan_response_uuid_tokens = base::SplitString(
      scan_response_uuids, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);

  // string -> vector<UUID>
  std::vector<UUID> ids;
  for (const auto& uuid_token : scan_response_uuid_tokens)
    ids.emplace_back(uuid_token);

  std::string decoded_data;
  base::Base64Decode(scan_response_data, &decoded_data);
  std::vector<uint8_t> decoded_advertise_data(decoded_data.begin(),
                                              decoded_data.end());

  base::Base64Decode(manufacturer_data, &decoded_data);
  std::vector<uint8_t> decoded_manufacturer_data(decoded_data.begin(),
                                                 decoded_data.end());

  gatt_servers_[service_uuid]->SetScanResponse(ids, decoded_advertise_data,
                                               decoded_manufacturer_data,
                                               TokenBool(transmit_name));
  return true;
}

bool LinuxIPCHost::OnStartService(const std::string& service_uuid) {
  return gatt_servers_[service_uuid]->Start();
}

bool LinuxIPCHost::OnStopService(const std::string& service_uuid) {
  return gatt_servers_[service_uuid]->Stop();
}

bool LinuxIPCHost::OnMessage() {
  std::string ipc_msg;
  ssize_t size;

  OSI_NO_INTR(size =
                  recv(pfds_[kFdIpc].fd, &ipc_msg[0], 0, MSG_PEEK | MSG_TRUNC));
  if (-1 == size) {
    LOG_ERROR(LOG_TAG, "Error reading datagram size: %s", strerror(errno));
    return false;
  } else if (0 == size) {
    LOG_INFO(LOG_TAG, "%s:%d: Connection closed", __func__, __LINE__);
    return false;
  }

  ipc_msg.resize(size);
  OSI_NO_INTR(size = read(pfds_[kFdIpc].fd, &ipc_msg[0], ipc_msg.size()));
  if (-1 == size) {
    LOG_ERROR(LOG_TAG, "Error reading IPC: %s", strerror(errno));
    return false;
  } else if (0 == size) {
    LOG_INFO(LOG_TAG, "%s:%d: Connection closed", __func__, __LINE__);
    return false;
  }

  std::vector<std::string> tokens = base::SplitString(
      ipc_msg, "|", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
  switch (tokens.size()) {
    case 2:
      if (tokens[0] == kSetAdapterNameCommand)
        return OnSetAdapterName(tokens[1]);
      if (tokens[0] == kCreateServiceCommand) return OnCreateService(tokens[1]);
      if (tokens[0] == kDestroyServiceCommand)
        return OnDestroyService(tokens[1]);
      if (tokens[0] == kStartServiceCommand) return OnStartService(tokens[1]);
      if (tokens[0] == kStopServiceCommand) return OnStopService(tokens[1]);
      break;
    case 4:
      if (tokens[0] == kSetCharacteristicValueCommand)
        return OnSetCharacteristicValue(tokens[1], tokens[2], tokens[3]);
      break;
    case 5:
      if (tokens[0] == kAddCharacteristicCommand)
        return OnAddCharacteristic(tokens[1], tokens[2], tokens[3], tokens[4]);
      break;
    case 6:
      if (tokens[0] == kSetAdvertisementCommand)
        return OnSetAdvertisement(tokens[1], tokens[2], tokens[3], tokens[4],
                                  tokens[5]);
      if (tokens[0] == kSetScanResponseCommand)
        return OnSetScanResponse(tokens[1], tokens[2], tokens[3], tokens[4],
                                 tokens[5]);
      break;
    default:
      break;
  }

  LOG_ERROR(LOG_TAG, "Malformed IPC message: %s", ipc_msg.c_str());
  return false;
}

bool LinuxIPCHost::OnGattWrite() {
  UUID::UUID128Bit id;
  ssize_t r;

  OSI_NO_INTR(r = read(pfds_[kFdGatt].fd, id.data(), id.size()));
  if (r != id.size()) {
    LOG_ERROR(LOG_TAG, "Error reading GATT attribute ID");
    return false;
  }

  std::vector<uint8_t> value;
  // TODO(icoolidge): Generalize this for multiple clients.
  auto server = gatt_servers_.begin();
  server->second->GetCharacteristicValue(UUID(id), &value);
  const std::string value_string(value.begin(), value.end());
  std::string encoded_value;
  base::Base64Encode(value_string, &encoded_value);

  std::string transmit(kWriteCharacteristicCommand);
  transmit += "|" + server->first;
  transmit += "|" + base::HexEncode(id.data(), id.size());
  transmit += "|" + encoded_value;

  OSI_NO_INTR(r = write(pfds_[kFdIpc].fd, transmit.data(), transmit.size()));
  if (-1 == r) {
    LOG_ERROR(LOG_TAG, "Error replying to IPC: %s", strerror(errno));
    return false;
  }

  return true;
}

}  // namespace ipc
