// 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/l2cap/a2dp_offload_manager.h"

#include <pw_bluetooth/hci_android.emb.h>

#include <cstdint>
#include <utility>

#include "pw_bluetooth_sapphire/internal/host/common/host_error.h"
#include "pw_bluetooth_sapphire/internal/host/hci-spec/constants.h"
#include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
#include "pw_bluetooth_sapphire/internal/host/hci-spec/vendor_protocol.h"
#include "pw_bluetooth_sapphire/internal/host/l2cap/channel.h"
#include "pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
#include "pw_bluetooth_sapphire/internal/host/transport/control_packets.h"
#include "pw_bluetooth_sapphire/internal/host/transport/emboss_control_packets.h"

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

void A2dpOffloadManager::StartA2dpOffload(
    const Configuration& config,
    ChannelId local_id,
    ChannelId remote_id,
    hci_spec::ConnectionHandle link_handle,
    uint16_t max_tx_sdu_size,
    hci::ResultCallback<> callback) {
  BT_DEBUG_ASSERT(cmd_channel_.is_alive());

  switch (a2dp_offload_status_) {
    case A2dpOffloadStatus::kStarted: {
      bt_log(WARN,
             "l2cap",
             "Only one channel can offload A2DP at a time; already offloaded "
             "(handle: %#.4x, local id: %#.4x",
             *offloaded_link_handle_,
             *offloaded_channel_id_);
      callback(ToResult(HostError::kInProgress));
      return;
    }
    case A2dpOffloadStatus::kStarting: {
      bt_log(WARN,
             "l2cap",
             "A2DP offload is currently starting (status: %hhu)",
             static_cast<unsigned char>(a2dp_offload_status_));
      callback(ToResult(HostError::kInProgress));
      return;
    }
    case A2dpOffloadStatus::kStopping: {
      bt_log(WARN,
             "l2cap",
             "A2DP offload is stopping... wait until stopped before starting "
             "(status: %hhu)",
             static_cast<unsigned char>(a2dp_offload_status_));
      callback(ToResult(HostError::kInProgress));
      return;
    }
    case A2dpOffloadStatus::kStopped:
      break;
  }

  offloaded_link_handle_ = link_handle;
  offloaded_channel_id_ = local_id;
  a2dp_offload_status_ = A2dpOffloadStatus::kStarting;

  constexpr size_t kPacketSize = pw::bluetooth::vendor::android_hci::
      StartA2dpOffloadCommand::MaxSizeInBytes();
  auto packet = hci::EmbossCommandPacket::New<
      pw::bluetooth::vendor::android_hci::StartA2dpOffloadCommandWriter>(
      hci_android::kA2dpOffloadCommand, kPacketSize);
  auto packet_view = packet.view_t();

  packet_view.vendor_command().sub_opcode().Write(
      hci_android::kStartA2dpOffloadCommandSubopcode);
  packet_view.codec_type().Write(
      static_cast<pw::bluetooth::vendor::android_hci::A2dpCodecType>(
          config.codec));
  packet_view.max_latency().Write(config.max_latency);

  packet_view.scms_t_enable().enabled().Write(config.scms_t_enable.enabled);
  packet_view.scms_t_enable().header().Write(config.scms_t_enable.header);

  packet_view.sampling_frequency().Write(
      static_cast<pw::bluetooth::vendor::android_hci::A2dpSamplingFrequency>(
          config.sampling_frequency));
  packet_view.bits_per_sample().Write(
      static_cast<pw::bluetooth::vendor::android_hci::A2dpBitsPerSample>(
          config.bits_per_sample));
  packet_view.channel_mode().Write(
      static_cast<pw::bluetooth::vendor::android_hci::A2dpChannelMode>(
          config.channel_mode));
  packet_view.encoded_audio_bitrate().Write(config.encoded_audio_bit_rate);
  packet_view.connection_handle().Write(link_handle);
  packet_view.l2cap_channel_id().Write(remote_id);
  packet_view.l2cap_mtu_size().Write(max_tx_sdu_size);

  // kAptx and kAptxhd codecs not yet handled
  if (config.codec == hci_android::A2dpCodecType::kSbc) {
    auto sbc_codec_information =
        packet_view.sbc_codec_information().BackingStorage();
    MutableBufferView sbc_codec_information_buf = MutableBufferView(
        sbc_codec_information.data(), sbc_codec_information.SizeInBytes());
    sbc_codec_information_buf.Fill(0);
    sbc_codec_information_buf.WriteObj(config.codec_information.sbc);
  } else if (config.codec == hci_android::A2dpCodecType::kLdac) {
    auto ldac_codec_information =
        packet_view.ldac_codec_information().BackingStorage();
    MutableBufferView ldac_codec_information_buf = MutableBufferView(
        ldac_codec_information.data(), ldac_codec_information.SizeInBytes());
    ldac_codec_information_buf.Fill(0);
    ldac_codec_information_buf.WriteObj(config.codec_information.ldac);
  } else if (config.codec == hci_android::A2dpCodecType::kAac) {
    auto aac_codec_information =
        packet_view.aac_codec_information().BackingStorage();
    MutableBufferView aac_codec_information_buf = MutableBufferView(
        aac_codec_information.data(), aac_codec_information.SizeInBytes());
    aac_codec_information_buf.Fill(0);
    aac_codec_information_buf.WriteObj(config.codec_information.aac);
  }

  cmd_channel_->SendCommand(
      std::move(packet),
      [cb = std::move(callback),
       id = local_id,
       handle = link_handle,
       self = weak_self_.GetWeakPtr(),
       this](auto /*transaction_id*/, const hci::EventPacket& event) mutable {
        if (!self.is_alive()) {
          return;
        }

        if (event.ToResult().is_error()) {
          bt_log(WARN,
                 "l2cap",
                 "Start A2DP offload command failed (result: %s, handle: "
                 "%#.4x, local id: %#.4x)",
                 bt_str(event.ToResult()),
                 handle,
                 id);
          a2dp_offload_status_ = A2dpOffloadStatus::kStopped;
        } else {
          bt_log(INFO,
                 "l2cap",
                 "A2DP offload started (handle: %#.4x, local id: %#.4x",
                 handle,
                 id);
          a2dp_offload_status_ = A2dpOffloadStatus::kStarted;
        }
        cb(event.ToResult());

        // If we tried to stop while A2DP was still starting, perform the stop
        // command now
        if (pending_stop_a2dp_offload_request_.has_value()) {
          auto callback = std::move(pending_stop_a2dp_offload_request_.value());
          pending_stop_a2dp_offload_request_.reset();

          RequestStopA2dpOffload(id, handle, std::move(callback));
        }
      });
}

