/* SPDX-License-Identifier: GPL-2.0-or-later
 * Copyright 2008 Blender Foundation. */

/** \file
 * \ingroup edgpencil
 * Operators for editing Grease Pencil strokes.
 */

#include <math.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "DNA_gpencil_types.h"
#include "DNA_view3d_types.h"

#include "BKE_context.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_curve.h"
#include "BKE_gpencil_geom.h"

#include "BLI_listbase.h"
#include "BLI_math.h"

#include "RNA_access.h"
#include "RNA_define.h"

#include "WM_api.h"
#include "WM_types.h"

#include "ED_gpencil.h"

#include "DEG_depsgraph.h"

#include "gpencil_intern.h"

/* -------------------------------------------------------------------- */
/** \name Enter Edit-Mode
 * \{ */

/* Poll callback for checking if there is an active layer and we are in curve edit mode. */
static bool gpencil_curve_edit_mode_poll(bContext *C)
{
  Object *ob = CTX_data_active_object(C);
  if ((ob == NULL) || (ob->type != OB_GPENCIL)) {
    return false;
  }
  bGPdata *gpd = (bGPdata *)ob->data;
  if (!GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) {
    return false;
  }

  bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd);
  return (gpl != NULL);
}

static int gpencil_stroke_enter_editcurve_mode_exec(bContext *C, wmOperator *op)
{
  Object *ob = CTX_data_active_object(C);
  bGPdata *gpd = ob->data;

  float error_threshold = RNA_float_get(op->ptr, "error_threshold");
  gpd->curve_edit_threshold = error_threshold;

  if (ELEM(NULL, gpd)) {
    return OPERATOR_CANCELLED;
  }

  LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
    LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
      if (gpf == gpl->actframe) {
        LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
          /* only allow selected and non-converted strokes to be transformed */
          if ((gps->flag & GP_STROKE_SELECT && gps->editcurve == NULL) ||
              (gps->editcurve != NULL && gps->editcurve->flag & GP_CURVE_NEEDS_STROKE_UPDATE)) {
            BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps);
            /* Update the selection from the stroke to the curve. */
            BKE_gpencil_editcurve_stroke_sync_selection(gpd, gps, gps->editcurve);
            gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE;
            BKE_gpencil_stroke_geometry_update(gpd, gps);
          }
        }
      }
    }
  }

  gpd->flag |= GP_DATA_CURVE_EDIT_MODE;

  /* notifiers */
  DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
  WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);

  return OPERATOR_FINISHED;
}

void GPENCIL_OT_stroke_enter_editcurve_mode(wmOperatorType *ot)
{
  PropertyRNA *prop;

  /* identifiers */
  ot->name = "Enter curve edit mode";
  ot->idname = "GPENCIL_OT_stroke_enter_editcurve_mode";
  ot->description = "Called to transform a stroke into a curve";

  /* api callbacks */
  ot->exec = gpencil_stroke_enter_editcurve_mode_exec;
  ot->poll = gpencil_active_layer_poll;

  /* flags */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  /* properties */
  prop = RNA_def_float(ot->srna,
                       "error_threshold",
                       0.1f,
                       FLT_MIN,
                       100.0f,
                       "Error Threshold",
                       "Threshold on the maximum deviation from the actual stroke",
                       FLT_MIN,
                       10.0f);
  RNA_def_property_ui_range(prop, FLT_MIN, 10.0f, 0.1f, 5);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Set Handle Type
 * \{ */

static int gpencil_editcurve_set_handle_type_exec(bContext *C, wmOperator *op)
{
  Object *ob = CTX_data_active_object(C);
  bGPdata *gpd = ob->data;
  const int handle_type = RNA_enum_get(op->ptr, "type");

  if (ELEM(NULL, gpd)) {
    return OPERATOR_CANCELLED;
  }

  GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc)
  {
    for (int i = 0; i < gpc->tot_curve_points; i++) {
      bGPDcurve_point *gpc_pt = &gpc->curve_points[i];

      if (gpc_pt->flag & GP_CURVE_POINT_SELECT) {
        BezTriple *bezt = &gpc_pt->bezt;

        if (bezt->f2 & SELECT) {
          bezt->h1 = handle_type;
          bezt->h2 = handle_type;
        }
        else {
          if (bezt->f1 & SELECT) {
            bezt->h1 = handle_type;
          }
          if (bezt->f3 & SELECT) {
            bezt->h2 = handle_type;
          }
        }
      }
    }

    BKE_gpencil_editcurve_recalculate_handles(gps);
    gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE;
    BKE_gpencil_stroke_geometry_update(gpd, gps);
  }
  GP_EDITABLE_CURVES_END(gps_iter);

  /* notifiers */
  DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
  WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);

  return OPERATOR_FINISHED;
}

void GPENCIL_OT_stroke_editcurve_set_handle_type(wmOperatorType *ot)
{
  static const EnumPropertyItem editcurve_handle_type_items[] = {
      {HD_FREE, "FREE", 0, "Free", ""},
      {HD_AUTO, "AUTOMATIC", 0, "Automatic", ""},
      {HD_VECT, "VECTOR", 0, "Vector", ""},
      {HD_ALIGN, "ALIGNED", 0, "Aligned", ""},
      {0, NULL, 0, NULL, NULL},
  };

  /* identifiers */
  ot->name = "Set handle type";
  ot->idname = "GPENCIL_OT_stroke_editcurve_set_handle_type";
  ot->description = "Set the type of a edit curve handle";

  /* api callbacks */
  ot->invoke = WM_menu_invoke;
  ot->exec = gpencil_editcurve_set_handle_type_exec;
  ot->poll = gpencil_curve_edit_mode_poll;

  /* flags */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  /* properties */
  ot->prop = RNA_def_enum(ot->srna, "type", editcurve_handle_type_items, 1, "Type", "Spline type");
}

/** \} */
