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

#include "gpu/core1/voronoi.hpp"

#include "../kernel.hpp"

namespace VideoStitch {
namespace Core {

namespace {
#include "voronoi.xxd"
}

INDIRECT_REGISTER_OPENCL_PROGRAM(voronoi, true);

Status setInitialImageMask(GPU::Buffer<unsigned char> dst, GPU::Buffer<uint32_t> src, std::size_t width,
                           std::size_t height, uint32_t fromIdMask, GPU::Stream stream) {
  auto kernel2D = GPU::Kernel::get(PROGRAM(voronoi), KERNEL_STR(edtReflexiveKernel))
                      .setup2D(stream, (unsigned)width, (unsigned)height);
  return kernel2D.enqueueWithKernelArgs(dst, src, (unsigned)width, (unsigned)height, fromIdMask);
}

Status voronoiInit(GPU::Buffer<uint32_t> buffer, std::size_t width, std::size_t height, uint32_t blackMask,
                   uint32_t whiteMask, unsigned blockSize, GPU::Stream stream) {
  auto voronoiInit = GPU::Kernel::get(PROGRAM(voronoi), KERNEL_STR(voronoiInitKernel))
                         .setup2D(stream, (unsigned)width, (unsigned)height, blockSize, blockSize);
  return voronoiInit.enqueueWithKernelArgs(buffer.get(), (unsigned)width, (unsigned)height, blackMask, whiteMask);
}

Status voronoiComputeEuclidean(GPU::Buffer<uint32_t> dst, GPU::Buffer<uint32_t> src, std::size_t width,
                               std::size_t height, uint32_t step, bool hWrap, unsigned blockSize, GPU::Stream stream) {
  std::string voronoiComputeVariant;

  if (hWrap) {
    voronoiComputeVariant = KERNEL_STR(voronoiCompute_Wrap_distSqr);
  } else {
    voronoiComputeVariant = KERNEL_STR(voronoiCompute_NoWrap_distSqr);
  }

  PanoRegion region;
  region.panoDim = {-1};
  region.viewLeft = 0;
  region.viewTop = 0;
  region.viewWidth = (int32_t)width;
  region.viewHeight = (int32_t)height;

  auto voronoiCompute = GPU::Kernel::get(PROGRAM(voronoi), voronoiComputeVariant)
                            .setup2D(stream, (unsigned)width, (unsigned)height, blockSize);
  return voronoiCompute.enqueueWithKernelArgs(dst, src, region, step);
}

Status voronoiComputeErect(GPU::Buffer<uint32_t> dst, GPU::Buffer<uint32_t> src, const PanoRegion& region,
                           uint32_t step, bool hWrap, unsigned blockSize, GPU::Stream stream) {
  std::string voronoiComputeVariant;

  if (hWrap) {
    voronoiComputeVariant = KERNEL_STR(voronoiCompute_Wrap_distSphere);
  } else {
    voronoiComputeVariant = KERNEL_STR(voronoiCompute_NoWrap_distSphere);
  }

  auto voronoiCompute = GPU::Kernel::get(PROGRAM(voronoi), voronoiComputeVariant)
                            .setup2D(stream, region.viewWidth, region.viewHeight, blockSize);
  return voronoiCompute.enqueueWithKernelArgs(dst, src, region, step);
}

Status voronoiMakeMask(GPU::Buffer<unsigned char> dst, GPU::Buffer<uint32_t> src, std::size_t width, std::size_t height,
                       unsigned blockSize, GPU::Stream stream) {
  auto voronoiMakeMask = GPU::Kernel::get(PROGRAM(voronoi), KERNEL_STR(voronoiMakeMaskKernel))
                             .setup2D(stream, (unsigned)width, (unsigned)height, blockSize, blockSize);
  return voronoiMakeMask.enqueueWithKernelArgs(dst.get(), src, (unsigned)width, (unsigned)height);
}

Status initForMaskComputation(GPU::Buffer<uint32_t> dst, GPU::Buffer<const uint32_t> buf, std::size_t width,
                              std::size_t height, uint32_t mask, uint32_t otherMask, GPU::Stream stream) {
  auto edtInit =
      GPU::Kernel::get(PROGRAM(voronoi), KERNEL_STR(edtInit)).setup2D(stream, (unsigned)width, (unsigned)height);
  return edtInit.enqueueWithKernelArgs(dst, buf, (unsigned)width, (unsigned)height, mask, otherMask);
}

Status makeMaskErect(GPU::Buffer<unsigned char> dst, GPU::Buffer<uint32_t> blackResult,
                     GPU::Buffer<uint32_t> whiteResult, const PanoRegion& region, bool hWrap,
                     float maxTransitionDistance, float power, GPU::Stream stream) {
  const auto buildMaskVariant =
      (hWrap ? KERNEL_STR(buildTransitionMask_Wrap_distSphere) : KERNEL_STR(buildTransitionMask_NoWrap_distSphere));

  auto buildMask =
      GPU::Kernel::get(PROGRAM(voronoi), buildMaskVariant).setup2D(stream, region.viewWidth, region.viewHeight);
  return buildMask.enqueueWithKernelArgs(dst, blackResult, whiteResult, region, maxTransitionDistance, power);
}

Status makeMaskEuclidean(GPU::Buffer<unsigned char> dst, GPU::Buffer<uint32_t> blackResult,
                         GPU::Buffer<uint32_t> whiteResult, const PanoRegion& region, bool hWrap,
                         float maxTransitionDistance, float power, GPU::Stream stream) {
  const auto makeMaskVariant =
      (hWrap ? KERNEL_STR(buildTransitionMask_Wrap_distSqr) : KERNEL_STR(buildTransitionMask_NoWrap_distSqr));

  auto makeMask =
      GPU::Kernel::get(PROGRAM(voronoi), makeMaskVariant).setup2D(stream, region.viewWidth, region.viewHeight);
  return makeMask.enqueueWithKernelArgs(dst, blackResult, whiteResult, region, maxTransitionDistance, power);
}

Status extractEuclideanDist(GPU::Buffer<unsigned char> dst, GPU::Buffer<uint32_t> whiteResult, std::size_t width,
                            std::size_t height, bool hWrap, float maxTransitionDistance, float power,
                            GPU::Stream stream) {
  const auto extractDistVariant =
      (hWrap ? KERNEL_STR(extractDistKernel_Wrap_distSqr) : KERNEL_STR(extractDistKernel_NoWrap_distSqr));

  auto extract =
      GPU::Kernel::get(PROGRAM(voronoi), extractDistVariant).setup2D(stream, (unsigned)width, (unsigned)height);
  return extract.enqueueWithKernelArgs(dst, whiteResult, (unsigned)width, (unsigned)height, maxTransitionDistance,
                                       power);
}

}  // namespace Core
}  // namespace VideoStitch