// Copyright (c) 2012-2017 VideoStitch SAS
// Copyright (c) 2018 stitchEm

#pragma once

#include "libvideostitch/stitchOutput.hpp"
#include "libvideostitch/frame.hpp"
#include "libvideostitch/profile.hpp"
#include "muxer.hpp"

#include <condition_variable>
#include <memory>
#include <mutex>
#include <queue>
#include <stdint.h>
#include <cstdio>

#ifndef _MSC_VER
#include <sys/time.h>
#endif

struct AVCodecContext;
struct AVFrame;

namespace VideoStitch {
namespace Util {
enum AvErrorCode : short;
}

namespace Output {

/**
 * @brief Additional indirection onto the implementation.
 * @note  Allows reseting the writer implementation while keeping the same LibavWriter object
 */
class AvMuxer_pimpl;

class LibavWriter : public VideoWriter, public AudioWriter {
 public:
  static Output* create(const Ptv::Value& config, const std::string& name, const char* baseName, unsigned width,
                        unsigned height, FrameRate framerate, const Audio::SamplingRate samplingRate,
                        const Audio::SamplingDepth samplingDepth, const Audio::ChannelLayout channleLayout);

  ~LibavWriter();

  void pushVideo(const Frame& videoFrame);
  void pushAudio(Audio::Samples& audioSamples);

 private:
  LibavWriter(const Ptv::Value& config, const std::string& name, const VideoStitch::PixelFormat fmt, AddressSpace type,
              unsigned width, unsigned height, FrameRate framerate, const Audio::SamplingRate samplingRate,
              const Audio::SamplingDepth samplingDepth, const Audio::ChannelLayout channleLayout);

  Util::AvErrorCode encodeVideoFrame(AVFrame* frame, int64_t frameOffset);
  Util::AvErrorCode encodeAudioFrame(AVFrame* frame);
  MuxerThreadStatus flushVideo();
  MuxerThreadStatus flushAudio();
  MuxerThreadStatus close();

  bool needsRespawn(std::shared_ptr<AvMuxer_pimpl>&, mtime_t);
  bool implReady(std::shared_ptr<AvMuxer_pimpl>&, AVCodecContext*, mtime_t);
  bool hasAudio() const { return audioCodecContext != nullptr; }

  bool createVideoCodec(AddressSpace type, unsigned width, unsigned height, FrameRate framerate);
  bool createAudioCodec();
  bool resetCodec(AVCodecContext*, MuxerThreadStatus& status);
  Ptv::Value* m_config;

  std::deque<AVFrame*> videoFrames;
  AVDictionary* codecConfig;
  AVCodecContext* videoCodecContext;
  mtime_t firstVideoPTS;

  AVFrame* audioFrame;
  Audio::Samples audioBuffer;
  uint8_t* audioData[MAX_AUDIO_CHANNELS];  // intermediate buffer
  const uint8_t* avSamples;                // buffer used by libav
  Audio::SamplingFormat m_sampleFormat;
  std::vector<int64_t> m_channelMap;
  std::size_t m_audioFrameSizeInBytes;
  AVCodecContext* audioCodecContext;

  int m_currentImplNumber;
  std::shared_ptr<AvMuxer_pimpl> m_pimplVideo;
  std::shared_ptr<AvMuxer_pimpl> m_pimplAudio;
  std::mutex pimplMu;
};
}  // namespace Output
}  // namespace VideoStitch