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

#include <algorithm> /* std::find */
#include <map>
#include <vector>

#include "BCAnimationCurve.h"
#include "BCAnimationSampler.h"
#include "BCMath.h"
#include "ExportSettings.h"
#include "collada_utils.h"

#include "BKE_action.h"
#include "BKE_constraint.h"
#include "BKE_key.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_material.h"

#include "BLI_listbase.h"

#include "DNA_anim_types.h"
#include "DNA_constraint_types.h"
#include "DNA_key_types.h"
#include "DNA_scene_types.h"

#include "ED_object.h"

static std::string EMPTY_STRING;
static BCAnimationCurveMap BCEmptyAnimationCurves;

BCAnimationSampler::BCAnimationSampler(BCExportSettings &export_settings, BCObjectSet &object_set)
    : export_settings(export_settings)
{
  BCObjectSet::iterator it;
  for (it = object_set.begin(); it != object_set.end(); ++it) {
    Object *ob = *it;
    add_object(ob);
  }
}

BCAnimationSampler::~BCAnimationSampler()
{
  BCAnimationObjectMap::iterator it;
  for (it = objects.begin(); it != objects.end(); ++it) {
    BCAnimation *animation = it->second;
    delete animation;
  }
}

void BCAnimationSampler::add_object(Object *ob)
{
  BlenderContext blender_context = export_settings.get_blender_context();
  BCAnimation *animation = new BCAnimation(blender_context.get_context(), ob);
  objects[ob] = animation;

  initialize_keyframes(animation->frame_set, ob);
  initialize_curves(animation->curve_map, ob);
}

BCAnimationCurveMap *BCAnimationSampler::get_curves(Object *ob)
{
  BCAnimation &animation = *objects[ob];
  if (animation.curve_map.empty()) {
    initialize_curves(animation.curve_map, ob);
  }
  return &animation.curve_map;
}

static void get_sample_frames(BCFrameSet &sample_frames,
                              int sampling_rate,
                              bool keyframe_at_end,
                              Scene *scene)
{
  sample_frames.clear();

  if (sampling_rate < 1) {
    return; /* no sample frames in this case */
  }

  float sfra = scene->r.sfra;
  float efra = scene->r.efra;

  int frame_index;
  for (frame_index = nearbyint(sfra); frame_index < efra; frame_index += sampling_rate) {
    sample_frames.insert(frame_index);
  }

  if (frame_index >= efra && keyframe_at_end) {
    sample_frames.insert(efra);
  }
}

static bool is_object_keyframe(Object *ob, int frame_index)
{
  return false;
}

static void add_keyframes_from(bAction *action, BCFrameSet &frameset)
{
  if (action) {
    FCurve *fcu = nullptr;
    for (fcu = (FCurve *)action->curves.first; fcu; fcu = fcu->next) {
      BezTriple *bezt = fcu->bezt;
      for (int i = 0; i < fcu->totvert; bezt++, i++) {
        int frame_index = nearbyint(bezt->vec[1][0]);
        frameset.insert(frame_index);
      }
    }
  }
}

void BCAnimationSampler::check_property_is_animated(
    BCAnimation &animation, float *ref, float *val, std::string data_path, int length)
{
  for (int array_index = 0; array_index < length; array_index++) {
    if (!bc_in_range(ref[length], val[length], 0.00001)) {
      BCCurveKey key(BC_ANIMATION_TYPE_OBJECT, data_path, array_index);
      BCAnimationCurveMap::iterator it = animation.curve_map.find(key);
      if (it == animation.curve_map.end()) {
        animation.curve_map[key] = new BCAnimationCurve(key, animation.get_reference());
      }
    }
  }
}

void BCAnimationSampler::update_animation_curves(BCAnimation &animation,
                                                 BCSample &sample,
                                                 Object *ob,
                                                 int frame)
{
  BCAnimationCurveMap::iterator it;
  for (it = animation.curve_map.begin(); it != animation.curve_map.end(); ++it) {
    BCAnimationCurve *curve = it->second;
    if (curve->is_transform_curve()) {
      curve->add_value_from_matrix(sample, frame);
    }
    else {
      curve->add_value_from_rna(frame);
    }
  }
}

