/*
 * Copyright 2020 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
 * www.ehima.com
 *
 * 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 "BTAudioProviderLeAudio"

#include "LeAudioAudioProvider.h"

#include <android-base/logging.h>

#include "BluetoothAudioSessionReport_2_1.h"
#include "BluetoothAudioSupportedCodecsDB_2_1.h"

namespace android {
namespace hardware {
namespace bluetooth {
namespace audio {
namespace V2_1 {
namespace implementation {

using ::android::bluetooth::audio::BluetoothAudioSessionReport_2_1;
using ::android::hardware::Void;
using ::android::hardware::bluetooth::audio::V2_0::BitsPerSample;
using ::android::hardware::bluetooth::audio::V2_0::ChannelMode;
using ::android::hardware::bluetooth::audio::V2_1::SampleRate;

static constexpr uint32_t kBufferOutCount = 2;  // two frame buffer
static constexpr uint32_t kBufferInCount = 2;   // two frame buffer

LeAudioOutputAudioProvider::LeAudioOutputAudioProvider()
    : LeAudioAudioProvider() {
  session_type_ = SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH;
}

LeAudioInputAudioProvider::LeAudioInputAudioProvider()
    : LeAudioAudioProvider() {
  session_type_ = SessionType::LE_AUDIO_SOFTWARE_DECODED_DATAPATH;
}

LeAudioAudioProvider::LeAudioAudioProvider()
    : BluetoothAudioProvider(), mDataMQ(nullptr) {}

bool LeAudioAudioProvider::isValid(const V2_0::SessionType& sessionType) {
  LOG(ERROR) << __func__ << ", invalid session type for Le Audio provider: "
             << toString(sessionType);

  return false;
}

bool LeAudioAudioProvider::isValid(const SessionType& sessionType) {
  return (sessionType == session_type_);
}

Return<void> LeAudioAudioProvider::startSession_2_1(
    const sp<V2_0::IBluetoothAudioPort>& hostIf,
    const AudioConfiguration& audioConfig, startSession_cb _hidl_cb) {
  /**
   * Initialize the audio platform if audioConfiguration is supported.
   * Save the IBluetoothAudioPort interface, so that it can be used
   * later to send stream control commands to the HAL client, based on
   * interaction with Audio framework.
   */
  if (audioConfig.getDiscriminator() !=
      AudioConfiguration::hidl_discriminator::pcmConfig) {
    LOG(WARNING) << __func__
                 << " - Invalid Audio Configuration=" << toString(audioConfig);
    _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
             DataMQ::Descriptor());
    return Void();
  } else if (!android::bluetooth::audio::IsSoftwarePcmConfigurationValid_2_1(
                 audioConfig.pcmConfig())) {
    LOG(WARNING) << __func__ << " - Unsupported PCM Configuration="
                 << toString(audioConfig.pcmConfig());
    _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
             DataMQ::Descriptor());
    return Void();
  }

  uint32_t kDataMqSize = 0;
  switch (audioConfig.pcmConfig().sampleRate) {
    case SampleRate::RATE_8000:
      kDataMqSize = 8000;
      break;
    case SampleRate::RATE_16000:
      kDataMqSize = 16000;
      break;
    case SampleRate::RATE_24000:
      kDataMqSize = 24000;
      break;
    case SampleRate::RATE_32000:
      kDataMqSize = 32000;
      break;
    case SampleRate::RATE_44100:
      kDataMqSize = 44100;
      break;
    case SampleRate::RATE_48000:
      kDataMqSize = 48000;
      break;
    default:
      LOG(WARNING) << __func__ << " - Unsupported sampling frequency="
                   << toString(audioConfig.pcmConfig());
      _hidl_cb(BluetoothAudioStatus::UNSUPPORTED_CODEC_CONFIGURATION,
               DataMQ::Descriptor());
      return Void();
  }

  /* Number of samples per millisecond */
  kDataMqSize = ceil(kDataMqSize / 1000);

  switch (audioConfig.pcmConfig().channelMode) {
    case ChannelMode::MONO:
      break;
    case ChannelMode::STEREO:
      kDataMqSize *= 2;
      break;
    default:
      /* This should never happen it would be caught while validating
       * parameters.
       */
      break;
  }

  switch (audioConfig.pcmConfig().bitsPerSample) {
    case BitsPerSample::BITS_16:
      kDataMqSize *= 2;
      break;
    case BitsPerSample::BITS_24:
      kDataMqSize *= 3;
      break;
    case BitsPerSample::BITS_32:
      kDataMqSize *= 4;
      break;
    default:
      /* This should never happen it would be caught while validating
       * parameters.
       */
      break;
  }

  if (session_type_ == SessionType::LE_AUDIO_SOFTWARE_ENCODING_DATAPATH)
    kDataMqSize *= kBufferOutCount;
  else if (session_type_ == SessionType::LE_AUDIO_SOFTWARE_DECODED_DATAPATH)
    kDataMqSize *= kBufferInCount;
  else
    LOG(WARNING) << __func__ << ", default single buffer used";

  kDataMqSize *= audioConfig.pcmConfig().dataIntervalUs / 1000;

  LOG(INFO) << __func__ << " - size of audio buffer " << kDataMqSize
            << " byte(s)";

  std::unique_ptr<DataMQ> tempDataMQ(
      new DataMQ(kDataMqSize, /* EventFlag */ true));
  if (tempDataMQ && tempDataMQ->isValid()) {
    mDataMQ = std::move(tempDataMQ);
  } else {
    ALOGE_IF(!tempDataMQ, "failed to allocate data MQ");
    ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "data MQ is invalid");
    _hidl_cb(BluetoothAudioStatus::FAILURE, DataMQ::Descriptor());
    return Void();
  }

  return BluetoothAudioProvider::startSession_2_1(hostIf, audioConfig,
                                                  _hidl_cb);
}

Return<void> LeAudioAudioProvider::onSessionReady(startSession_cb _hidl_cb) {
  if (mDataMQ && mDataMQ->isValid()) {
    BluetoothAudioSessionReport_2_1::OnSessionStarted(
        session_type_, stack_iface_, mDataMQ->getDesc(), audio_config_);
    _hidl_cb(BluetoothAudioStatus::SUCCESS, *mDataMQ->getDesc());
  } else {
    _hidl_cb(BluetoothAudioStatus::FAILURE, DataMQ::Descriptor());
  }
  return Void();
}

}  // namespace implementation
}  // namespace V2_1
}  // namespace audio
}  // namespace bluetooth
}  // namespace hardware
}  // namespace android