void A2dpOffloadManager::RequestStopA2dpOffload(
    ChannelId local_id,
    hci_spec::ConnectionHandle link_handle,
    hci::ResultCallback<> callback) {
  BT_DEBUG_ASSERT(cmd_channel_.is_alive());

  switch (a2dp_offload_status_) {
    case A2dpOffloadStatus::kStopped: {
      bt_log(DEBUG,
             "l2cap",
             "No channels are offloading A2DP (status: %hhu)",
             static_cast<unsigned char>(a2dp_offload_status_));
      callback(fit::success());
      return;
    }
    case A2dpOffloadStatus::kStopping: {
      bt_log(WARN,
             "l2cap",
             "A2DP offload is currently stopping (status: %hhu)",
             static_cast<unsigned char>(a2dp_offload_status_));
      callback(ToResult(HostError::kInProgress));
      return;
    }
    case A2dpOffloadStatus::kStarting:
    case A2dpOffloadStatus::kStarted:
      break;
  }

  if (!IsChannelOffloaded(local_id, link_handle)) {
    callback(fit::success());
    return;
  }

  // Wait until offloading status is |kStarted| before sending stop command
  if (a2dp_offload_status_ == A2dpOffloadStatus::kStarting) {
    pending_stop_a2dp_offload_request_ = std::move(callback);
    return;
  }

  a2dp_offload_status_ = A2dpOffloadStatus::kStopping;

  auto packet = hci::EmbossCommandPacket::New<
      pw::bluetooth::vendor::android_hci::StopA2dpOffloadCommandWriter>(
      hci_android::kA2dpOffloadCommand);
  auto packet_view = packet.view_t();

  packet_view.vendor_command().sub_opcode().Write(
      hci_android::kStopA2dpOffloadCommandSubopcode);

  cmd_channel_->SendCommand(
      std::move(packet),
      [cb = std::move(callback),
       self = weak_self_.GetWeakPtr(),
       id = local_id,
       handle = link_handle,
       this](auto /*transaction_id*/, const hci::EventPacket& event) mutable {
        if (!self.is_alive()) {
          return;
        }

        if (event.ToResult().is_error()) {
          bt_log(WARN,
                 "l2cap",
                 "Stop A2DP offload command failed (result: %s, handle: %#.4x, "
                 "local id: %#.4x)",
                 bt_str(event.ToResult()),
                 handle,
                 id);
        } else {
          bt_log(INFO,
                 "l2cap",
                 "A2DP offload stopped (handle: %#.4x, local id: %#.4x",
                 handle,
                 id);
        }
        cb(event.ToResult());

        a2dp_offload_status_ = A2dpOffloadStatus::kStopped;
      });
}

bool A2dpOffloadManager::IsChannelOffloaded(
    ChannelId id, hci_spec::ConnectionHandle link_handle) const {
  if (!offloaded_channel_id_.has_value() ||
      !offloaded_link_handle_.has_value()) {
    bt_log(DEBUG,
           "l2cap",
           "Channel is not offloaded (handle: %#.4x, local id: %#.4x) ",
           link_handle,
           id);
    return false;
  }

  // Same channel that requested start A2DP offloading must request stop
  // offloading
  if (id != offloaded_channel_id_ || link_handle != offloaded_link_handle_) {
    bt_log(WARN,
           "l2cap",
           "Offloaded channel must request stop offloading; offloaded channel "
           "(handle: %#.4x, local id: %#.4x)",
           *offloaded_link_handle_,
           *offloaded_channel_id_);
    return false;
  }

  return id == *offloaded_channel_id_ &&
         link_handle == *offloaded_link_handle_ &&
         (a2dp_offload_status_ == A2dpOffloadStatus::kStarted ||
          a2dp_offload_status_ == A2dpOffloadStatus::kStarting);
}

}  // namespace bt::l2cap
