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

#include "COM_TranslateOperation.h"

namespace blender::compositor {

TranslateOperation::TranslateOperation() : TranslateOperation(DataType::Color) {}
TranslateOperation::TranslateOperation(DataType data_type, ResizeMode resize_mode)
{
  this->add_input_socket(data_type, resize_mode);
  this->add_input_socket(DataType::Value, ResizeMode::None);
  this->add_input_socket(DataType::Value, ResizeMode::None);
  this->add_output_socket(data_type);
  this->set_canvas_input_index(0);
  is_delta_set_ = false;
  is_relative_ = false;
  this->x_extend_mode_ = MemoryBufferExtend::Clip;
  this->y_extend_mode_ = MemoryBufferExtend::Clip;
  this->sampler_ = PixelSampler::Nearest;

  this->flags_.can_be_constant = true;
}

void TranslateOperation::set_wrapping(int wrapping_type)
{
  switch (wrapping_type) {
    case CMP_NODE_WRAP_X:
      x_extend_mode_ = MemoryBufferExtend::Repeat;
      break;
    case CMP_NODE_WRAP_Y:
      y_extend_mode_ = MemoryBufferExtend::Repeat;
      break;
    case CMP_NODE_WRAP_XY:
      x_extend_mode_ = MemoryBufferExtend::Repeat;
      y_extend_mode_ = MemoryBufferExtend::Repeat;
      break;
    default:
      break;
  }
}

void TranslateOperation::update_memory_buffer_started(MemoryBuffer * /*output*/,
                                                      const rcti & /*area*/,
                                                      Span<MemoryBuffer *> /*inputs*/)
{
  ensure_delta();
}

void TranslateOperation::get_area_of_interest(const int input_idx,
                                              const rcti &output_area,
                                              rcti &r_input_area)
{
  if (input_idx == 0) {
    ensure_delta();
    r_input_area = output_area;
    if (x_extend_mode_ == MemoryBufferExtend::Clip) {
      const int delta_x = this->get_delta_x();
      BLI_rcti_translate(&r_input_area, -delta_x, 0);
    }
    else if (x_extend_mode_ == MemoryBufferExtend::Repeat) {
      /* The region of interest should consider the whole input image to avoid cropping effects,
       * e.g. by prior scaling or rotating.
       * NOTE: this is still consistent with immediate realization of transform nodes in GPU
       * compositor, where nodes are to be evaluated from left to right. */
      const int in_width = get_width();
      BLI_rcti_resize_x(&r_input_area, in_width);
    }

    if (y_extend_mode_ == MemoryBufferExtend::Clip) {
      const int delta_y = this->get_delta_y();
      BLI_rcti_translate(&r_input_area, 0, -delta_y);
    }
    else if (y_extend_mode_ == MemoryBufferExtend::Repeat) {
      const int in_height = get_height();
      BLI_rcti_resize_y(&r_input_area, in_height);
    }
  }
  else {
    r_input_area = output_area;
  }
}

void TranslateOperation::update_memory_buffer_partial(MemoryBuffer *output,
                                                      const rcti &area,
                                                      Span<MemoryBuffer *> inputs)
{
  MemoryBuffer *input = inputs[0];
  /* Linking X and Y input sockets to non-constant input may result in a non-constant output, see
   * Stabilize2dNode for example. */
  if (input->is_a_single_elem() && output->is_a_single_elem()) {
    copy_v4_v4(output->get_elem(0, 0), input->get_elem(0, 0));
    return;
  }

  /* Some compositor operations produce an empty output buffer by specifying a COM_AREA_NONE canvas
   * to indicate an invalid output, for instance, when the Mask operation reference an invalid
   * mask. The intention is that this buffer would signal that a fallback value would fill the
   * canvas of consumer operations. Since the aforementioned filling is achieved through the
   * Translate operation as part of canvas conversion in COM_convert_canvas, we handle the empty
   * buffer case here and fill the output using a fallback black color. */
  if (BLI_rcti_is_empty(&input->get_rect())) {
    const float value[4] = {0.0f, 0.0f, 0.0f, 1.0f};
    output->fill(area, value);
    return;
  }

  float delta_x = this->get_delta_x();
  float delta_y = this->get_delta_y();
  if (sampler_ == PixelSampler::Nearest) {
    /* Use same rounding convention for GPU compositor. */
    delta_x = round(delta_x);
    delta_y = round(delta_y);
  }

  for (int y = area.ymin; y < area.ymax; y++) {
    float *out = output->get_elem(area.xmin, y);
    for (int x = area.xmin; x < area.xmax; x++) {
      const float input_x = x - delta_x;
      const float input_y = y - delta_y;
      input->read(out, input_x, input_y, sampler_, x_extend_mode_, y_extend_mode_);
      out += output->elem_stride;
    }
  }
}

TranslateCanvasOperation::TranslateCanvasOperation()
    : TranslateOperation(DataType::Color, ResizeMode::None)
{
}

void TranslateCanvasOperation::determine_canvas(const rcti &preferred_area, rcti &r_area)
{
  const bool determined =
      get_input_socket(IMAGE_INPUT_INDEX)->determine_canvas(preferred_area, r_area);
  if (determined) {
    NodeOperationInput *x_socket = get_input_socket(X_INPUT_INDEX);
    NodeOperationInput *y_socket = get_input_socket(Y_INPUT_INDEX);
    rcti unused = COM_AREA_NONE;
    x_socket->determine_canvas(r_area, unused);
    y_socket->determine_canvas(r_area, unused);

    ensure_delta();
    const float delta_x = x_extend_mode_ == MemoryBufferExtend::Clip ? get_delta_x() : 0.0f;
    const float delta_y = y_extend_mode_ == MemoryBufferExtend::Clip ? get_delta_y() : 0.0f;
    BLI_rcti_translate(&r_area, delta_x, delta_y);
  }
}

}  // namespace blender::compositor
