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

#include "exprProcessor.hpp"
#include "gridProcessor.hpp"
#include "tintProcessor.hpp"
#include "maskProcessor.hpp"

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

#include <memory>

namespace VideoStitch {
namespace Core {

namespace {
/**
 * A PreProcessor that just chains several PreProcessors.
 */
class MultiPreProcessor : public PreProcessor {
 public:
  ~MultiPreProcessor() {
    for (auto preprocessor = preprocessors.begin(); preprocessor != preprocessors.end(); ++preprocessor) {
      delete *preprocessor;
    }
  }

  void add(PreProcessor* preprocessor) { preprocessors.push_back(preprocessor); }

  Status process(frameid_t frame, GPU::Surface& devBuffer, int64_t width, int64_t height, readerid_t inputId,
                 GPU::Stream& stream) const {
    Status s;
    for (auto preprocessor = preprocessors.begin(); preprocessor != preprocessors.end(); ++preprocessor) {
      Status processingStatus = (*preprocessor)->process(frame, devBuffer, width, height, inputId, stream);
      if (processingStatus.ok()) {
        s = processingStatus;
      }
    }
    return s;
  }

  void getDisplayName(std::ostream& os) const {
    for (auto preprocessor = preprocessors.begin(); preprocessor != preprocessors.end(); ++preprocessor) {
      (*preprocessor)->getDisplayName(os);
      os << ", ";
    }
  }

 private:
  std::vector<PreProcessor*> preprocessors;
};
}  // namespace

Potential<PreProcessor> PreProcessor::create(const Ptv::Value& config, Util::OpaquePtr** /*ctx*/) {
  // We accept either lists of processors or single objects.
  if (config.getType() == Ptv::Value::LIST) {
    if (config.asList().size() == 0) {
      return {Origin::PreProcessor, ErrType::InvalidConfiguration, "Configuration is empty"};
    } else if (config.asList().size() == 1) {
      return create(*config.asList()[0]);
    } else {
      std::unique_ptr<MultiPreProcessor> multiPreprocessor(new MultiPreProcessor());
      for (auto subConfig = config.asList().begin(); subConfig != config.asList().end(); ++subConfig) {
        Potential<PreProcessor> preprocessor = PreProcessor::create(*(*subConfig));
        FAIL_RETURN(preprocessor.status());
        multiPreprocessor->add(preprocessor.release());
      }
      return Potential<PreProcessor>(multiPreprocessor.release());
    }
  }

  if (!Parse::checkType("PreProcessor", config, Ptv::Value::OBJECT)) {
    return {Origin::PreProcessor, ErrType::InvalidConfiguration, "Invalid configuration type for 'PreProcessor'"};
  }

  std::string strType;
  if (Parse::populateString("PreProcessor", config, "type", strType, true) == Parse::PopulateResult_WrongType) {
    return {Origin::PreProcessor, ErrType::InvalidConfiguration,
            "Invalid configuration type for 'PreProcessor', expected string"};
  }

  if (strType == "expr") {
    return Potential<PreProcessor>(ExprProcedure::create(config));
  } else if (strType == "tint") {
    return Potential<PreProcessor>(TintPreProcessor::create(config));
  } else if (strType == "mask") {
    return Potential<PreProcessor>(MaskPreProcessor::create(config));
  }
  return {Origin::PreProcessor, ErrType::InvalidConfiguration, "No such pre-processor: '" + strType + "'"};
}

}  // namespace Core
}  // namespace VideoStitch