/*
 * Copyright 2023 The Android Open Source Project
 *
 * 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 "mmc/codec_client/codec_client.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "mmc/daemon/constants.h"
#include "mmc/metrics/mmc_rtt_logger.h"
#include "mmc/proto/mmc_config.pb.h"
#include "mmc/proto/mmc_service.pb.h"
namespace mmc {
namespace {
using namespace bluetooth;
// Codec param field number in |ConfigParam|
const int kUnsupportedType = -1;
const int kHfpLc3EncoderId = 1;
const int kHfpLc3DecoderId = 2;
const int kA2dpAacEncoderId = 5;
// Maps |ConfigParam| proto field to int, because proto-lite does not support
// reflection.
int CodecId(const ConfigParam& config) {
  if (config.has_hfp_lc3_encoder_param()) {
    return kHfpLc3EncoderId;
  } else if (config.has_hfp_lc3_decoder_param()) {
    return kHfpLc3DecoderId;
  } else if (config.has_a2dp_aac_encoder_param()) {
    return kA2dpAacEncoderId;
  } else {
    log::warn("Unsupported codec type is used.");
    return kUnsupportedType;
  }
}
}  // namespace
CodecClient::CodecClient() {
  skt_fd_ = -1;
  codec_manager_ = nullptr;
  record_logger_ = nullptr;
  // Set up DBus connection.
  dbus::Bus::Options options;
  options.bus_type = dbus::Bus::SYSTEM;
  bus_ = new dbus::Bus(options);
  if (!bus_->Connect()) {
    log::error("Failed to connect system bus");
    return;
  }
  // Get proxy to send DBus method call.
  codec_manager_ =
          bus_->GetObjectProxy(mmc::kMmcServiceName, dbus::ObjectPath(mmc::kMmcServicePath));
  if (!codec_manager_) {
    log::error("Failed to get object proxy");
    return;
  }
}
CodecClient::~CodecClient() {
  cleanup();
  if (bus_) {
    bus_->ShutdownAndBlock();
  }
}
int CodecClient::init(const ConfigParam config) {
  cleanup();
  // Set up record logger.
  record_logger_ = std::make_unique(CodecId(config));
  dbus::MethodCall method_call(mmc::kMmcServiceInterface, mmc::kCodecInitMethod);
  dbus::MessageWriter writer(&method_call);
  mmc::CodecInitRequest request;
  *request.mutable_config() = config;
  if (!writer.AppendProtoAsArrayOfBytes(request)) {
    log::error("Failed to encode CodecInitRequest protobuf");
    return -EINVAL;
  }
  std::unique_ptr dbus_response =
          codec_manager_
                  ->CallMethodAndBlock(&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)
// TODO(b/297976471): remove the build flag once libchrome uprev is done.
#if BASE_VER >= 1170299
                  .value_or(nullptr)
#endif
          ;
  if (!dbus_response) {
    log::error("CodecInit failed");
    return -ECOMM;
  }
  dbus::MessageReader reader(dbus_response.get());
  mmc::CodecInitResponse response;
  if (!reader.PopArrayOfBytesAsProto(&response)) {
    log::error("Failed to parse response protobuf");
    return -EINVAL;
  }
  if (response.socket_token().empty()) {
    log::error("CodecInit returned empty socket token");
    return -EBADMSG;
  }
  if (response.input_frame_size() < 0) {
    log::error("CodecInit returned negative frame size");
    return -EBADMSG;
  }
  // Create socket.
  skt_fd_ = socket(AF_UNIX, SOCK_SEQPACKET, 0);
  if (skt_fd_ < 0) {
    log::error("Failed to create socket: {}", strerror(errno));
    return -errno;
  }
  struct sockaddr_un addr = {};
  addr.sun_family = AF_UNIX;
  strncpy(addr.sun_path, response.socket_token().c_str(), sizeof(addr.sun_path) - 1);
  // Connect to socket for transcoding.
  int rc = connect(skt_fd_, (struct sockaddr*)&addr, sizeof(struct sockaddr_un));
  if (rc < 0) {
    log::error("Failed to connect socket: {}", strerror(errno));
    return -errno;
  }
  unlink(addr.sun_path);
  return response.input_frame_size();
}
void CodecClient::cleanup() {
  if (skt_fd_ >= 0) {
    close(skt_fd_);
    skt_fd_ = -1;
  }
  // Upload Rtt statics when the session ends.
  if (record_logger_.get() != nullptr) {
    record_logger_->UploadTranscodeRttStatics();
    record_logger_.release();
  }
  dbus::MethodCall method_call(mmc::kMmcServiceInterface, mmc::kCodecCleanUpMethod);
  std::unique_ptr dbus_response =
          codec_manager_
                  ->CallMethodAndBlock(&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)
// TODO(b/297976471): remove the build flag once libchrome uprev is done.
#if BASE_VER >= 1170299
                  .value_or(nullptr)
#endif
          ;
  if (!dbus_response) {
    log::warn("CodecCleanUp failed");
  }
  return;
}
int CodecClient::transcode(uint8_t* i_buf, int i_len, uint8_t* o_buf, int o_len) {
  // Start Timer
  base::ElapsedTimer timer;
  // i_buf and o_buf cannot be null.
  if (i_buf == nullptr || o_buf == nullptr) {
    log::error("Buffer is null");
    return -EINVAL;
  }
  if (i_len <= 0 || o_len <= 0) {
    log::error("Non-positive buffer length");
    return -EINVAL;
  }
  // Use MSG_NOSIGNAL to ignore SIGPIPE.
  int rc = send(skt_fd_, i_buf, i_len, MSG_NOSIGNAL);
  if (rc < 0) {
    log::error("Failed to send data: {}", strerror(errno));
    return -errno;
  }
  // Full packet should be sent under SOCK_SEQPACKET setting.
  if (rc < i_len) {
    log::error("Failed to send full packet");
    return -EIO;
  }
  struct pollfd pfd;
  pfd.fd = skt_fd_;
  pfd.events = POLLIN;
  int pollret = poll(&pfd, 1, -1);
  if (pollret < 0) {
    log::error("Failed to poll: {}", strerror(errno));
    return -errno;
  }
  if (pfd.revents & (POLLHUP | POLLNVAL)) {
    log::error("Socket closed remotely.");
    return -EIO;
  }
  // POLLIN is returned..
  rc = recv(skt_fd_, o_buf, o_len, MSG_NOSIGNAL);
  if (rc < 0) {
    log::error("Failed to recv data: {}", strerror(errno));
    return -errno;
  }
  // Should be able to recv data when POLLIN is returned.
  if (rc == 0) {
    log::error("Failed to recv data");
    return -EIO;
  }
  // End timer
  record_logger_->RecordRtt(timer.Elapsed().InMicroseconds());
  return rc;
}
}  // namespace mmc