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

/** \file
 * \ingroup RNA
 */

#include <cstdlib>

#include "DNA_action_types.h"
#include "DNA_anim_types.h"
#include "DNA_scene_types.h"

#include "BLI_utildefines.h"

#include "MEM_guardedalloc.h"

#include "RNA_define.hh"
#include "RNA_enum_types.hh"

#include "rna_internal.hh"

#include "WM_types.hh"

/* Which part of bone(s) get baked */
/* TODO: icons? */
const EnumPropertyItem rna_enum_motionpath_bake_location_items[] = {
    {MOTIONPATH_BAKE_HEADS, "HEADS", 0, "Heads", "Calculate bone paths from heads"},
    {0, "TAILS", 0, "Tails", "Calculate bone paths from tails"},
#if 0
    {MOTIONPATH_BAKE_CENTERS,
     "CENTROID",
     0,
     "Centers",
     "Calculate bone paths from center of mass"},
#endif
    {0, nullptr, 0, nullptr, nullptr},
};

const EnumPropertyItem rna_enum_motionpath_display_type_items[] = {
    {MOTIONPATH_TYPE_ACFRA,
     "CURRENT_FRAME",
     0,
     "Around Frame",
     "Display Paths of poses within a fixed number of frames around the current frame"},
    {MOTIONPATH_TYPE_RANGE,
     "RANGE",
     0,
     "In Range",
     "Display Paths of poses within specified range"},
    {0, nullptr, 0, nullptr, nullptr},
};

const EnumPropertyItem rna_enum_motionpath_range_items[] = {
    {MOTIONPATH_RANGE_KEYS_ALL, "KEYS_ALL", 0, "All Keys", "From the first keyframe to the last"},
    {MOTIONPATH_RANGE_KEYS_SELECTED,
     "KEYS_SELECTED",
     0,
     "Selected Keys",
     "From the first selected keyframe to the last"},
    {MOTIONPATH_RANGE_SCENE, "SCENE", 0, "Scene Frame Range", "The entire Scene / Preview range"},
    {MOTIONPATH_RANGE_MANUAL, "MANUAL", 0, "Manual Range", "Manually determined frame range"},
    {0, nullptr, 0, nullptr, nullptr},
};

#ifdef RNA_RUNTIME

static PointerRNA rna_AnimViz_motion_paths_get(PointerRNA *ptr)
{
  return rna_pointer_inherit_refine(ptr, &RNA_AnimVizMotionPaths, ptr->data);
}

static void rna_AnimViz_path_start_frame_set(PointerRNA *ptr, int value)
{
  bAnimVizSettings *data = (bAnimVizSettings *)ptr->data;

  /* XXX: Watch it! Path Start > MAXFRAME/2 could be a problem. */
  data->path_sf = value;
  FRAMENUMBER_MIN_CLAMP(data->path_sf);

  CLAMP(data->path_ef, data->path_sf + 1, MAXFRAME / 2);
}

static void rna_AnimViz_path_end_frame_set(PointerRNA *ptr, int value)
{
  bAnimVizSettings *data = (bAnimVizSettings *)ptr->data;

  data->path_ef = value;
  CLAMP_MAX(data->path_sf, data->path_ef - 1);
  if (U.flag & USER_NONEGFRAMES) {
    CLAMP_MIN(data->path_sf, 0);
    CLAMP_MIN(data->path_ef, 1);
  }
}

#else

void rna_def_motionpath_common(StructRNA *srna)
{
  PropertyRNA *prop;

  prop = RNA_def_property(srna, "motion_path", PROP_POINTER, PROP_NONE);
  RNA_def_property_pointer_sdna(prop, nullptr, "mpath");
  RNA_def_property_ui_text(prop, "Motion Path", "Motion Path for this element");
}

