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

/** \file
 * \ingroup animrig
 *
 * \brief Layered Action evaluation.
 */
#pragma once

#include "BLI_map.hh"
#include "BLI_string_ref.hh"

#include "ANIM_action.hh"

namespace blender::animrig {

/* Identifies the property that an evaluated animation value is for.
 *
 * This could be replaced with either `FCurveIdentifier` or `RNAPath`.  However,
 * `FCurveIdentifier` is semantically meant to represent an fcurve itself rather
 * than the property an fcurve might be for, and moreover not all animation will
 * necessarily come from fcurves in the future anyway.  `RNAPath` would be more
 * semantically appropriate, but it stores a full copy of the string component
 * of the path, and here we want to be lighter than that and use a string
 * reference.
 */
class PropIdentifier {
 public:
  /**
   * Reference to the RNA path of the property.
   *
   * This string is typically owned by the FCurve that animates the property.
   */
  StringRefNull rna_path;
  int array_index;

  PropIdentifier() = default;

  PropIdentifier(const StringRefNull rna_path, const int array_index)
      : rna_path(rna_path), array_index(array_index)
  {
  }

  bool operator==(const PropIdentifier &other) const
  {
    return rna_path == other.rna_path && array_index == other.array_index;
  }
  bool operator!=(const PropIdentifier &other) const
  {
    return !(*this == other);
  }

  uint64_t hash() const
  {
    return get_default_hash(rna_path, array_index);
  }
};

/**
 * The evaluated value for an animated property, along with its RNA pointer.
 */
class AnimatedProperty {
 public:
  float value;
  PathResolvedRNA prop_rna;

  AnimatedProperty(const float value, const PathResolvedRNA &prop_rna)
      : value(value), prop_rna(prop_rna)
  {
  }
};

/* Result of FCurve evaluation for an action slot.
 * Mapping from property identifier to its float value.
 *
 * Can be fed to the evaluation of the next layer, mixed with another strip, or
 * used to modify actual RNA properties.
 *
 * TODO: see if this is efficient, and contains enough info, for mixing. For now
 * this just captures the FCurve evaluation result, but doesn't have any info
 * about how to do the mixing (LERP, quaternion SLERP, etc.).
 */
class EvaluationResult {
 protected:
  using EvaluationMap = Map<PropIdentifier, AnimatedProperty>;
  EvaluationMap result_;

 public:
  EvaluationResult() = default;

  operator bool() const
  {
    return !this->is_empty();
  }
  bool is_empty() const
  {
    return result_.is_empty();
  }

  /**
   * When the expected count of entries is known, reserving with size instead of growing on demand
   * is more performant.
   */
  void reserve(const int64_t size)
  {
    result_.reserve(size);
  };

  void store(const StringRefNull rna_path,
             const int array_index,
             const float value,
             const PathResolvedRNA &prop_rna)
  {
    PropIdentifier key(rna_path, array_index);
    AnimatedProperty anim_prop(value, prop_rna);
    result_.add_overwrite(key, anim_prop);
  }

  AnimatedProperty value(const StringRefNull rna_path, const int array_index) const
  {
    PropIdentifier key(rna_path, array_index);
    return result_.lookup(key);
  }

  const AnimatedProperty *lookup_ptr(const PropIdentifier &key) const
  {
    return result_.lookup_ptr(key);
  }
  AnimatedProperty *lookup_ptr(const PropIdentifier &key)
  {
    return result_.lookup_ptr(key);
  }

  EvaluationMap::ItemIterator items() const
  {
    return result_.items();
  }
};

/**
 * Evaluate the given action for the given slot and animated ID.
 *
 * This does *not* apply the resulting values to the ID.  Instead, it returns
 * the resulting values in an `EvaluationResult`.
 */
EvaluationResult evaluate_action(PointerRNA &animated_id_ptr,
                                 Action &action,
                                 slot_handle_t slot_handle,
                                 const AnimationEvalContext &anim_eval_context);

/**
 * Top level animation evaluation function.
 *
 * Animate the given ID, using the layered Action and the given slot.
 *
 * \param flush_to_original: when true, look up the original data-block (assuming
 * the given one is an evaluated copy) and update that too.
 */
void evaluate_and_apply_action(PointerRNA &animated_id_ptr,
                               Action &action,
                               slot_handle_t slot_handle,
                               const AnimationEvalContext &anim_eval_context,
                               bool flush_to_original);

}  // namespace blender::animrig
