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

#include "discardOutput.hpp"
#include "profilingOutput.hpp"

#include "util/plugin.hpp"

#include "libvideostitch/logging.hpp"
#include "libvideostitch/ptv.hpp"
#include "libvideostitch/parse.hpp"
#include "libvideostitch/stitchOutput.hpp"

#include <cassert>
#include <iostream>
#include <mutex>

namespace VideoStitch {

using namespace Plugin;

namespace Output {

Output::~Output() {}

Output::Output(const std::string& nameParam) {
  strncpy(name, nameParam.c_str(), sizeof(name));
  name[sizeof(name) - 1] = '\0';
}

VideoWriter* Output::getVideoWriter() const { return dynamic_cast<VideoWriter*>(const_cast<Output*>(this)); }

AudioWriter* Output::getAudioWriter() const {
  AudioWriter* audio = dynamic_cast<AudioWriter*>(const_cast<Output*>(this));
  if (audio) {
    if (audio->getChannelLayout() != Audio::UNKNOWN && audio->getSamplingRate() != Audio::SamplingRate::SR_NONE &&
        audio->getSamplingDepth() != Audio::SamplingDepth::SD_NONE) {
      return audio;
    }
  }
  return nullptr;
}

BaseConfig::BaseConfig() : numberNumDigits(1), downsamplingFactor(1) {}

/**
 * Returns true on success.
 */
Status BaseConfig::parse(const Ptv::Value& config) {
  clear();
  // Make sure config is an object.
  if (!Parse::checkType("Output", config, Ptv::Value::OBJECT)) {
    return {Origin::Output, ErrType::InvalidConfiguration, "'OutputWriter' value has wrong type"};
  }
  std::string strFmtStr;
  Parse::PopulateResult popResFmt = Parse::populateString("Output", config, "type", strFmtStr, true);
  strncpy(strFmt, strFmtStr.c_str(), sizeof(strFmt));
  strFmt[sizeof(strFmt) - 1] = '\0';
  if (popResFmt == Parse::PopulateResult_WrongType) {
    return {Origin::Output, ErrType::InvalidConfiguration, "OutputWriter 'type' must be a string"};
  }
  std::string baseNameStr;
  Parse::PopulateResult popResBasename = Parse::populateString("Output", config, "filename", baseNameStr, true);
  strncpy(baseName, baseNameStr.c_str(), sizeof(baseName));
  baseName[sizeof(baseName) - 1] = '\0';
  if (popResBasename == Parse::PopulateResult_WrongType) {
    return {Origin::Output, ErrType::InvalidConfiguration, "OutputWriter 'filename' must be a string"};
  }
  if (Parse::populateInt("Output", config, "numbered_digits", numberNumDigits, false) ==
      Parse::PopulateResult_WrongType) {
    return {Origin::Output, ErrType::InvalidConfiguration, "OutputWriter 'numbered_digits' must be an integer value"};
  }
  if (Parse::populateInt("Output", config, "downsampling_factor", downsamplingFactor, false) ==
      Parse::PopulateResult_WrongType) {
    return {Origin::Output, ErrType::InvalidConfiguration,
            "OutputWriter 'downsampling_factor' must be an integer value"};
  }
  if (baseNameStr == "") {
    return {Origin::Output, ErrType::InvalidConfiguration, "OutputWriter output filename is present but empty"};
  }
  return Status::OK();
}

void BaseConfig::clear() {
  strFmt[0] = '\0';
  baseName[0] = '\0';
  numberNumDigits = 1;
  downsamplingFactor = 1;
}

OutputEventManager& Output::getOutputEventManager() { return outputEventManager; }

VideoWriter::VideoWriter(unsigned width, unsigned height, FrameRate framerate, VideoStitch::PixelFormat pixelFormat,
                         AddressSpace outputType)
    : Output(""),
      latency(0),
      width(width),
      height(height),
      framerate(framerate),
      pixelFormat(pixelFormat),
      outputType(outputType) {}

AudioWriter::AudioWriter(Audio::SamplingRate rate, Audio::SamplingDepth depth, Audio::ChannelLayout layout)
    : Output(""), rate(rate), depth(depth), layout(layout) {}

VideoWriter::~VideoWriter() {}

AudioWriter::~AudioWriter() {}

Potential<Output> create(const Ptv::Value& config, const std::string& name, unsigned width, unsigned height,
                         FrameRate framerate, Audio::SamplingRate rate, Audio::SamplingDepth depth,
                         Audio::ChannelLayout audioLayout) {
  BaseConfig baseConfig;
  FAIL_CAUSE(baseConfig.parse(config), Origin::Output, ErrType::InvalidConfiguration,
             "Cannot create output writer '" + name + "'");

  // If we're given a downsampling factor, make sure the factor is doable and modify width/height/padding.
  if (baseConfig.downsamplingFactor > 1) {
    if ((width % baseConfig.downsamplingFactor != 0) || (height % baseConfig.downsamplingFactor != 0)) {
      return {Origin::Output, ErrType::InvalidConfiguration,
              "Ouput writer '" + name +
                  "' specified an invalid downsampling_factor. The output dimensions must be a multiple of the "
                  "downsampling factor."};
    }
    width /= baseConfig.downsamplingFactor;
    height /= baseConfig.downsamplingFactor;
  }

  // First try to open with plugins.
  {
    std::unique_lock<std::mutex> lock(pluginsMutex);
    for (VSWriterPlugin::InstanceVector::const_iterator l_it = VSWriterPlugin::Instances().begin(),
                                                        l_last = VSWriterPlugin::Instances().end();
         l_it != l_last; ++l_it) {
      if ((*l_it)->handles(&config)) {
        Potential<Output>* potWriter =
            (*l_it)->create(&config, VSWriterPlugin::Config(name, width, height, framerate, rate, depth, audioLayout));
        if (!potWriter->ok()) {
          std::stringstream msg;
          const Status writerStatus = potWriter->status();
          delete potWriter;
          msg << "Couldn't create the writer for plugin '" << (*l_it)->getName() << "'";
          return {Origin::Output, ErrType::SetupFailure, msg.str(), writerStatus};
        } else {
          Output* writer = potWriter->release();
          delete potWriter;
          return writer;
        }
      }
    }
  }

  // Without plugins
  if (!strcmp(baseConfig.strFmt, "null")) {
    return Potential<Output>(new DiscardVideoWriter(name, width, height, framerate));
  } else if (!strcmp(baseConfig.strFmt, "profiling")) {
    return Potential<Output>(new ProfilingWriter(name, width, height, framerate));
  } else {
    std::stringstream msg;
    msg << "Could not create an output for configuration '" << std::string(baseConfig.strFmt) << "'";
    if (VSWriterPlugin::Instances().empty()) {
      msg << ". \n"
          << " No output plugin has been loaded. Check your software installation.";
    }
    return {Origin::Output, ErrType::InvalidConfiguration, msg.str()};
  }
}

int64_t VideoWriter::getExpectedFrameSize() const { return getExpectedFrameSizeFor(pixelFormat, width, height); }

int64_t VideoWriter::getExpectedFrameSizeFor(VideoStitch::PixelFormat format, int64_t width, int64_t height) {
  switch (format) {
    case VideoStitch::PixelFormat::RGBA:
    case VideoStitch::PixelFormat::BGRU:
      return width * height * 4;
    case VideoStitch::PixelFormat::RGB:
    case VideoStitch::PixelFormat::BGR:
      return width * height * 3;
    case VideoStitch::PixelFormat::YV12:
    case VideoStitch::PixelFormat::NV12:
      return (width * height * 3) / 2;
    case VideoStitch::PixelFormat::YUV422P10:
      // 20-bits pixels, but padded to 16-bits per component
      return width * height * 4;
    case VideoStitch::PixelFormat::UYVY:
    case VideoStitch::PixelFormat::YUY2:
      return width * height * 2;
    case VideoStitch::PixelFormat::Grayscale:
      return width * height;
    default:
      assert(false);
      return 0;
  }
}

StereoWriter::~StereoWriter() {}
}  // namespace Output
}  // namespace VideoStitch