BCSample &BCAnimationSampler::sample_object(Object *ob, int frame_index, bool for_opensim)
{
  BCSample &ob_sample = sample_data.add(ob, frame_index);
#if 0
  if (export_settings.get_apply_global_orientation()) {
    const BCMatrix &global_transform = export_settings.get_global_transform();
    ob_sample.get_matrix(global_transform);
  }
#endif

  if (ob->type == OB_ARMATURE) {
    bPoseChannel *pchan;
    for (pchan = (bPoseChannel *)ob->pose->chanbase.first; pchan; pchan = pchan->next) {
      Bone *bone = pchan->bone;
      Matrix bmat;
      if (bc_bone_matrix_local_get(ob, bone, bmat, for_opensim)) {

        ob_sample.add_bone_matrix(bone, bmat);
      }
    }
  }
  return ob_sample;
}

void BCAnimationSampler::sample_scene(BCExportSettings &export_settings, bool keyframe_at_end)
{
  BlenderContext blender_context = export_settings.get_blender_context();
  int sampling_rate = export_settings.get_sampling_rate();
  bool for_opensim = export_settings.get_open_sim();
  bool keep_keyframes = export_settings.get_keep_keyframes();
  BC_export_animation_type export_animation_type = export_settings.get_export_animation_type();

  Scene *scene = blender_context.get_scene();
  BCFrameSet scene_sample_frames;
  get_sample_frames(scene_sample_frames, sampling_rate, keyframe_at_end, scene);
  BCFrameSet::iterator it;

  int startframe = scene->r.sfra;
  int endframe = scene->r.efra;

  for (int frame_index = startframe; frame_index <= endframe; frame_index++) {
    /* Loop over all frames and decide for each frame if sampling is necessary */
    bool is_scene_sample_frame = false;
    bool needs_update = true;
    if (scene_sample_frames.find(frame_index) != scene_sample_frames.end()) {
      bc_update_scene(blender_context, frame_index);
      needs_update = false;
      is_scene_sample_frame = true;
    }

    bool needs_sampling = is_scene_sample_frame || keep_keyframes ||
                          export_animation_type == BC_ANIMATION_EXPORT_KEYS;
    if (!needs_sampling) {
      continue;
    }

    BCAnimationObjectMap::iterator obit;
    for (obit = objects.begin(); obit != objects.end(); ++obit) {
      Object *ob = obit->first;
      BCAnimation *animation = obit->second;
      BCFrameSet &object_keyframes = animation->frame_set;
      if (is_scene_sample_frame || object_keyframes.find(frame_index) != object_keyframes.end()) {

        if (needs_update) {
          bc_update_scene(blender_context, frame_index);
          needs_update = false;
        }

        BCSample &sample = sample_object(ob, frame_index, for_opensim);
        update_animation_curves(*animation, sample, ob, frame_index);
      }
    }
  }
}

bool BCAnimationSampler::is_animated_by_constraint(Object *ob,
                                                   ListBase *conlist,
                                                   std::set<Object *> &animated_objects)
{
  bConstraint *con;
  for (con = (bConstraint *)conlist->first; con; con = con->next) {
    ListBase targets = {nullptr, nullptr};

    if (!bc_validateConstraints(con)) {
      continue;
    }

    if (BKE_constraint_targets_get(con, &targets)) {
      bConstraintTarget *ct;
      Object *obtar;
      bool found = false;

      for (ct = (bConstraintTarget *)targets.first; ct; ct = ct->next) {
        obtar = ct->tar;
        if (obtar) {
          if (animated_objects.find(obtar) != animated_objects.end()) {
            found = true;
            break;
          }
        }
      }
      BKE_constraint_targets_flush(con, &targets, true);
      return found;
    }
  }
  return false;
}

void BCAnimationSampler::find_depending_animated(std::set<Object *> &animated_objects,
                                                 std::set<Object *> &candidates)
{
  bool found_more;
  do {
    found_more = false;
    std::set<Object *>::iterator it;
    for (it = candidates.begin(); it != candidates.end(); ++it) {
      Object *cob = *it;
      ListBase *conlist = ED_object_constraint_active_list(cob);
      if (is_animated_by_constraint(cob, conlist, animated_objects)) {
        animated_objects.insert(cob);
        candidates.erase(cob);
        found_more = true;
        break;
      }
    }
  } while (found_more && !candidates.empty());
}

