/**********************************************************************

  Audacity: A Digital Audio Editor

  AVFormatContextWrapper.cpp

  Dmitry Vedenko

**********************************************************************/

#include "AVFormatContextWrapper.h"

#include "FFmpegFunctions.h"

#include "AVInputFormatWrapper.h"
#include "AVOutputFormatWrapper.h"
#include "AVStreamWrapper.h"

AVFormatContextWrapper::AVFormatContextWrapper(const FFmpegFunctions& ffmpeg) noexcept
    : mFFmpeg(ffmpeg)
{
}

AVFormatContext* AVFormatContextWrapper::GetWrappedValue() noexcept
{
   return mAVFormatContext;
}

const AVFormatContext* AVFormatContextWrapper::GetWrappedValue() const noexcept
{
   return mAVFormatContext;
}

AVFormatContextWrapper::~AVFormatContextWrapper()
{
   if (mAVFormatContext != nullptr)
      mFFmpeg.avformat_free_context(mAVFormatContext);
}

AVIOContextWrapper::OpenResult AVFormatContextWrapper::OpenInputContext(
   const wxString& path,
   const AVInputFormatWrapper* inputFormat,
   AVDictionaryWrapper options
)
{
   auto ioContext = mFFmpeg.CreateAVIOContext();

   const auto result = ioContext->Open(path, false);

   if (result != AVIOContextWrapper::OpenResult::Success)
      return result;

   SetAVIOContext(std::move(ioContext));

   AVDictionary* dict = options.Release();

   /*
      Documentation for the last argument:
      "A dictionary filled with AVFormatContext and demuxer-private options.
      On return this parameter will be destroyed and replaced with a
      dict containing options that were not found.
      May be NULL."
    */
   int rc = (mFFmpeg.avformat_open_input(
          &mAVFormatContext, path.c_str(),
          inputFormat != nullptr ? inputFormat->GetWrappedValue() : nullptr,
          &dict));

   // Don't leak the replacement dictionary
   AVDictionaryWrapper cleanup{ mFFmpeg, dict };

   if (rc)
   {
      return AVIOContextWrapper::OpenResult::InternalError;
   }

   if (mFFmpeg.avformat_find_stream_info(mAVFormatContext, nullptr) < 0)
      return AVIOContextWrapper::OpenResult::InternalError;

   UpdateStreamList();

   mInputFormat = mFFmpeg.CreateAVInputFormatWrapper(GetIFormat());

   return result;
}

AVIOContextWrapper::OpenResult
AVFormatContextWrapper::OpenOutputContext(const wxString& path)
{
   auto ioContext = mFFmpeg.CreateAVIOContext();

   const auto result = ioContext->Open(path, true);

   if (result != AVIOContextWrapper::OpenResult::Success)
      return result;

   SetAVIOContext(std::move(ioContext));

   return result;
}

std::unique_ptr<AVPacketWrapper> AVFormatContextWrapper::ReadNextPacket()
{
   std::unique_ptr<AVPacketWrapper> packet = mFFmpeg.CreateAVPacketWrapper();

   if (mFFmpeg.av_read_frame(mAVFormatContext, packet->GetWrappedValue()) < 0)
      return {};

   return packet;
}

std::unique_ptr<AVStreamWrapper> AVFormatContextWrapper::CreateStream()
{
   // The complementary deallocation happens in avformat_free_context
   AVStream* stream = mFFmpeg.avformat_new_stream(mAVFormatContext, nullptr);

   if (stream == nullptr)
      return {};

   UpdateStreamList();

   return mFFmpeg.CreateAVStreamWrapper(stream, true);
}

const AVInputFormatWrapper*
AVFormatContextWrapper::GetInputFormat() const noexcept
{
   return mInputFormat.get();
}

const AVOutputFormatWrapper*
AVFormatContextWrapper::GetOutputFormat() const noexcept
{
   return mOutputFormat.get();
}

const AVStreamWrapper*
AVFormatContextWrapper::GetStream(int index) const noexcept
{
   if (index < GetStreamsCount())
      return GetStreams()[index].get();

   return nullptr;
}
