/* SPDX-FileCopyrightText: 2011 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

#include "BLI_math_vector_types.hh"

#include "DNA_movieclip_types.h"

#include "BKE_context.hh"
#include "BKE_lib_id.hh"
#include "BKE_tracking.hh"

#include "UI_interface.hh"

#include "GPU_shader.hh"
#include "GPU_texture.hh"

#include "COM_distortion_grid.hh"
#include "COM_node_operation.hh"
#include "COM_utilities.hh"

#include "node_composite_util.hh"

namespace blender::nodes::node_composite_movie_distortion_cc {

static const EnumPropertyItem type_items[] = {
    {int(compositor::DistortionType::Undistort), "UNDISTORT", 0, N_("Undistort"), ""},
    {int(compositor::DistortionType::Distort), "DISTORT", 0, N_("Distort"), ""},
    {0, nullptr, 0, nullptr, nullptr},
};

static void node_declare(NodeDeclarationBuilder &b)
{
  b.add_input<decl::Color>("Image"_ustr)
      .default_value({0.8f, 0.8f, 0.8f, 1.0f})
      .structure_type(StructureType::Dynamic);
  b.add_input<decl::Menu>("Type"_ustr)
      .default_value(compositor::DistortionType::Undistort)
      .static_items(type_items)
      .optional_label();

  b.add_output<decl::Color>("Image"_ustr).structure_type(StructureType::Dynamic);
}

static void node_init(const bContext *C, PointerRNA *ptr)
{
  bNode *node = static_cast<bNode *>(ptr->data);
  Scene *scene = CTX_data_scene(C);

  node->id = id_cast<ID *>(scene->clip);
  id_us_plus(node->id);
}

static void node_draw_buttons(ui::Layout &layout, bContext *C, PointerRNA *ptr)
{
  template_id(&layout, C, ptr, "clip", nullptr, "CLIP_OT_open", nullptr);
}

using namespace blender::compositor;

class MovieDistortionOperation : public NodeOperation {
 public:
  using NodeOperation::NodeOperation;

  void execute() override
  {
    const Result &input_image = this->get_input("Image");
    if (input_image.is_single_value() || !this->get_movie_clip()) {
      Result &output_image = this->get_result("Image");
      output_image.share_data(input_image);
      return;
    }

    const Result &distortion_grid = context().cache_manager().distortion_grids.get(
        context(),
        get_movie_clip(),
        input_image.domain(),
        get_distortion_type(),
        context().get_frame_number());

    if (this->context().use_gpu()) {
      this->execute_gpu(distortion_grid);
    }
    else {
      this->execute_cpu(distortion_grid);
    }
  }

  void execute_gpu(const Result &distortion_grid)
  {
    gpu::Shader *shader = context().get_shader("compositor_movie_distortion");
    GPU_shader_bind(shader);

    Result &input_image = get_input("Image");
    GPU_texture_extend_mode(input_image, GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER);
    GPU_texture_filter_mode(input_image, true);
    input_image.bind_as_texture(shader, "input_tx");

    distortion_grid.bind_as_texture(shader, "distortion_grid_tx");

    Result &output_image = get_result("Image");
    output_image.allocate_texture(distortion_grid.domain());
    output_image.bind_as_image(shader, "output_img");

    compute_dispatch_threads_at_least(shader, distortion_grid.domain().data_size);

    input_image.unbind_as_texture();
    distortion_grid.unbind_as_texture();
    output_image.unbind_as_image();
    GPU_shader_unbind();
  }

  void execute_cpu(const Result &distortion_grid)
  {
    Result &input = get_input("Image");

    Result &output = get_result("Image");
    output.allocate_texture(distortion_grid.domain());

    parallel_for(distortion_grid.domain().data_size, [&](const int2 texel) {
      output.store_pixel(
          texel, input.sample_bilinear_zero<Color>(distortion_grid.load_pixel<float2>(texel)));
    });
  }

  DistortionType get_distortion_type()
  {
    return DistortionType(this->get_input("Type").get_single_value_default<MenuValue>().value);
  }

  MovieClip *get_movie_clip()
  {
    return reinterpret_cast<MovieClip *>(node().id);
  }
};

static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
{
  return new MovieDistortionOperation(context, node);
}

static void node_register()
{
  static bke::bNodeType ntype;

  cmp_node_type_base(&ntype, "CompositorNodeMovieDistortion"_ustr, CMP_NODE_MOVIEDISTORTION);
  ntype.ui_name = "Movie Distortion";
  ntype.ui_description =
      "Remove lens distortion from footage, using motion tracking camera lens settings";
  ntype.enum_name_legacy = "MOVIEDISTORTION";
  ntype.nclass = NODE_CLASS_DISTORT;
  ntype.declare = node_declare;
  ntype.draw_buttons = node_draw_buttons;
  ntype.initfunc_api = node_init;
  ntype.get_compositor_operation = get_compositor_operation;

  bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)

}  // namespace blender::nodes::node_composite_movie_distortion_cc
