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

#include "BLI_hash.h"
#include "BLI_math_vector.hh"
#include "BLI_rand.hh"
#include "BLI_task.hh"

#include "DNA_brush_types.h"

#include "BKE_context.hh"
#include "BKE_crazyspace.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_paint.hh"

#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"

#include "WM_api.hh"
#include "WM_types.hh"

#include "../paint_intern.hh"
#include "grease_pencil_intern.hh"

namespace blender::ed::sculpt_paint::greasepencil {

/* Use a hash to generate random numbers. */
static float hash_rng(uint32_t seed1, uint32_t seed2, int index)
{
  return BLI_hash_int_01(BLI_hash_int_3d(seed1, seed2, uint32_t(index)));
}

class RandomizeOperation : public GreasePencilStrokeOperationCommon {
 public:
  using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon;

  /* Get a different seed value for each stroke. */
  uint32_t unique_seed() const;

  void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
  void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
  void on_stroke_done(const bContext & /*C*/) override {}
};

uint32_t RandomizeOperation::unique_seed() const
{
  return RandomNumberGenerator::from_random_seed().get_uint32();
}

void RandomizeOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample)
{
  this->init_stroke(C, start_sample);
  this->init_auto_masking(C, start_sample);
}

void RandomizeOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample)
{
  Paint &paint = *BKE_paint_get_active_from_context(&C);
  const Brush &brush = *BKE_paint_brush(&paint);
  const int sculpt_mode_flag = brush.gpencil_settings->sculpt_mode_flag;

  this->foreach_editable_drawing_with_automask(
      C,
      [&](const GreasePencilStrokeParams &params,
          const IndexMask &point_mask,
          const DeltaProjectionFunc &projection_fn) {
        const uint32_t seed = this->unique_seed();

        bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params);
        const Array<float2> view_positions = view_positions_from_point_mask(params, point_mask);
        bke::CurvesGeometry &curves = params.drawing.strokes_for_write();
        bke::MutableAttributeAccessor attributes = curves.attributes_for_write();

        bool changed = false;
        if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_POSITION) {
          MutableSpan<float3> positions = curves.positions_for_write();

          /* Jitter is applied perpendicular to the mouse movement vector. */
          const float2 forward = math::normalize(this->mouse_delta(extension_sample));
          const float2 sideways = float2(-forward.y, forward.x);

          point_mask.foreach_index(
              [&](const int64_t point_i) {
                const float2 &co = view_positions[point_i];
                const float influence = brush_point_influence(
                    paint, brush, co, extension_sample, params.multi_frame_falloff);
                if (influence <= 0.0f) {
                  return;
                }
                const float noise = 2.0f * hash_rng(seed, 5678, point_i) - 1.0f;
                positions[point_i] += compute_orig_delta(
                    projection_fn, deformation, point_i, sideways * influence * noise);
              },
              exec_mode::grain_size(4096));

          if (curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
            MutableSpan<float3> handle_positions_left = curves.handle_positions_left_for_write();
            MutableSpan<float3> handle_positions_right = curves.handle_positions_right_for_write();

            const Array<float2> view_positions_left = view_positions_left_from_point_mask(
                params, point_mask);
            const Array<float2> view_positions_right = view_positions_right_from_point_mask(
                params, point_mask);

            point_mask.foreach_index(
                [&](const int64_t point_i) {
                  const float2 co_left = view_positions_left[point_i];
                  const float2 co_right = view_positions_right[point_i];
                  const float influence_left = brush_point_influence(
                      paint, brush, co_left, extension_sample, params.multi_frame_falloff);
                  const float influence_right = brush_point_influence(
                      paint, brush, co_right, extension_sample, params.multi_frame_falloff);

                  const float noise_left = 2.0f * hash_rng(seed, 3621, point_i) - 1.0f;
                  handle_positions_left[point_i] += compute_orig_delta(
                      projection_fn, deformation, point_i, sideways * influence_left * noise_left);
                  const float noise_right = 2.0f * hash_rng(seed, 9754, point_i) - 1.0f;
                  handle_positions_right[point_i] += compute_orig_delta(
                      projection_fn,
                      deformation,
                      point_i,
                      sideways * influence_right * noise_right);
                },
                exec_mode::grain_size(4096));

            curves.calculate_bezier_auto_handles();
            curves.calculate_bezier_aligned_handles();
          }

          params.drawing.tag_positions_changed();
          changed = true;
        }
        if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_STRENGTH) {
          MutableSpan<float> opacities = params.drawing.opacities_for_write();
          point_mask.foreach_index(
              [&](const int64_t point_i) {
                const float2 &co = view_positions[point_i];
                const float influence = brush_point_influence(
                    paint, brush, co, extension_sample, params.multi_frame_falloff);
                if (influence <= 0.0f) {
                  return;
                }
                const float noise = 2.0f * hash_rng(seed, 1212, point_i) - 1.0f;
                opacities[point_i] = math::clamp(
                    opacities[point_i] + influence * noise, 0.0f, 1.0f);
              },
              exec_mode::grain_size(4096));
          changed = true;
        }
        if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_THICKNESS) {
          const MutableSpan<float> radii = params.drawing.radii_for_write();
          point_mask.foreach_index(
              [&](const int64_t point_i) {
                const float2 &co = view_positions[point_i];
                const float influence = brush_point_influence(
                    paint, brush, co, extension_sample, params.multi_frame_falloff);
                if (influence <= 0.0f) {
                  return;
                }
                const float noise = 2.0f * hash_rng(seed, 1212, point_i) - 1.0f;
                radii[point_i] = math::max(radii[point_i] + influence * noise * 0.001f, 0.0f);
              },
              exec_mode::grain_size(4096));
          curves.tag_radii_changed();
          changed = true;
        }
        if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_UV) {
          if (bke::SpanAttributeWriter<float> rotations =
                  attributes.lookup_or_add_for_write_span<float>("rotation",
                                                                 bke::AttrDomain::Point))
          {
            point_mask.foreach_index(
                [&](const int64_t point_i) {
                  const float2 &co = view_positions[point_i];
                  const float influence = brush_point_influence(
                      paint, brush, co, extension_sample, params.multi_frame_falloff);
                  if (influence <= 0.0f) {
                    return;
                  }
                  const float noise = 2.0f * hash_rng(seed, 1212, point_i) - 1.0f;
                  rotations.span[point_i] = math::clamp(
                      rotations.span[point_i] + influence * noise, -float(M_PI_2), float(M_PI_2));
                },
                exec_mode::grain_size(4096));
            rotations.finish();
            changed = true;
          }
        }
        return changed;
      });
  this->stroke_extended(extension_sample);
}

std::unique_ptr<GreasePencilStrokeOperation> new_randomize_operation(
    const BrushStrokeMode stroke_mode)
{
  return std::make_unique<RandomizeOperation>(stroke_mode);
}

}  // namespace blender::ed::sculpt_paint::greasepencil
