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

#include "./linearFlowWarper.hpp"

#include "backend/cuda/deviceBuffer.hpp"
#include "core1/imageMerger.hpp"
#include "cuda/error.hpp"
#include "cuda/util.hpp"
#include "gpu/core1/voronoi.hpp"
#include "gpu/uniqueBuffer.hpp"
#include "parse/json.hpp"

#include "libvideostitch/parse.hpp"

//#define WARPER_DEBUG

#ifdef WARPER_DEBUG
#ifndef NDEBUG
#include "util/debugUtils.hpp"
#include <sstream>
#endif
#endif

namespace VideoStitch {
namespace Core {

#define LINEAR_FLOW_WRAPER_MAX_TRANSITION_DISTANCE 150
#define LINEAR_FLOW_WRAPER_POWER 1.0

Potential<ImageWarperFactory> LinearFlowWarper::Factory::parse(const Ptv::Value& value) {
  int maxTransitionDistance = LINEAR_FLOW_WRAPER_MAX_TRANSITION_DISTANCE;
  if (Parse::populateInt("LinearFlowWarperFactory", value, "maxTransitionDistance", maxTransitionDistance, false) ==
      Parse::PopulateResult_WrongType) {
    return {Origin::Stitcher, ErrType::InvalidConfiguration,
            "Invalid type for 'maxTransitionDistance' configuration, expected int"};
  }
  double power = LINEAR_FLOW_WRAPER_POWER;
  if (Parse::populateDouble("LinearFlowWarperFactory", value, "power", power, false) ==
      Parse::PopulateResult_WrongType) {
    return {Origin::Stitcher, ErrType::InvalidConfiguration,
            "Invalid type for 'maxTransitionDistance' configuration, expected double"};
  }
  return Potential<ImageWarperFactory>(new LinearFlowWarper::Factory((float)maxTransitionDistance, power));
}

LinearFlowWarper::Factory::Factory(const float maxTransitionDistance, const double power)
    : maxTransitionDistance(maxTransitionDistance), power(power) {}

Ptv::Value* LinearFlowWarper::Factory::serialize() const {
  Ptv::Value* res = Ptv::Value::emptyObject();
  res->push("type", new Parse::JsonValue(LinearFlowWarper::getName()));
  res->push("maxTransitionDistance", new Parse::JsonValue(maxTransitionDistance));
  res->push("power", new Parse::JsonValue(power));
  return res;
}

std::string LinearFlowWarper::Factory::getImageWarperName() const { return LinearFlowWarper::getName(); }

std::string LinearFlowWarper::Factory::hash() const {
  std::stringstream ss;
  ss << "LinearFlowWarper "
     << "maxTransitionDistance " << maxTransitionDistance << "power " << power;
  return ss.str();
}

Potential<ImageWarper> LinearFlowWarper::Factory::create() const {
  std::map<std::string, float> parameters;
  parameters["maxTransitionDistance"] = (float)maxTransitionDistance;
  parameters["power"] = (float)power;
  return Potential<ImageWarper>(new LinearFlowWarper(parameters));
}

bool LinearFlowWarper::Factory::needsInputPreProcessing() const { return true; }

ImageWarperFactory* LinearFlowWarper::Factory::clone() const { return new Factory(maxTransitionDistance, power); }

LinearFlowWarper::LinearFlowWarper(const std::map<std::string, float>& parameters_) : ImageWarper(parameters_) {
  if (parameters.find("maxTransitionDistance") == parameters.end()) {
    parameters["maxTransitionDistance"] = LINEAR_FLOW_WRAPER_MAX_TRANSITION_DISTANCE;
  }
  if (parameters.find("power") == parameters.end()) {
    parameters["power"] = LINEAR_FLOW_WRAPER_POWER;
  }
}

std::string LinearFlowWarper::getName() { return std::string("linearflow"); }

const GPU::Buffer<const unsigned char> LinearFlowWarper::getLinearMaskWeight() const {
  return linearMaskWeight.borrow_const();
}

Rect LinearFlowWarper::getMaskRect() const { return mergerPair->getBoundingPanosIRect(); }

bool LinearFlowWarper::needImageFlow() const { return true; }

Status LinearFlowWarper::setupCommon(GPU::Stream gpuStream) {
  if (!mergerPair->doesOverlap()) {
    return Status::OK();
  }
  const float maxTransitionDistance = parameters["maxTransitionDistance"];
  const double power = parameters["power"];
  // Use Voronoi mask generated from voronoiKernel to generate a smooth transition
  // from the first to the second image
  // Prepare our own devMask
  Rect iRect = mergerPair->getBoundingPanosIRect();
  GPU::UniqueBuffer<uint32_t> work1;
  GPU::UniqueBuffer<uint32_t> work2;
  GPU::UniqueBuffer<uint32_t> devMask;
  FAIL_RETURN(devMask.alloc(iRect.getWidth() * iRect.getHeight(), "Linear Flow Warper"));
  FAIL_RETURN(work1.alloc(iRect.getWidth() * iRect.getHeight(), "Linear Flow Warper"));
  FAIL_RETURN(work2.alloc(iRect.getWidth() * iRect.getHeight(), "Linear Flow Warper"));
  FAIL_RETURN(linearMaskWeight.alloc(iRect.getWidth() * iRect.getHeight(), "Linear Flow Warper"));
  FAIL_RETURN(mergerPair->setupPairMappingMask(devMask.borrow(), gpuStream));
#ifdef WARPER_DEBUG
  {
    std::stringstream ss;
    ss.str("");
    ss << "C:/Users/Chuong.VideoStitch-09/Documents/GitHub/VideoStitch/VideoStitch-master/lib/src/test/data/flow/";
    ss << "panoToInput0-";
    ss << mergerPair->getImIdString(0) << " - " << mergerPair->getImIdString(1) << ".png";
    Debug::dumpRGBACoordinateDeviceBuffer(ss.str().c_str(), mergerPair->getPanoToInputSpaceCoordMapping(0),
                                          mergerPair->getBoundingPanoRect(0).getWidth(),
                                          mergerPair->getBoundingPanoRect(0).getHeight());
  }
  {
    std::stringstream ss;
    ss.str("");
    ss << "C:/Users/Chuong.VideoStitch-09/Documents/GitHub/VideoStitch/VideoStitch-master/lib/src/test/data/flow/";
    ss << "warperMaskDev-";
    ss << mergerPair->getImIdString(0) << " - " << mergerPair->getImIdString(1) << ".png";
    Debug::dumpRGBAIndexDeviceBuffer(ss.str().c_str(), devMask.borrow_const(), iRect.getWidth(), iRect.getHeight());
  }
#endif
  FAIL_RETURN(computeEuclideanDistanceMap(linearMaskWeight.borrow(), devMask.borrow(), work1.borrow(), work2.borrow(),
                                          iRect.getWidth(), iRect.getHeight(), 1 << 2, 1 << 1, false,
                                          maxTransitionDistance, (float)power, gpuStream));

#ifdef WARPER_DEBUG
  {
    std::stringstream ss;
    ss.str("");
    ss << "C:/Users/Chuong.VideoStitch-09/Documents/GitHub/VideoStitch/VideoStitch-master/lib/src/test/data/flow/";
    ss << "warperMaskWeight-";
    ss << mergerPair->getImIdString(0) << " - " << mergerPair->getImIdString(1) << ".png";
    Debug::dumpMonochromeDeviceBuffer<Debug::linear>(ss.str().c_str(), linearMaskWeight.borrow_const(),
                                                     iRect.getWidth(), iRect.getHeight());
  }
#endif
  return CUDA_STATUS;
}

ImageWarper::ImageWarperAlgorithm LinearFlowWarper::getWarperAlgorithm() const {
  return ImageWarper::ImageWarperAlgorithm::LinearFlowWarper;
}

}  // namespace Core
}  // namespace VideoStitch