void BCAnimationSampler::get_animated_from_export_set(std::set<Object *> &animated_objects,
                                                      LinkNode &export_set)
{
  /* Check if this object is animated. That is: Check if it has its own action, or:
   *
   * - Check if it has constraints to other objects.
   * - at least one of the other objects is animated as well.
   */

  animated_objects.clear();
  std::set<Object *> static_objects;
  std::set<Object *> candidates;

  LinkNode *node;
  for (node = &export_set; node; node = node->next) {
    Object *cob = (Object *)node->link;
    if (bc_has_animations(cob)) {
      animated_objects.insert(cob);
    }
    else {
      ListBase conlist = cob->constraints;
      if (conlist.first) {
        candidates.insert(cob);
      }
    }
  }
  find_depending_animated(animated_objects, candidates);
}

void BCAnimationSampler::get_object_frames(BCFrames &frames, Object *ob)
{
  sample_data.get_frames(ob, frames);
}

void BCAnimationSampler::get_bone_frames(BCFrames &frames, Object *ob, Bone *bone)
{
  sample_data.get_frames(ob, bone, frames);
}

bool BCAnimationSampler::get_bone_samples(BCMatrixSampleMap &samples, Object *ob, Bone *bone)
{
  sample_data.get_matrices(ob, bone, samples);
  return bc_is_animated(samples);
}

bool BCAnimationSampler::get_object_samples(BCMatrixSampleMap &samples, Object *ob)
{
  sample_data.get_matrices(ob, samples);
  return bc_is_animated(samples);
}

#if 0
/**
 * Add sampled values to #FCurve
 * If no #FCurve exists, create a temporary #FCurve;
 * \note The temporary #FCurve will later be removed when the
 * #BCAnimationSampler is removed (by its destructor).
 *
 * \param curve: The curve to which the data is added.
 * \param matrices: The set of matrix values from where the data is taken.
 * \param animation_type:
 * - #BC_ANIMATION_EXPORT_SAMPLES: Use all matrix data.
 * - #BC_ANIMATION_EXPORT_KEYS: Only take data from matrices for keyframes.
 */
void BCAnimationSampler::add_value_set(BCAnimationCurve &curve,
                                       BCFrameSampleMap &samples,
                                       BC_export_animation_type animation_type)
{
  int array_index = curve.get_array_index();
  const BC_animation_transform_type tm_type = curve.get_transform_type();

  BCFrameSampleMap::iterator it;
  for (it = samples.begin(); it != samples.end(); ++it) {
    const int frame_index = nearbyint(it->first);
    if (animation_type == BC_ANIMATION_EXPORT_SAMPLES || curve.is_keyframe(frame_index)) {

      const BCSample *sample = it->second;
      float val = 0;

      int subindex = curve.get_subindex();
      bool good;
      if (subindex == -1) {
        good = sample->get_value(tm_type, array_index, &val);
      }
      else {
        good = sample->get_value(tm_type, array_index, &val, subindex);
      }

      if (good) {
        curve.add_value(val, frame_index);
      }
    }
  }
  curve.remove_unused_keyframes();
  curve.calchandles();
}
#endif

void BCAnimationSampler::generate_transform(Object *ob,
                                            const BCCurveKey &key,
                                            BCAnimationCurveMap &curves)
{
  BCAnimationCurveMap::const_iterator it = curves.find(key);
  if (it == curves.end()) {
    curves[key] = new BCAnimationCurve(key, ob);
  }
}

void BCAnimationSampler::generate_transforms(Object *ob,
                                             const std::string prep,
                                             const BC_animation_type type,
                                             BCAnimationCurveMap &curves)
{
  generate_transform(ob, BCCurveKey(type, prep + "location", 0), curves);
  generate_transform(ob, BCCurveKey(type, prep + "location", 1), curves);
  generate_transform(ob, BCCurveKey(type, prep + "location", 2), curves);
  generate_transform(ob, BCCurveKey(type, prep + "rotation_euler", 0), curves);
  generate_transform(ob, BCCurveKey(type, prep + "rotation_euler", 1), curves);
  generate_transform(ob, BCCurveKey(type, prep + "rotation_euler", 2), curves);
  generate_transform(ob, BCCurveKey(type, prep + "scale", 0), curves);
  generate_transform(ob, BCCurveKey(type, prep + "scale", 1), curves);
  generate_transform(ob, BCCurveKey(type, prep + "scale", 2), curves);
}

