// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/mojo/services/mojo_audio_decoder_service.h"

#include <memory>
#include <utility>
#include <variant>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "base/types/optional_util.h"
#include "media/base/content_decryption_module.h"
#include "media/mojo/common/media_type_converters.h"
#include "media/mojo/common/mojo_decoder_buffer_converter.h"
#include "media/mojo/services/mojo_cdm_service_context.h"
#include "media/mojo/services/mojo_media_client.h"
#include "media/mojo/services/mojo_media_log.h"

namespace media {

static constexpr std::string_view kNotInitializedMessage =
    "Decoder can't be used after Initialize() fails.";
static constexpr std::string_view kDataSourceNotSetMessage =
    "SetDataSource() must be called before Decode() or Reset().";

MojoAudioDecoderService::MojoAudioDecoderService(
    MojoMediaClient* mojo_media_client,
    MojoCdmServiceContext* mojo_cdm_service_context,
    scoped_refptr<base::SingleThreadTaskRunner> task_runner)
    : mojo_media_client_(mojo_media_client),
      mojo_cdm_service_context_(mojo_cdm_service_context),
      task_runner_(std::move(task_runner)) {
  DCHECK(mojo_cdm_service_context_);
  weak_this_ = weak_factory_.GetWeakPtr();
}

MojoAudioDecoderService::~MojoAudioDecoderService() {
  if (last_decode_status_) {
    base::UmaHistogramEnumeration("Media.MojoAudioDecoder.LastDecodeStatus",
                                  last_decode_status_->code());
  }
}

void MojoAudioDecoderService::GetSupportedConfigs(
    GetSupportedConfigsCallback callback) {
  DVLOG(3) << __func__;
  TRACE_EVENT0("media", "MojoAudioDecoderService::GetSupportedConfigs");
  std::move(callback).Run(
      mojo_media_client_->GetSupportedAudioDecoderConfigs());
}

void MojoAudioDecoderService::Construct(
    mojo::PendingAssociatedRemote<mojom::AudioDecoderClient> client,
    mojo::PendingRemote<mojom::MediaLog> media_log) {
  DVLOG(1) << __func__;
  if (client_) {
    mojo::ReportBadMessage("Construct() may only be called once.");
    return;
  }

  client_.Bind(std::move(client));

  auto mojo_media_log =
      std::make_unique<MojoMediaLog>(std::move(media_log), task_runner_);
  decoder_ = mojo_media_client_->CreateAudioDecoder(task_runner_,
                                                    std::move(mojo_media_log));
}

void MojoAudioDecoderService::Initialize(
    const AudioDecoderConfig& config,
    const std::optional<base::UnguessableToken>& cdm_id,
    InitializeCallback callback) {
  DVLOG(1) << __func__ << " " << config.AsHumanReadableString();

  if (!decoder_) {
    std::move(callback).Run(DecoderStatus::Codes::kFailed, false,
                            AudioDecoderType::kUnknown);
    mojo::ReportBadMessage(kNotInitializedMessage);
    return;
  }

  // |cdm_context_ref_| must be kept as long as |cdm_context| is used by the
  // |decoder_|. We do NOT support resetting |cdm_context_ref_| because in
  // general we don't support resetting CDM in the media pipeline.
  if (cdm_id) {
    if (!cdm_id_) {
      DCHECK(!cdm_context_ref_);
      cdm_id_ = cdm_id;
      cdm_context_ref_ =
          mojo_cdm_service_context_->GetCdmContextRef(cdm_id.value());
    } else if (cdm_id != cdm_id_) {
      CHECK(mojo::IsInMessageDispatch());
      mojo::ReportBadMessage("The caller should not switch CDM");
      return;
    }
  }

  // Get CdmContext, which could be null.
  CdmContext* cdm_context =
      cdm_context_ref_ ? cdm_context_ref_->GetCdmContext() : nullptr;

  if (config.is_encrypted() && !cdm_context) {
    DVLOG(1) << "CdmContext for "
             << CdmContext::CdmIdToString(base::OptionalToPtr(cdm_id))
             << " not found for encrypted audio";
    OnInitialized(std::move(callback),
                  DecoderStatus::Codes::kUnsupportedEncryptionMode);
    return;
  }

  decoder_->Initialize(
      config, cdm_context,
      base::BindOnce(&MojoAudioDecoderService::OnInitialized, weak_this_,
                     std::move(callback)),
      base::BindRepeating(&MojoAudioDecoderService::OnAudioBufferReady,
                          weak_this_),
      base::BindRepeating(&MojoAudioDecoderService::OnWaiting, weak_this_));
}

void MojoAudioDecoderService::SetDataSource(
    mojo::ScopedDataPipeConsumerHandle receive_pipe) {
  DVLOG(1) << __func__;
  if (mojo_decoder_buffer_reader_) {
    mojo::ReportBadMessage("SetDataSource() may only be called once.");
    return;
  }

  mojo_decoder_buffer_reader_ =
      std::make_unique<MojoDecoderBufferReader>(std::move(receive_pipe));
}

void MojoAudioDecoderService::Decode(mojom::DecoderBufferPtr buffer,
                                     DecodeCallback callback) {
  DVLOG(3) << __func__;
  if (!decoder_) {
    std::move(callback).Run(DecoderStatus::Codes::kFailed);
    mojo::ReportBadMessage(kNotInitializedMessage);
    return;
  }

  if (!mojo_decoder_buffer_reader_) {
    std::move(callback).Run(DecoderStatus::Codes::kFailed);
    mojo::ReportBadMessage(kDataSourceNotSetMessage);
    return;
  }

  mojo_decoder_buffer_reader_->ReadDecoderBuffer(
      std::move(buffer),
      base::BindOnce(&MojoAudioDecoderService::OnReadDone, weak_this_,
                     mojo::GetBadMessageCallback(), std::move(callback)));
}

void MojoAudioDecoderService::Reset(ResetCallback callback) {
  DVLOG(1) << __func__;
  if (!decoder_) {
    std::move(callback).Run();
    mojo::ReportBadMessage(kNotInitializedMessage);
    return;
  }

  if (!mojo_decoder_buffer_reader_) {
    std::move(callback).Run();
    mojo::ReportBadMessage(kDataSourceNotSetMessage);
    return;
  }

  // Reset the reader so that pending decodes will be dispatches first.
  mojo_decoder_buffer_reader_->Flush(
      base::BindOnce(&MojoAudioDecoderService::OnReaderFlushDone, weak_this_,
                     std::move(callback)));
}

void MojoAudioDecoderService::OnInitialized(InitializeCallback callback,
                                            DecoderStatus status) {
  DVLOG(1) << __func__ << " success:" << status.is_ok();
  base::UmaHistogramEnumeration("Media.MojoAudioDecoder.Initialized",
                                status.code());
  if (!status.is_ok()) {
    // Do not call decoder_->NeedsBitstreamConversion() if init failed.
    std::move(callback).Run(std::move(status), false,
                            decoder_->GetDecoderType());
    decoder_.reset();
    return;
  }

  std::move(callback).Run(std::move(status),
                          decoder_->NeedsBitstreamConversion(),
                          decoder_->GetDecoderType());
}

// The following methods are needed so that we can bind them with a weak pointer
// to avoid running the |callback| after connection error happens and |this| is
// deleted. It's not safe to run the |callback| after a connection error.

void MojoAudioDecoderService::OnReadDone(
    mojo::ReportBadMessageCallback bad_message_callback,
    DecodeCallback callback,
    scoped_refptr<DecoderBuffer> buffer) {
  DVLOG(3) << __func__ << " success:" << !!buffer;

  if (!buffer) {
    std::move(callback).Run(DecoderStatus::Codes::kFailedToGetDecoderBuffer);
    return;
  }

  if (buffer->end_of_stream() && buffer->next_config() &&
      !std::holds_alternative<AudioDecoderConfig>(*buffer->next_config())) {
    std::move(bad_message_callback)
        .Run("Invalid DecoderBuffer::next_config() for audio.");
    return;
  }

  if (!buffer->end_of_stream() && buffer->side_data() &&
      buffer->side_data()->secure_handle) {
    std::move(bad_message_callback)
        .Run("Renderer sent non-zero DecoderBufferSideData.secure_handle.");
    return;
  }

  decoder_->Decode(std::move(buffer),
                   base::BindOnce(&MojoAudioDecoderService::OnDecodeStatus,
                                  weak_this_, std::move(callback)));
}

void MojoAudioDecoderService::OnReaderFlushDone(ResetCallback callback) {
  decoder_->Reset(base::BindOnce(&MojoAudioDecoderService::OnResetDone,
                                 weak_this_, std::move(callback)));
}

void MojoAudioDecoderService::OnDecodeStatus(DecodeCallback callback,
                                             const DecoderStatus status) {
  status.DebugLog(3);
  last_decode_status_ = status;
  std::move(callback).Run(std::move(status));
}

void MojoAudioDecoderService::OnResetDone(ResetCallback callback) {
  DVLOG(1) << __func__;
  std::move(callback).Run();
}

void MojoAudioDecoderService::OnAudioBufferReady(
    scoped_refptr<AudioBuffer> audio_buffer) {
  DVLOG(1) << __func__;
  client_->OnBufferDecoded(mojom::AudioBuffer::From(*audio_buffer));
}

void MojoAudioDecoderService::OnWaiting(WaitingReason reason) {
  DVLOG(1) << __func__;
  client_->OnWaiting(reason);
}

}  // namespace media