static void rna_def_animviz_motionpath_vert(BlenderRNA *brna)
{
  StructRNA *srna;
  PropertyRNA *prop;

  srna = RNA_def_struct(brna, "MotionPathVert", nullptr);
  RNA_def_struct_sdna(srna, "bMotionPathVert");
  RNA_def_struct_ui_text(srna, "Motion Path Cache Point", "Cached location on path");

  prop = RNA_def_property(srna, "co", PROP_FLOAT, PROP_XYZ);
  RNA_def_property_array(prop, 3);
  RNA_def_property_ui_text(prop, "Coordinates", "");

  prop = RNA_def_property(srna, "select", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, nullptr, "flag", MOTIONPATH_VERT_SEL);
  RNA_def_property_ui_text(prop, "Select", "Path point is selected for editing");
}

static void rna_def_animviz_motion_path(BlenderRNA *brna)
{
  StructRNA *srna;
  PropertyRNA *prop;

  srna = RNA_def_struct(brna, "MotionPath", nullptr);
  RNA_def_struct_sdna(srna, "bMotionPath");
  RNA_def_struct_ui_text(
      srna, "Motion Path", "Cache of the world-space positions of an element over a frame range");

  /* Collections */
  prop = RNA_def_property(srna, "points", PROP_COLLECTION, PROP_NONE);
  RNA_def_property_collection_sdna(prop, nullptr, "points", "length");
  RNA_def_property_struct_type(prop, "MotionPathVert");
  RNA_def_property_ui_text(prop, "Motion Path Points", "Cached positions per frame");

  /* Playback Ranges */
  prop = RNA_def_property(srna, "frame_start", PROP_INT, PROP_TIME);
  RNA_def_property_int_sdna(prop, nullptr, "start_frame");
  RNA_def_property_clear_flag(prop, PROP_EDITABLE);
  RNA_def_property_ui_text(prop, "Start Frame", "Starting frame of the stored range");

  prop = RNA_def_property(srna, "frame_end", PROP_INT, PROP_TIME);
  RNA_def_property_int_sdna(prop, nullptr, "end_frame");
  RNA_def_property_clear_flag(prop, PROP_EDITABLE);
  RNA_def_property_ui_text(prop, "End Frame", "End frame of the stored range");

  prop = RNA_def_property(srna, "length", PROP_INT, PROP_TIME);
  RNA_def_property_clear_flag(prop, PROP_EDITABLE);
  RNA_def_property_ui_text(prop, "Length", "Number of frames cached");

  /* Custom Color */
  prop = RNA_def_property(srna, "color", PROP_FLOAT, PROP_COLOR_GAMMA);
  RNA_def_property_array(prop, 3);
  RNA_def_property_ui_text(
      prop, "Color Pre", "Custom color for motion path before the current frame");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);
  RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);

  prop = RNA_def_property(srna, "color_post", PROP_FLOAT, PROP_COLOR_GAMMA);
  RNA_def_property_array(prop, 3);
  RNA_def_property_ui_text(
      prop, "Color Post", "Custom color for motion path after the current frame");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);
  RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);

  /* Line width */
  prop = RNA_def_property(srna, "line_thickness", PROP_INT, PROP_NONE);
  RNA_def_property_int_sdna(prop, nullptr, "line_thickness");
  RNA_def_property_range(prop, 1, 6);
  RNA_def_property_ui_text(prop, "Line Thickness", "Line thickness for motion path");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);
  RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);

  /* Settings */
  prop = RNA_def_property(srna, "use_bone_head", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, nullptr, "flag", MOTIONPATH_FLAG_BHEAD);
  RNA_def_property_clear_flag(prop, PROP_EDITABLE); /* xxx */
  RNA_def_property_ui_text(
      prop,
      "Use Bone Heads",
      "For PoseBone paths, use the bone head location when calculating this path");

  /* FIXME: Motion Paths are not currently editable... */
  prop = RNA_def_property(srna, "is_modified", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, nullptr, "flag", MOTIONPATH_FLAG_EDIT);
  RNA_def_property_ui_text(prop, "Edit Path", "Path is being edited");

  /* Use custom color */
  prop = RNA_def_property(srna, "use_custom_color", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, nullptr, "flag", MOTIONPATH_FLAG_CUSTOM);
  RNA_def_property_ui_text(prop, "Custom Colors", "Use custom color for this motion path");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);
  RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);

  /* Draw lines between keyframes */
  prop = RNA_def_property(srna, "lines", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, nullptr, "flag", MOTIONPATH_FLAG_LINES);
  RNA_def_property_ui_text(prop, "Lines", "Use straight lines between keyframe points");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);
}