void BCAnimationSampler::generate_transforms(Object *ob, Bone *bone, BCAnimationCurveMap &curves)
{
  std::string prep = "pose.bones[\"" + std::string(bone->name) + "\"].";
  generate_transforms(ob, prep, BC_ANIMATION_TYPE_BONE, curves);

  for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
    generate_transforms(ob, child, curves);
  }
}

void BCAnimationSampler::initialize_keyframes(BCFrameSet &frameset, Object *ob)
{
  frameset.clear();
  add_keyframes_from(bc_getSceneObjectAction(ob), frameset);
  add_keyframes_from(bc_getSceneCameraAction(ob), frameset);
  add_keyframes_from(bc_getSceneLightAction(ob), frameset);

  for (int a = 0; a < ob->totcol; a++) {
    Material *ma = BKE_object_material_get(ob, a + 1);
    add_keyframes_from(bc_getSceneMaterialAction(ma), frameset);
  }
}

void BCAnimationSampler::initialize_curves(BCAnimationCurveMap &curves, Object *ob)
{
  BC_animation_type object_type = BC_ANIMATION_TYPE_OBJECT;

  bAction *action = bc_getSceneObjectAction(ob);
  if (action) {
    FCurve *fcu = (FCurve *)action->curves.first;

    for (; fcu; fcu = fcu->next) {
      object_type = BC_ANIMATION_TYPE_OBJECT;
      if (ob->type == OB_ARMATURE) {
        char boneName[MAXBONENAME];
        if (BLI_str_quoted_substr(fcu->rna_path, "pose.bones[", boneName, sizeof(boneName))) {
          object_type = BC_ANIMATION_TYPE_BONE;
        }
      }

      /* Adding action curves on object */
      BCCurveKey key(object_type, fcu->rna_path, fcu->array_index);
      curves[key] = new BCAnimationCurve(key, ob, fcu);
    }
  }

  /* Add missing curves */
  object_type = BC_ANIMATION_TYPE_OBJECT;
  generate_transforms(ob, EMPTY_STRING, object_type, curves);
  if (ob->type == OB_ARMATURE) {
    bArmature *arm = (bArmature *)ob->data;
    for (Bone *root_bone = (Bone *)arm->bonebase.first; root_bone; root_bone = root_bone->next) {
      generate_transforms(ob, root_bone, curves);
    }
  }

  /* Add curves on Object->data actions */
  action = nullptr;
  if (ob->type == OB_CAMERA) {
    action = bc_getSceneCameraAction(ob);
    object_type = BC_ANIMATION_TYPE_CAMERA;
  }
  else if (ob->type == OB_LAMP) {
    action = bc_getSceneLightAction(ob);
    object_type = BC_ANIMATION_TYPE_LIGHT;
  }

  if (action) {
    /* Add light action or Camera action */
    FCurve *fcu = (FCurve *)action->curves.first;
    for (; fcu; fcu = fcu->next) {
      BCCurveKey key(object_type, fcu->rna_path, fcu->array_index);
      curves[key] = new BCAnimationCurve(key, ob, fcu);
    }
  }

  /* Add curves on Object->material actions. */
  object_type = BC_ANIMATION_TYPE_MATERIAL;
  for (int a = 0; a < ob->totcol; a++) {
    /* Export Material parameter animations. */
    Material *ma = BKE_object_material_get(ob, a + 1);
    if (ma) {
      action = bc_getSceneMaterialAction(ma);
      if (action) {
        /* isMatAnim = true; */
        FCurve *fcu = (FCurve *)action->curves.first;
        for (; fcu; fcu = fcu->next) {
          BCCurveKey key(object_type, fcu->rna_path, fcu->array_index, a);
          curves[key] = new BCAnimationCurve(key, ob, fcu);
        }
      }
    }
  }
}

/* ==================================================================== */

BCSample &BCSampleFrame::add(Object *ob)
{
  BCSample *sample = new BCSample(ob);
  sampleMap[ob] = sample;
  return *sample;
}

const BCSample *BCSampleFrame::get_sample(Object *ob) const
{
  BCSampleMap::const_iterator it = sampleMap.find(ob);
  if (it == sampleMap.end()) {
    return nullptr;
  }
  return it->second;
}

