// Copyright 2023 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_sapphire/internal/host/transport/control_packets.h"

#include "pw_bluetooth_sapphire/internal/host/common/assert.h"
#include "pw_bluetooth_sapphire/internal/host/transport/emboss_control_packets.h"
#include "pw_bluetooth_sapphire/internal/host/transport/error.h"
#include "pw_bluetooth_sapphire/internal/host/transport/slab_allocators.h"

namespace bt::hci {
namespace {

// Limit CommandPacket template instantiations to 2 (small and large):
using SmallCommandPacket =
    allocators::internal::FixedSizePacket<hci_spec::CommandHeader,
                                          allocators::kSmallControlPacketSize>;
using LargeCommandPacket =
    allocators::internal::FixedSizePacket<hci_spec::CommandHeader,
                                          allocators::kLargeControlPacketSize>;

using EventFixedSizedPacket =
    allocators::internal::FixedSizePacket<hci_spec::EventHeader,
                                          allocators::kLargeControlPacketSize>;

// TODO(fxbug.dev/42058160): Use Pigweed's slab allocator
std::unique_ptr<CommandPacket> NewCommandPacket(size_t payload_size) {
  BT_DEBUG_ASSERT(payload_size <= allocators::kLargeControlPayloadSize);

  if (payload_size <= allocators::kSmallControlPayloadSize) {
    return std::make_unique<SmallCommandPacket>(payload_size);
  }
  return std::make_unique<LargeCommandPacket>(payload_size);
}

// Returns true and populates the |out_code| field with the status parameter.
// Returns false if |event|'s payload is too small to hold a T. T must have a
// |status| member of type pw::bluetooth::emboss::StatusCode for this to
// compile.
template <typename T>
bool StatusCodeFromEvent(const EventPacket& event,
                         pw::bluetooth::emboss::StatusCode* out_code) {
  BT_DEBUG_ASSERT(out_code);

  if (event.view().payload_size() < sizeof(T))
    return false;

  *out_code = event.params<T>().status;
  return true;
}

// For packet definitions that have been migrated to Emboss, parameterize this
// method over the Emboss view.
template <typename T>
bool StatusCodeFromEmbossEvent(const EventPacket& event,
                               pw::bluetooth::emboss::StatusCode* out_code) {
  BT_DEBUG_ASSERT(out_code);

  auto emboss_packet = EmbossEventPacket::New<T>(event.event_code());
  bt::MutableBufferView dest = emboss_packet.mutable_data();
  event.view().data().Copy(&dest);

  if (event.view().payload_size() <
      T::IntrinsicSizeInBytes().Read() -
          pw::bluetooth::emboss::EventHeader::IntrinsicSizeInBytes()) {
    return false;
  }

  *out_code = emboss_packet.view_t().status().Read();
  return true;
}

// As pw::bluetooth::emboss::StatusCodeFromEvent, but for LEMetaEvent subevents.
// Returns true and populates the |out_code| field with the subevent status
// parameter. Returns false if |event|'s payload is too small to hold a
// LEMetaEvent containing a T. T must have a |status| member of type
// pw::bluetooth::emboss::StatusCode for this to compile.
template <typename T>
bool StatusCodeFromSubevent(const EventPacket& event,
                            pw::bluetooth::emboss::StatusCode* out_code) {
  BT_ASSERT(out_code);

  if (event.view().payload_size() <
      sizeof(hci_spec::LEMetaEventParams) + sizeof(T))
    return false;

  *out_code = event.subevent_params<T>()->status;
  return true;
}

// Specialization for the CommandComplete event.
template <>
bool StatusCodeFromEvent<hci_spec::CommandCompleteEventParams>(
    const EventPacket& event, pw::bluetooth::emboss::StatusCode* out_code) {
  BT_DEBUG_ASSERT(out_code);

  const auto* params = event.return_params<hci_spec::SimpleReturnParams>();
  if (!params)
    return false;

  *out_code = params->status;
  return true;
}

}  // namespace

namespace hci_android = bt::hci_spec::vendor::android;

// static
std::unique_ptr<CommandPacket> CommandPacket::New(hci_spec::OpCode opcode,
                                                  size_t payload_size) {
  auto packet = NewCommandPacket(payload_size);
  if (!packet)
    return nullptr;

  packet->WriteHeader(opcode);
  return packet;
}

void CommandPacket::WriteHeader(hci_spec::OpCode opcode) {
  mutable_view()->mutable_header()->opcode = htole16(opcode);
  BT_ASSERT(view().payload_size() < std::numeric_limits<uint8_t>::max());
  mutable_view()->mutable_header()->parameter_total_size =
      static_cast<uint8_t>(view().payload_size());
}

// static
std::unique_ptr<EventPacket> EventPacket::New(size_t payload_size) {
  // TODO(fxbug.dev/42058160): Use Pigweed's slab allocator
  return std::make_unique<EventFixedSizedPacket>(payload_size);
}

bool EventPacket::ToStatusCode(
    pw::bluetooth::emboss::StatusCode* out_code) const {
#define CASE_EVENT_STATUS(event_name)                                    \
  case hci_spec::k##event_name##EventCode:                               \
    return StatusCodeFromEvent<hci_spec::event_name##EventParams>(*this, \
                                                                  out_code)

#define CASE_EMBOSS_EVENT_STATUS(event_name) \
  case hci_spec::k##event_name##EventCode:   \
    return StatusCodeFromEmbossEvent<        \
        pw::bluetooth::emboss::event_name##EventView>(*this, out_code)

#define CASE_SUBEVENT_STATUS(subevent_name)                                 \
  case hci_spec::k##subevent_name##SubeventCode:                            \
    return StatusCodeFromSubevent<hci_spec::subevent_name##SubeventParams>( \
        *this, out_code)

  switch (event_code()) {
    CASE_EMBOSS_EVENT_STATUS(AuthenticationComplete);
    CASE_EVENT_STATUS(ChangeConnectionLinkKeyComplete);
    CASE_EVENT_STATUS(CommandComplete);
    CASE_EVENT_STATUS(CommandStatus);
    CASE_EMBOSS_EVENT_STATUS(ConnectionComplete);
    CASE_EMBOSS_EVENT_STATUS(DisconnectionComplete);
    CASE_EMBOSS_EVENT_STATUS(RemoteNameRequestComplete);
    CASE_EVENT_STATUS(ReadRemoteSupportedFeaturesComplete);
    CASE_EVENT_STATUS(SimplePairingComplete);
    CASE_EMBOSS_EVENT_STATUS(InquiryComplete);
    case hci_spec::kEncryptionChangeEventCode:
      return StatusCodeFromEmbossEvent<
          pw::bluetooth::emboss::EncryptionChangeEventV1View>(*this, out_code);
    case hci_spec::kLEMetaEventCode: {
      auto subevent_code = params<hci_spec::LEMetaEventParams>().subevent_code;
      switch (subevent_code) {
        CASE_SUBEVENT_STATUS(LEAdvertisingSetTerminated);
        default:
          BT_PANIC("LE subevent (%#.2x) not implemented!", subevent_code);
          break;
      }
    }

      // TODO(armansito): Complete this list.

    default:
      BT_PANIC("event (%#.2x) not implemented!", event_code());
      break;
  }
  return false;

#undef CASE_EVENT_STATUS
}

hci::Result<> EventPacket::ToResult() const {
  pw::bluetooth::emboss::StatusCode code;
  if (!ToStatusCode(&code)) {
    return bt::ToResult(HostError::kPacketMalformed);
  }
  return bt::ToResult(code);
}

void EventPacket::InitializeFromBuffer() {
  mutable_view()->Resize(view().header().parameter_total_size);
}

}  // namespace bt::hci