/* --- */

static void rna_def_animviz_paths(BlenderRNA *brna)
{
  StructRNA *srna;
  PropertyRNA *prop;

  srna = RNA_def_struct(brna, "AnimVizMotionPaths", nullptr);
  RNA_def_struct_sdna(srna, "bAnimVizSettings");
  RNA_def_struct_nested(brna, srna, "AnimViz");
  RNA_def_struct_ui_text(
      srna, "Motion Path Settings", "Motion Path settings for animation visualization");

  RNA_define_lib_overridable(true);

  /* Enums */
  prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE);
  RNA_def_property_enum_sdna(prop, nullptr, "path_type");
  RNA_def_property_enum_items(prop, rna_enum_motionpath_display_type_items);
  RNA_def_property_ui_text(prop, "Paths Type", "Type of range to show for Motion Paths");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);

  prop = RNA_def_property(srna, "range", PROP_ENUM, PROP_NONE);
  RNA_def_property_enum_sdna(prop, nullptr, "path_range");
  RNA_def_property_enum_items(prop, rna_enum_motionpath_range_items);
  RNA_def_property_ui_text(prop, "Paths Range", "Type of range to calculate for Motion Paths");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);

  prop = RNA_def_property(srna, "bake_location", PROP_ENUM, PROP_NONE);
  RNA_def_property_enum_bitflag_sdna(prop, nullptr, "path_bakeflag");
  RNA_def_property_enum_items(prop, rna_enum_motionpath_bake_location_items);
  RNA_def_property_ui_text(prop, "Bake Location", "When calculating Bone Paths, use Head or Tips");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);

  /* Settings */
  prop = RNA_def_property(srna, "show_frame_numbers", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, nullptr, "path_viewflag", MOTIONPATH_VIEW_FNUMS);
  RNA_def_property_ui_text(prop, "Show Frame Numbers", "Show frame numbers on Motion Paths");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);

  prop = RNA_def_property(srna, "show_keyframe_highlight", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, nullptr, "path_viewflag", MOTIONPATH_VIEW_KFRAS);
  RNA_def_property_ui_text(
      prop, "Highlight Keyframes", "Emphasize position of keyframes on Motion Paths");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);

  prop = RNA_def_property(srna, "show_keyframe_numbers", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, nullptr, "path_viewflag", MOTIONPATH_VIEW_KFNOS);
  RNA_def_property_ui_text(
      prop, "Show Keyframe Numbers", "Show frame numbers of Keyframes on Motion Paths");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);

  prop = RNA_def_property(srna, "show_keyframe_action_all", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, nullptr, "path_viewflag", MOTIONPATH_VIEW_KFACT);
  RNA_def_property_ui_text(
      prop,
      "All Action Keyframes",
      "For bone motion paths, search whole Action for keyframes instead of in group"
      " with matching name only (is slower)");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);

  prop = RNA_def_property(srna, "frame_step", PROP_INT, PROP_NONE);
  RNA_def_property_int_sdna(prop, nullptr, "path_step");
  RNA_def_property_range(prop, 1, 100);
  RNA_def_property_ui_text(
      prop,
      "Frame Step",
      "Number of frames between paths shown (not for 'On Keyframes' Onion-skinning method)");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);

  /* Playback Ranges */
  prop = RNA_def_property(srna, "frame_start", PROP_INT, PROP_TIME);
  RNA_def_property_int_sdna(prop, nullptr, "path_sf");
  RNA_def_property_int_funcs(prop, nullptr, "rna_AnimViz_path_start_frame_set", nullptr);
  RNA_def_property_ui_text(prop,
                           "Start Frame",
                           "Starting frame of range of paths to display/calculate "
                           "(not for 'Around Frame' Onion-skinning method)");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);

  prop = RNA_def_property(srna, "frame_end", PROP_INT, PROP_TIME);
  RNA_def_property_int_sdna(prop, nullptr, "path_ef");
  RNA_def_property_int_funcs(prop, nullptr, "rna_AnimViz_path_end_frame_set", nullptr);
  RNA_def_property_ui_text(prop,
                           "End Frame",
                           "End frame of range of paths to display/calculate "
                           "(not for 'Around Frame' Onion-skinning method)");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);

  /* Around Current Ranges */
  prop = RNA_def_property(srna, "frame_before", PROP_INT, PROP_TIME);
  RNA_def_property_int_sdna(prop, nullptr, "path_bc");
  RNA_def_property_range(prop, 1, MAXFRAMEF / 2);
  RNA_def_property_ui_text(prop,
                           "Before Current",
                           "Number of frames to show before the current frame "
                           "(only for 'Around Frame' Onion-skinning method)");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);

  prop = RNA_def_property(srna, "frame_after", PROP_INT, PROP_TIME);
  RNA_def_property_int_sdna(prop, nullptr, "path_ac");
  RNA_def_property_range(prop, 1, MAXFRAMEF / 2);
  RNA_def_property_ui_text(prop,
                           "After Current",
                           "Number of frames to show after the current frame "
                           "(only for 'Around Frame' Onion-skinning method)");
  RNA_def_property_update(prop, NC_OBJECT | ND_DRAW_ANIMVIZ, nullptr);

  /* Readonly Property - Do any motion paths exist/need updating? (Mainly for bone paths) */
  prop = RNA_def_property(srna, "has_motion_paths", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, nullptr, "path_bakeflag", MOTIONPATH_BAKE_HAS_PATHS);
  /* NOTE: This is really an internal state var for convenience, so don't allow edits! */
  RNA_def_property_clear_flag(prop, PROP_EDITABLE);
  RNA_def_property_ui_text(
      prop, "Has Motion Paths", "Are there any bone paths that will need updating (read-only)");

  /* If enabled, bakes the motion paths into camera space. */
  prop = RNA_def_property(srna, "use_camera_space_bake", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, nullptr, "path_bakeflag", MOTIONPATH_BAKE_CAMERA_SPACE);
  RNA_def_property_ui_text(
      prop,
      "Bake to active Camera",
      "Motion path points will be baked into the camera space of the active camera. This means "
      "they will only look right when looking through that camera. Switching cameras using "
      "markers is not supported");

  RNA_define_lib_overridable(false);
}

