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

#include "libvideostitch/imageWarperFactory.hpp"

#include "parallax/noWarper.hpp"
#include "parallax/noFlow.hpp"
#ifndef VS_OPENCL
#include "parallax/linearFlowWarper.hpp"
#include "parallax/simpleFlow.hpp"
#endif

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

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

namespace VideoStitch {
namespace Core {

Potential<Core::ImageWarperFactory> ImageWarperFactory::createWarperFactory(const Ptv::Value* value) {
  if (!value) {
    std::unique_ptr<Ptv::Value> emptyObject = std::unique_ptr<Ptv::Value>(Ptv::Value::emptyObject());
    return NoWarper::Factory::parse(*emptyObject.get());
  }
  // Make sure value is an object, if not, return nowarper
  if (!Parse::checkType("ImageWarperFactory", *value, Ptv::Value::OBJECT)) {
    return Potential<Core::ImageWarperFactory>(new NoWarper::Factory());
  }
  std::string type;
  if (Parse::populateString("ImageWarperFactory", *value, "type", type, false) == Parse::PopulateResult_WrongType) {
    return {Origin::ImageWarper, ErrType::InvalidConfiguration,
            "Invalid type for 'type' configuration, expected string"};
  }
  if (type == NoWarper::getName() || type == "") {
    return NoWarper::Factory::parse(*value);
#ifndef VS_OPENCL
  } else if (type == LinearFlowWarper::getName()) {
    return LinearFlowWarper::Factory::parse(*value);
#endif
  } else {
    Logger::get(Logger::Error) << "Unknown warper type: '" << type << "'." << std::endl;
    return {Origin::ImageWarper, ErrType::InvalidConfiguration, "Invalid warper type"};
  }
}

const std::vector<std::string>& ImageWarperFactory::availableWarpers() {
  static std::vector<std::string> availableWarpers;
  // Lazily fill in the list of mergers. TODO: use a better, macro-based, registration pattern.
  static std::mutex mutex;
  {
    std::unique_lock<std::mutex> lock(mutex);
    if (availableWarpers.empty()) {
      availableWarpers.push_back(NoWarper::getName());
#ifndef VS_OPENCL
      availableWarpers.push_back(LinearFlowWarper::getName());
#endif
    }
  }
  return availableWarpers;
}

std::vector<std::string> ImageWarperFactory::compatibleWarpers(const std::string& flow) {
  std::vector<std::string> compatibleWarpers;
  // Lazily fill in the list of mergers. TODO: use a better, macro-based, registration pattern.
  static std::mutex mutex;
  {
    std::unique_lock<std::mutex> lock(mutex);
    compatibleWarpers.clear();
    if (flow == NoFlow::getName()) {
      compatibleWarpers.push_back(NoWarper::getName());
    }
#ifndef VS_OPENCL
    if (flow == SimpleFlow::getName()) {
      compatibleWarpers.push_back(LinearFlowWarper::getName());
    }
#endif
  }
  return compatibleWarpers;
}

bool ImageWarperFactory::equal(const ImageWarperFactory& other) const { return hash() == other.hash(); }

namespace {
/**
 * A merger factory that cannot instantiate a merger. This can be useful when creating a merger without stitchers.
 */
class ImpotentWarperFactory : public ImageWarperFactory {
 public:
  virtual Potential<ImageWarper> create() const override {
    return {Origin::ImageWarper, ErrType::ImplementationError, "ImpotentWarper is not implemented properly"};
  }

  virtual ~ImpotentWarperFactory() {}

  virtual Ptv::Value* serialize() const override { return nullptr; }

  virtual std::string getImageWarperName() const override { return "ImpotentWarper"; }

  virtual bool needsInputPreProcessing() const override { return false; }

  virtual ImpotentWarperFactory* clone() const override { return new ImpotentWarperFactory(); }

  virtual std::string hash() const override { return "ImpotentWarper"; }
};
}  // namespace

Potential<ImageWarperFactory> ImageWarperFactory::newImpotentWarperFactory() {
  return Potential<ImageWarperFactory>(new ImpotentWarperFactory);
}

}  // namespace Core
}  // namespace VideoStitch