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

#include "depthControllerImpl.hpp"

#include "depthPipeline.hpp"

#include "core/readerController.hpp"
#include "core/stitchOutput/asyncOutput.hpp"

#include "libvideostitch/depthDef.hpp"
#include "libvideostitch/panoDef.hpp"

#include <sstream>

namespace VideoStitch {
namespace Core {

DepthControllerImpl::DepthControllerImpl(const PanoDefinition& pano, ReaderController* readerController,
                                         InputPipeline* pipe)
    : InputControllerImpl(readerController), pano(pano.clone()), pipe(pipe) {}

Potential<DepthController> DepthControllerImpl::create(const PanoDefinition& pano, const DepthDefinition& depthDef,
                                                       const AudioPipeDefinition& audioPipeDef,
                                                       Input::ReaderFactory* readerFactory) {
  {
    std::stringstream validationMessages;
    if (!pano.validate(validationMessages)) {
      return {Origin::Stitcher, ErrType::InvalidConfiguration,
              "Could not validate panorama configuration: " + validationMessages.str()};
    }
  }

  if (!pano.numInputs()) {
    return {Origin::Stitcher, ErrType::InvalidConfiguration, "Configuration does not cotain any inputs"};
  }

  auto potReaderController = ReaderController::create(pano, audioPipeDef, readerFactory);

  FAIL_RETURN(potReaderController.status());

#if USE_SGM
  auto potPipeline = SGMDepthPipeline::createSGMDepthPipeline(potReaderController->getReaders(), pano, depthDef);
#else
  auto potPipeline = DepthPipeline::createDepthPipeline(potReaderController->getReaders(), pano, depthDef);
#endif
  FAIL_RETURN(potPipeline.status());

  return new DepthControllerImpl(pano, potReaderController.release(), potPipeline.release());
}

DepthControllerImpl::~DepthControllerImpl() { delete pipe; }

ControllerStatus DepthControllerImpl::estimateDepth(std::vector<ExtractOutput*> extracts) {
  auto statusVideo = Input::ReadStatus::fromCode<Input::ReadStatusCode::EndOfFile>();
  auto statusAudio = Input::ReadStatus::fromCode<Input::ReadStatusCode::EndOfFile>();
  auto statusMetadata = Input::ReadStatus::fromCode<Input::ReadStatusCode::EndOfFile>();

  // load the acquisition data
  std::map<readerid_t, Input::PotentialFrame> inputBuffers;
  mtime_t date;
  Audio::audioBlocks_t audioBlocks;
  Input::MetadataChunk metadata;

  std::tie(statusVideo, statusAudio, statusMetadata) =
      readerController->load(date, inputBuffers, audioBlocks, metadata);

  if (statusVideo.ok()) {
    pipe->process(date, getFrameRate(), inputBuffers, extracts);
  }

  readerController->releaseBuffer(inputBuffers);

  switch (statusVideo.getCode()) {
    case Input::ReadStatusCode::ErrorWithStatus:
      return statusVideo.getStatus();
    case Input::ReadStatusCode::TryAgain:
      return Status{Origin::Stitcher, ErrType::RuntimeError, "Couldn't load inputs (TryAgain)"};
    case Input::ReadStatusCode::EndOfFile:
      return ControllerStatus::fromCode<ControllerStatusCode::EndOfStream>();
    case Input::ReadStatusCode::Ok:
      break;
  }

  return Status::OK();
}

Potential<ExtractOutput> DepthControllerImpl::createAsyncExtractOutput(
    int sourceID, std::shared_ptr<SourceSurface> surf, std::shared_ptr<VideoStitch::Output::VideoWriter> writer) {
  Potential<AsyncSourceOutput> potSourceOutput = AsyncSourceOutput::create({surf}, {}, {writer}, sourceID);
  FAIL_RETURN(potSourceOutput.status());
  return new ExtractOutput(potSourceOutput.release());
}

Potential<DepthController> createDepthController(const PanoDefinition& pano, const DepthDefinition& depthDef,
                                                 const AudioPipeDefinition& audioPipeDef,
                                                 Input::ReaderFactory* readerFactory) {
  return DepthControllerImpl::create(pano, depthDef, audioPipeDef, readerFactory);
}

}  // namespace Core
}  // namespace VideoStitch