const BCMatrix *BCSampleFrame::get_sample_matrix(Object *ob) const
{
  BCSampleMap::const_iterator it = sampleMap.find(ob);
  if (it == sampleMap.end()) {
    return nullptr;
  }
  BCSample *sample = it->second;
  return &sample->get_matrix();
}

const BCMatrix *BCSampleFrame::get_sample_matrix(Object *ob, Bone *bone) const
{
  BCSampleMap::const_iterator it = sampleMap.find(ob);
  if (it == sampleMap.end()) {
    return nullptr;
  }

  BCSample *sample = it->second;
  const BCMatrix *bc_bone = sample->get_matrix(bone);
  return bc_bone;
}

bool BCSampleFrame::has_sample_for(Object *ob) const
{
  return sampleMap.find(ob) != sampleMap.end();
}

bool BCSampleFrame::has_sample_for(Object *ob, Bone *bone) const
{
  const BCMatrix *bc_bone = get_sample_matrix(ob, bone);
  return bc_bone;
}

/* ==================================================================== */

BCSample &BCSampleFrameContainer::add(Object *ob, int frame_index)
{
  BCSampleFrame &frame = sample_frames[frame_index];
  return frame.add(ob);
}

/* ====================================================== */
/* Below are the getters which we need to export the data */
/* ====================================================== */

BCSampleFrame *BCSampleFrameContainer::get_frame(int frame_index)
{
  BCSampleFrameMap::iterator it = sample_frames.find(frame_index);
  BCSampleFrame *frame = (it == sample_frames.end()) ? nullptr : &it->second;
  return frame;
}

int BCSampleFrameContainer::get_frames(std::vector<int> &frames) const
{
  frames.clear(); /* safety; */
  BCSampleFrameMap::const_iterator it;
  for (it = sample_frames.begin(); it != sample_frames.end(); ++it) {
    frames.push_back(it->first);
  }
  return frames.size();
}

int BCSampleFrameContainer::get_frames(Object *ob, BCFrames &frames) const
{
  frames.clear(); /* safety; */
  BCSampleFrameMap::const_iterator it;
  for (it = sample_frames.begin(); it != sample_frames.end(); ++it) {
    const BCSampleFrame &frame = it->second;
    if (frame.has_sample_for(ob)) {
      frames.push_back(it->first);
    }
  }
  return frames.size();
}

int BCSampleFrameContainer::get_frames(Object *ob, Bone *bone, BCFrames &frames) const
{
  frames.clear(); /* safety; */
  BCSampleFrameMap::const_iterator it;
  for (it = sample_frames.begin(); it != sample_frames.end(); ++it) {
    const BCSampleFrame &frame = it->second;
    if (frame.has_sample_for(ob, bone)) {
      frames.push_back(it->first);
    }
  }
  return frames.size();
}

int BCSampleFrameContainer::get_samples(Object *ob, BCFrameSampleMap &samples) const
{
  samples.clear(); /* safety; */
  BCSampleFrameMap::const_iterator it;
  for (it = sample_frames.begin(); it != sample_frames.end(); ++it) {
    const BCSampleFrame &frame = it->second;
    const BCSample *sample = frame.get_sample(ob);
    if (sample) {
      samples[it->first] = sample;
    }
  }
  return samples.size();
}

int BCSampleFrameContainer::get_matrices(Object *ob, BCMatrixSampleMap &samples) const
{
  samples.clear(); /* safety; */
  BCSampleFrameMap::const_iterator it;
  for (it = sample_frames.begin(); it != sample_frames.end(); ++it) {
    const BCSampleFrame &frame = it->second;
    const BCMatrix *matrix = frame.get_sample_matrix(ob);
    if (matrix) {
      samples[it->first] = matrix;
    }
  }
  return samples.size();
}

int BCSampleFrameContainer::get_matrices(Object *ob, Bone *bone, BCMatrixSampleMap &samples) const
{
  samples.clear(); /* safety; */
  BCSampleFrameMap::const_iterator it;
  for (it = sample_frames.begin(); it != sample_frames.end(); ++it) {
    const BCSampleFrame &frame = it->second;
    const BCMatrix *sample = frame.get_sample_matrix(ob, bone);
    if (sample) {
      samples[it->first] = sample;
    }
  }
  return samples.size();
}
