// Copyright 2024 The Pigweed Authors
//
// 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
//
//     https://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 "pw_bluetooth_proxy/proxy_host.h"

#include "pw_assert/check.h"
#include "pw_bluetooth/emboss_util.h"
#include "pw_bluetooth/hci_commands.emb.h"
#include "pw_bluetooth/hci_common.emb.h"
#include "pw_bluetooth/hci_data.emb.h"
#include "pw_bluetooth/hci_h4.emb.h"
#include "pw_bluetooth/l2cap_frames.emb.h"
#include "pw_bluetooth_proxy/direction.h"
#include "pw_bluetooth_proxy/h4_packet.h"
#include "pw_bluetooth_proxy/internal/gatt_notify_channel_internal.h"
#include "pw_bluetooth_proxy/internal/l2cap_coc_internal.h"
#include "pw_bluetooth_proxy/internal/logical_transport.h"
#include "pw_bluetooth_proxy/l2cap_channel_common.h"
#include "pw_log/log.h"

namespace pw::bluetooth::proxy {

ProxyHost::ProxyHost(
    pw::Function<void(H4PacketWithHci&& packet)>&& send_to_host_fn,
    pw::Function<void(H4PacketWithH4&& packet)>&& send_to_controller_fn,
    uint16_t le_acl_credits_to_reserve,
    uint16_t br_edr_acl_credits_to_reserve)
    : hci_transport_(std::move(send_to_host_fn),
                     std::move(send_to_controller_fn)),
      acl_data_channel_(hci_transport_,
                        l2cap_channel_manager_,
                        le_acl_credits_to_reserve,
                        br_edr_acl_credits_to_reserve),
      l2cap_channel_manager_(acl_data_channel_) {
  PW_LOG_INFO(
      "btproxy: ProxyHost ctor - le_acl_credits_to_reserve: %u, "
      "br_edr_acl_credits_to_reserve: %u",
      le_acl_credits_to_reserve,
      br_edr_acl_credits_to_reserve);
}

ProxyHost::~ProxyHost() {
  PW_LOG_INFO("btproxy: ProxyHost dtor");
  acl_data_channel_.Reset();
  l2cap_channel_manager_.DeregisterAndCloseChannels(
      L2capChannelEvent::kChannelClosedByOther);
}

void ProxyHost::Reset() {
  // Reset AclDataChannel first, so that send credits are reset to 0 until
  // reinitialized by controller event. This way, new channels can still be
  // registered, but they cannot erroneously use invalidated send credits.
  acl_data_channel_.Reset();
  l2cap_channel_manager_.DeregisterAndCloseChannels(L2capChannelEvent::kReset);
}

void ProxyHost::HandleH4HciFromHost(H4PacketWithH4&& h4_packet) {
  switch (h4_packet.GetH4Type()) {
    case emboss::H4PacketType::COMMAND:
      HandleCommandFromHost(std::move(h4_packet));
      return;
    case emboss::H4PacketType::EVENT:
      HandleEventFromHost(std::move(h4_packet));
      return;
    case emboss::H4PacketType::ACL_DATA:
      HandleAclFromHost(std::move(h4_packet));
      return;
    case emboss::H4PacketType::UNKNOWN:
    case emboss::H4PacketType::SYNC_DATA:
    case emboss::H4PacketType::ISO_DATA:
    default:
      hci_transport_.SendToController(std::move(h4_packet));
      return;
  }
}

void ProxyHost::HandleH4HciFromController(H4PacketWithHci&& h4_packet) {
  switch (h4_packet.GetH4Type()) {
    case emboss::H4PacketType::EVENT:
      HandleEventFromController(std::move(h4_packet));
      return;
    case emboss::H4PacketType::ACL_DATA:
      HandleAclFromController(std::move(h4_packet));
      return;
    case emboss::H4PacketType::UNKNOWN:
    case emboss::H4PacketType::COMMAND:
    case emboss::H4PacketType::SYNC_DATA:
    case emboss::H4PacketType::ISO_DATA:
    default:
      hci_transport_.SendToHost(std::move(h4_packet));
      return;
  }
}

void ProxyHost::HandleEventFromController(H4PacketWithHci&& h4_packet) {
  pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
  Result<emboss::EventHeaderView> event =
      MakeEmbossView<emboss::EventHeaderView>(hci_buffer);
  if (!event.ok()) {
    PW_LOG_ERROR(
        "Buffer is too small for EventHeader. So will pass on to host without "
        "processing.");
    hci_transport_.SendToHost(std::move(h4_packet));
    return;
  }

  PW_MODIFY_DIAGNOSTICS_PUSH();
  PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum");
  switch (event->event_code().Read()) {
    case emboss::EventCode::NUMBER_OF_COMPLETED_PACKETS: {
      acl_data_channel_.HandleNumberOfCompletedPacketsEvent(
          std::move(h4_packet));
      break;
    }
    case emboss::EventCode::DISCONNECTION_COMPLETE: {
      acl_data_channel_.ProcessDisconnectionCompleteEvent(
          h4_packet.GetHciSpan());
      hci_transport_.SendToHost(std::move(h4_packet));
      break;
    }
    case emboss::EventCode::COMMAND_COMPLETE: {
      HandleCommandCompleteEvent(std::move(h4_packet));
      break;
    }
    case emboss::EventCode::CONNECTION_COMPLETE: {
      acl_data_channel_.HandleConnectionCompleteEvent(std::move(h4_packet));
      break;
    }
    case emboss::EventCode::LE_META_EVENT: {
      HandleLeMetaEvent(std::move(h4_packet));
      break;
    }
    default: {
      hci_transport_.SendToHost(std::move(h4_packet));
      return;
    }
  }
  PW_MODIFY_DIAGNOSTICS_POP();

  l2cap_channel_manager_.DeliverPendingEvents();
}

void ProxyHost::HandleEventFromHost(H4PacketWithH4&& h4_packet) {
  pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
  Result<emboss::EventHeaderView> event =
      MakeEmbossView<emboss::EventHeaderView>(hci_buffer);
  if (!event.ok()) {
    PW_LOG_ERROR(
        "Buffer is too small for EventHeader. So will pass on to controller "
        "without processing.");
    hci_transport_.SendToController(std::move(h4_packet));
    return;
  }

  PW_MODIFY_DIAGNOSTICS_PUSH();
  PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum");
  switch (event->event_code().Read()) {
    case emboss::EventCode::DISCONNECTION_COMPLETE: {
      acl_data_channel_.ProcessDisconnectionCompleteEvent(
          h4_packet.GetHciSpan());
      hci_transport_.SendToController(std::move(h4_packet));
      break;
    }
    default: {
      hci_transport_.SendToController(std::move(h4_packet));
      return;
    }
  }
  PW_MODIFY_DIAGNOSTICS_POP();
}

void ProxyHost::HandleAclFromController(H4PacketWithHci&& h4_packet) {
  pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();

  Result<emboss::AclDataFrameWriter> acl =
      MakeEmbossWriter<emboss::AclDataFrameWriter>(hci_buffer);
  if (!acl.ok()) {
    PW_LOG_ERROR(
        "Buffer is too small for ACL header. So will pass on to host.");
    hci_transport_.SendToHost(std::move(h4_packet));
    return;
  }

  if (!acl_data_channel_.HandleAclData(Direction::kFromController, *acl)) {
    hci_transport_.SendToHost(std::move(h4_packet));
    return;
  }
}

void ProxyHost::HandleLeMetaEvent(H4PacketWithHci&& h4_packet) {
  pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
  Result<emboss::LEMetaEventView> le_meta_event_view =
      MakeEmbossView<emboss::LEMetaEventView>(hci_buffer);
  if (!le_meta_event_view.ok()) {
    PW_LOG_ERROR(
        "Buffer is too small for LE_META_EVENT event. So will not process.");
    hci_transport_.SendToHost(std::move(h4_packet));
    return;
  }

  PW_MODIFY_DIAGNOSTICS_PUSH();
  PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum");
  switch (le_meta_event_view->subevent_code_enum().Read()) {
    case emboss::LeSubEventCode::CONNECTION_COMPLETE: {
      acl_data_channel_.HandleLeConnectionCompleteEvent(std::move(h4_packet));
      return;
    }
    case emboss::LeSubEventCode::ENHANCED_CONNECTION_COMPLETE_V1: {
      acl_data_channel_.HandleLeEnhancedConnectionCompleteV1Event(
          std::move(h4_packet));
      return;
    }
    case emboss::LeSubEventCode::ENHANCED_CONNECTION_COMPLETE_V2: {
      acl_data_channel_.HandleLeEnhancedConnectionCompleteV2Event(
          std::move(h4_packet));
      return;
    }
    default:
      break;
  }
  PW_MODIFY_DIAGNOSTICS_POP();
  hci_transport_.SendToHost(std::move(h4_packet));
}

void ProxyHost::HandleCommandCompleteEvent(H4PacketWithHci&& h4_packet) {
  pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
  Result<emboss::CommandCompleteEventView> command_complete_event =
      MakeEmbossView<emboss::CommandCompleteEventView>(hci_buffer);
  if (!command_complete_event.ok()) {
    PW_LOG_ERROR(
        "Buffer is too small for COMMAND_COMPLETE event. So will not process.");
    hci_transport_.SendToHost(std::move(h4_packet));
    return;
  }

  PW_MODIFY_DIAGNOSTICS_PUSH();
  PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum");
  switch (command_complete_event->command_opcode().Read()) {
    case emboss::OpCode::READ_BUFFER_SIZE: {
      Result<emboss::ReadBufferSizeCommandCompleteEventWriter> read_event =
          MakeEmbossWriter<emboss::ReadBufferSizeCommandCompleteEventWriter>(
              hci_buffer);
      if (!read_event.ok()) {
        PW_LOG_ERROR(
            "Buffer is too small for READ_BUFFER_SIZE command complete event. "
            "Will not process.");
        break;
      }
      acl_data_channel_.ProcessReadBufferSizeCommandCompleteEvent(*read_event);
      break;
    }
    case emboss::OpCode::LE_READ_BUFFER_SIZE_V1: {
      Result<emboss::LEReadBufferSizeV1CommandCompleteEventWriter> read_event =
          MakeEmbossWriter<
              emboss::LEReadBufferSizeV1CommandCompleteEventWriter>(hci_buffer);
      if (!read_event.ok()) {
        PW_LOG_ERROR(
            "Buffer is too small for LE_READ_BUFFER_SIZE_V1 command complete "
            "event. So will not process.");
        break;
      }
      acl_data_channel_.ProcessLEReadBufferSizeCommandCompleteEvent(
          *read_event);
      break;
    }
    case emboss::OpCode::LE_READ_BUFFER_SIZE_V2: {
      Result<emboss::LEReadBufferSizeV2CommandCompleteEventWriter> read_event =
          MakeEmbossWriter<
              emboss::LEReadBufferSizeV2CommandCompleteEventWriter>(hci_buffer);
      if (!read_event.ok()) {
        PW_LOG_ERROR(
            "Buffer is too small for LE_READ_BUFFER_SIZE_V2 command complete "
            "event. So will not process.");
        break;
      }
      acl_data_channel_.ProcessLEReadBufferSizeCommandCompleteEvent(
          *read_event);
      break;
    }
    default:
      break;
  }
  PW_MODIFY_DIAGNOSTICS_POP();
  hci_transport_.SendToHost(std::move(h4_packet));
}

void ProxyHost::HandleCommandFromHost(H4PacketWithH4&& h4_packet) {
  pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
  Result<emboss::GenericHciCommandView> command =
      MakeEmbossView<emboss::GenericHciCommandView>(hci_buffer);
  if (!command.ok()) {
    hci_transport_.SendToController(std::move(h4_packet));
    return;
  }

  if (command->header().opcode().Read() == emboss::OpCode::RESET) {
    PW_LOG_INFO("Resetting proxy on HCI_Reset Command from host.");
    Reset();
  }

  hci_transport_.SendToController(std::move(h4_packet));
}

void ProxyHost::HandleAclFromHost(H4PacketWithH4&& h4_packet) {
  pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();

  Result<emboss::AclDataFrameWriter> acl =
      MakeEmbossWriter<emboss::AclDataFrameWriter>(hci_buffer);
  if (!acl.ok()) {
    PW_LOG_ERROR(
        "Buffer is too small for ACL header. So will pass on to controller.");
    hci_transport_.SendToController(std::move(h4_packet));
    return;
  }

  if (!acl_data_channel_.HandleAclData(Direction::kFromHost, *acl)) {
    hci_transport_.SendToController(std::move(h4_packet));
    return;
  }
}

pw::Result<L2capCoc> ProxyHost::AcquireL2capCoc(
    pw::multibuf::MultiBufAllocator& rx_multibuf_allocator,
    uint16_t connection_handle,
    L2capCoc::CocConfig rx_config,
    L2capCoc::CocConfig tx_config,
    Function<void(multibuf::MultiBuf&& payload)>&& receive_fn,
    ChannelEventCallback&& event_fn) {
  Status status = acl_data_channel_.CreateAclConnection(connection_handle,
                                                        AclTransportType::kLe);
  if (status.IsResourceExhausted()) {
    return pw::Status::Unavailable();
  }
  PW_CHECK(status.ok() || status.IsAlreadyExists());

  L2capSignalingChannel* signaling_channel =
      acl_data_channel_.FindSignalingChannel(
          connection_handle,
          static_cast<uint16_t>(emboss::L2capFixedCid::LE_U_SIGNALING));
  PW_CHECK(signaling_channel);
  return L2capCocInternal::Create(rx_multibuf_allocator,
                                  l2cap_channel_manager_,
                                  signaling_channel,
                                  connection_handle,
                                  rx_config,
                                  tx_config,
                                  std::move(event_fn),
                                  /*receive_fn=*/std::move(receive_fn));
}

pw::Status ProxyHost::SendAdditionalRxCredits(uint16_t connection_handle,
                                              uint16_t local_cid,
                                              uint16_t additional_rx_credits) {
  // If value, includes unique_lock of channels_mutex_.
  std::optional<LockedL2capChannel> channel =
      l2cap_channel_manager_.FindChannelByLocalCid(connection_handle,
                                                   local_cid);
  PW_CHECK(channel.has_value());
  return static_cast<L2capCoc&>(channel->channel())
      .SendAdditionalRxCredits(additional_rx_credits);
}

pw::Result<BasicL2capChannel> ProxyHost::AcquireBasicL2capChannel(
    multibuf::MultiBufAllocator& rx_multibuf_allocator,
    uint16_t connection_handle,
    uint16_t local_cid,
    uint16_t remote_cid,
    AclTransportType transport,
    OptionalPayloadReceiveCallback&& payload_from_controller_fn,
    OptionalPayloadReceiveCallback&& payload_from_host_fn,
    ChannelEventCallback&& event_fn) {
  Status status =
      acl_data_channel_.CreateAclConnection(connection_handle, transport);
  if (status.IsResourceExhausted()) {
    return pw::Status::Unavailable();
  }
  PW_CHECK(status.ok() || status.IsAlreadyExists());
  return BasicL2capChannel::Create(l2cap_channel_manager_,
                                   &rx_multibuf_allocator,
                                   /*connection_handle=*/connection_handle,
                                   /*transport=*/transport,
                                   /*local_cid=*/local_cid,
                                   /*remote_cid=*/remote_cid,
                                   /*payload_from_controller_fn=*/
                                   std::move(payload_from_controller_fn),
                                   /*payload_from_host_fn=*/
                                   std::move(payload_from_host_fn),
                                   /*event_fn=*/std::move(event_fn));
}

pw::Result<GattNotifyChannel> ProxyHost::AcquireGattNotifyChannel(
    int16_t connection_handle,
    uint16_t attribute_handle,
    ChannelEventCallback&& event_fn) {
  Status status = acl_data_channel_.CreateAclConnection(connection_handle,
                                                        AclTransportType::kLe);
  if (status != OkStatus() && status != Status::AlreadyExists()) {
    return pw::Status::Unavailable();
  }
  return GattNotifyChannelInternal::Create(l2cap_channel_manager_,
                                           connection_handle,
                                           attribute_handle,
                                           std::move(event_fn));
}

pw::Result<RfcommChannel> ProxyHost::AcquireRfcommChannel(
    multibuf::MultiBufAllocator& rx_multibuf_allocator,
    uint16_t connection_handle,
    RfcommChannel::Config rx_config,
    RfcommChannel::Config tx_config,
    uint8_t channel_number,
    Function<void(multibuf::MultiBuf&& payload)>&& payload_from_controller_fn,
    ChannelEventCallback&& event_fn) {
  Status status = acl_data_channel_.CreateAclConnection(
      connection_handle, AclTransportType::kBrEdr);
  if (status != OkStatus() && status != Status::AlreadyExists()) {
    return pw::Status::Unavailable();
  }
  return RfcommChannel::Create(l2cap_channel_manager_,
                               rx_multibuf_allocator,
                               connection_handle,
                               rx_config,
                               tx_config,
                               channel_number,
                               std::move(payload_from_controller_fn),
                               std::move(event_fn));
}

bool ProxyHost::HasSendLeAclCapability() const {
  return acl_data_channel_.HasSendAclCapability(AclTransportType::kLe);
}

bool ProxyHost::HasSendBrEdrAclCapability() const {
  return acl_data_channel_.HasSendAclCapability(AclTransportType::kBrEdr);
}

uint16_t ProxyHost::GetNumFreeLeAclPackets() const {
  return acl_data_channel_.GetNumFreeAclPackets(AclTransportType::kLe);
}

uint16_t ProxyHost::GetNumFreeBrEdrAclPackets() const {
  return acl_data_channel_.GetNumFreeAclPackets(AclTransportType::kBrEdr);
}

void ProxyHost::RegisterL2capStatusDelegate(L2capStatusDelegate& delegate) {
  l2cap_channel_manager_.RegisterStatusDelegate(delegate);
}

void ProxyHost::UnregisterL2capStatusDelegate(L2capStatusDelegate& delegate) {
  l2cap_channel_manager_.UnregisterStatusDelegate(delegate);
}

}  // namespace pw::bluetooth::proxy