/* --- */

void rna_def_animviz_common(StructRNA *srna)
{
  PropertyRNA *prop;

  prop = RNA_def_property(srna, "animation_visualization", PROP_POINTER, PROP_NONE);
  RNA_def_property_flag(prop, PROP_NEVER_NULL);
  RNA_def_property_pointer_sdna(prop, nullptr, "avs");
  RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
  RNA_def_property_ui_text(prop, "Animation Visualization", "Animation data for this data-block");
}

static void rna_def_animviz(BlenderRNA *brna)
{
  StructRNA *srna;
  PropertyRNA *prop;

  srna = RNA_def_struct(brna, "AnimViz", nullptr);
  RNA_def_struct_sdna(srna, "bAnimVizSettings");
  RNA_def_struct_ui_text(
      srna, "Animation Visualization", "Settings for the visualization of motion");

  /* motion path settings (nested struct) */
  prop = RNA_def_property(srna, "motion_path", PROP_POINTER, PROP_NONE);
  RNA_def_property_flag(prop, PROP_NEVER_NULL);
  RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
  RNA_def_property_struct_type(prop, "AnimVizMotionPaths");
  RNA_def_property_pointer_funcs(prop, "rna_AnimViz_motion_paths_get", nullptr, nullptr, nullptr);
  RNA_def_property_ui_text(prop, "Motion Paths", "Motion Path settings for visualization");
}

/* --- */

void RNA_def_animviz(BlenderRNA *brna)
{
  rna_def_animviz(brna);
  rna_def_animviz_paths(brna);

  rna_def_animviz_motion_path(brna);
  rna_def_animviz_motionpath_vert(brna);
}

#endif
