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

/** \file
 * \ingroup bke
 */

#include <algorithm>

#include "MEM_guardedalloc.h"

#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"

#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
#include "BLI_vector.hh"

#include "BKE_sound.h"

#include "SEQ_iterator.hh"
#include "SEQ_retiming.hh"
#include "SEQ_sequencer.hh"
#include "SEQ_time.hh"
#include "SEQ_transform.hh"

#include "sequencer.hh"
#include "strip_time.hh"

namespace blender::seq {

MutableSpan<SeqRetimingKey> retiming_keys_get(const Strip *strip)
{
  blender::MutableSpan<SeqRetimingKey> handles(strip->retiming_keys, strip->retiming_keys_num);
  return handles;
}

bool retiming_is_last_key(const Strip *strip, const SeqRetimingKey *key)
{
  return retiming_key_index_get(strip, key) == strip->retiming_keys_num - 1;
}

SeqRetimingKey *retiming_last_key_get(const Strip *strip)
{
  return strip->retiming_keys + strip->retiming_keys_num - 1;
}

int retiming_key_index_get(const Strip *strip, const SeqRetimingKey *key)
{
  return key - strip->retiming_keys;
}

static int content_frame_index_get(const Scene *scene,
                                   const Strip *strip,
                                   const int timeline_frame)
{
  const float scene_fps = float(scene->r.frs_sec) / float(scene->r.frs_sec_base);
  const int sound_offset = time_get_rounded_sound_offset(strip, scene_fps);
  return (timeline_frame - time_start_frame_get(strip) - sound_offset) *
         time_media_playback_rate_factor_get(strip, scene_fps);
}

SeqRetimingKey *retiming_key_get_by_timeline_frame(const Scene *scene,
                                                   const Strip *strip,
                                                   const int timeline_frame)
{
  for (auto &key : retiming_keys_get(strip)) {
    const int key_timeline_frame = retiming_key_timeline_frame_get(scene, strip, &key);
    if (key_timeline_frame == timeline_frame) {
      return &key;
    }
  }

  return nullptr;
}

SeqRetimingKey *retiming_find_segment_start_key(const Strip *strip, float frame_index)
{
  SeqRetimingKey *start_key = nullptr;
  for (auto &key : retiming_keys_get(strip)) {
    if (retiming_is_last_key(strip, &key)) {
      break;
    }
    if (key.strip_frame_index > frame_index) {
      break;
    }

    start_key = &key;
  }

  return start_key;
}

int retiming_keys_count(const Strip *strip)
{
  return strip->retiming_keys_num;
}

void retiming_data_ensure(Strip *strip)
{
  if (!retiming_is_allowed(strip)) {
    return;
  }

  if (retiming_is_active(strip)) {
    return;
  }

  strip->retiming_keys = MEM_calloc_arrayN<SeqRetimingKey>(2, __func__);
  SeqRetimingKey *key = strip->retiming_keys + 1;
  key->strip_frame_index = strip->len - 1;
  key->retiming_factor = 1.0f;
  strip->retiming_keys_num = 2;
}

void retiming_data_clear(Strip *strip)
{
  if (strip->retiming_keys != nullptr) {
    MEM_freeN(strip->retiming_keys);
    strip->retiming_keys = nullptr;
    strip->retiming_keys_num = 0;
  }
  strip->flag &= ~SEQ_SHOW_RETIMING;
}

static void retiming_key_overlap(Scene *scene, Strip *strip)
{
  ListBase *seqbase = active_seqbase_get(editing_get(scene));
  blender::VectorSet<Strip *> strips;
  blender::VectorSet<Strip *> dependant;
  dependant.add(strip);
  iterator_set_expand(scene, seqbase, dependant, query_strip_effect_chain);
  strips.add_multiple(dependant);
  dependant.remove(strip);
  transform_handle_overlap(scene, seqbase, strips, dependant, true);
}

void retiming_reset(Scene *scene, Strip *strip)
{
  if (!retiming_is_allowed(strip)) {
    return;
  }

  retiming_data_clear(strip);

  blender::Span<Strip *> effects = SEQ_lookup_effects_by_strip(scene->ed, strip);
  strip_time_update_effects_strip_range(scene, effects);
  time_update_meta_strip_range(scene, lookup_meta_by_strip(scene->ed, strip));

  retiming_key_overlap(scene, strip);
}

bool retiming_is_active(const Strip *strip)
{
  return strip->retiming_keys_num > 1;
}

bool retiming_data_is_editable(const Strip *strip)
{
  return strip->flag & SEQ_SHOW_RETIMING;
}

bool retiming_is_allowed(const Strip *strip)
{
  if (strip->len < 2) {
    return false;
  }

  return ELEM(strip->type,
              STRIP_TYPE_SOUND_RAM,
              STRIP_TYPE_IMAGE,
              STRIP_TYPE_META,
              STRIP_TYPE_SCENE,
              STRIP_TYPE_MOVIE,
              STRIP_TYPE_MOVIECLIP,
              STRIP_TYPE_MASK);
}

static double strip_retiming_segment_length_get(const SeqRetimingKey *start_key)
{
  const SeqRetimingKey *end_key = start_key + 1;
  return end_key->strip_frame_index - start_key->strip_frame_index;
}

static float strip_retiming_segment_step_get(const SeqRetimingKey *start_key)
{
  const SeqRetimingKey *end_key = start_key + 1;
  const double segment_length = strip_retiming_segment_length_get(start_key);
  const double segment_fac_diff = end_key->retiming_factor - start_key->retiming_factor;
  return segment_fac_diff / segment_length;
}

static void strip_retiming_segment_as_line_segment(const SeqRetimingKey *start_key,
                                                   double r_v1[2],
                                                   double r_v2[2])
{
  const SeqRetimingKey *end_key = start_key + 1;
  r_v1[0] = start_key->strip_frame_index;
  r_v1[1] = start_key->retiming_factor;
  r_v2[0] = end_key->strip_frame_index;
  r_v2[1] = end_key->retiming_factor;
}

static void strip_retiming_line_segments_tangent_circle(const SeqRetimingKey *start_key,
                                                        double r_center[2],
                                                        double *radius)
{
  blender::double2 s1_1, s1_2, s2_1, s2_2, p1_2;

  /* Get 2 segments. */
  strip_retiming_segment_as_line_segment(start_key - 1, s1_1, s1_2);
  strip_retiming_segment_as_line_segment(start_key + 1, s2_1, s2_2);
  /* Backup first segment end point - needed to calculate arc radius. */
  copy_v2_v2_db(p1_2, s1_2);
  /* Convert segments to vectors. */
  blender::double2 v1, v2;
  sub_v2_v2v2_db(v1, s1_1, s1_2);
  sub_v2_v2v2_db(v2, s2_1, s2_2);
  /* Rotate segments by 90 degrees around seg. 1 end and seg. 2 start point. */
  std::swap(v1[0], v1[1]);
  std::swap(v2[0], v2[1]);
  v1[0] *= -1;
  v2[0] *= -1;
  copy_v2_v2_db(s1_1, s1_2);
  s1_2 += v1;
  copy_v2_v2_db(s2_2, s2_1);
  s2_2 += v2;
  /* Get center and radius of arc segment between 2 linear segments. */
  double lambda, mu;
  isect_seg_seg_v2_lambda_mu_db(s1_1, s1_2, s2_1, s2_2, &lambda, &mu);
  r_center[0] = s1_1[0] + lambda * (s1_2[0] - s1_1[0]);
  r_center[1] = s1_1[1] + lambda * (s1_2[1] - s1_1[1]);
  *radius = len_v2v2_db(p1_2, r_center);
}

bool retiming_key_is_transition_type(const SeqRetimingKey *key)
{
  return (key->flag & SEQ_SPEED_TRANSITION_IN) != 0 || (key->flag & SEQ_SPEED_TRANSITION_OUT) != 0;
}

bool retiming_key_is_transition_start(const SeqRetimingKey *key)
{
  return (key->flag & SEQ_SPEED_TRANSITION_IN) != 0;
}

SeqRetimingKey *retiming_transition_start_get(SeqRetimingKey *key)
{
  if (key->flag & SEQ_SPEED_TRANSITION_OUT) {
    return key - 1;
  }
  if (key->flag & SEQ_SPEED_TRANSITION_IN) {
    return key;
  }
  return nullptr;
}

bool retiming_key_is_freeze_frame(const SeqRetimingKey *key)
{
  return (key->flag & SEQ_FREEZE_FRAME_IN) != 0 || (key->flag & SEQ_FREEZE_FRAME_OUT) != 0;
}

/* Check colinearity of 2 segments allowing for some imprecision.
 * `isect_seg_seg_v2_lambda_mu_db()` return value does not work well in this case. */

static bool strip_retiming_transition_is_linear(const Strip *strip, const SeqRetimingKey *key)
{
  const float prev_speed = retiming_key_speed_get(strip, key - 1);
  const float next_speed = retiming_key_speed_get(strip, key + 2);

  return abs(prev_speed - next_speed) < 0.01f;
}

static float strip_retiming_evaluate_arc_segment(const SeqRetimingKey *key,
                                                 const float frame_index)
{
  double c[2], r;
  strip_retiming_line_segments_tangent_circle(key, c, &r);
  const int side = c[1] > key->retiming_factor ? -1 : 1;
  const float y = c[1] + side * sqrt(pow(r, 2) - pow((frame_index - c[0]), 2));
  return y;
}

float strip_retiming_evaluate(const Strip *strip, const float frame_index)
{
  const SeqRetimingKey *start_key = retiming_find_segment_start_key(strip, frame_index);

  const int start_key_index = start_key - strip->retiming_keys;
  BLI_assert(start_key_index < strip->retiming_keys_num);

  const float segment_frame_index = frame_index - start_key->strip_frame_index;

  if (!retiming_key_is_transition_start(start_key)) {
    const float segment_step = strip_retiming_segment_step_get(start_key);
    return std::min(1.0f, start_key->retiming_factor + float(segment_step * segment_frame_index));
  }

  if (strip_retiming_transition_is_linear(strip, start_key)) {
    const float segment_step = strip_retiming_segment_step_get(start_key - 1);
    return std::min(1.0f, start_key->retiming_factor + float(segment_step * segment_frame_index));
  }

  /* Sanity check for transition type. */
  BLI_assert(start_key_index > 0);
  BLI_assert(start_key_index < strip->retiming_keys_num - 1);
  UNUSED_VARS_NDEBUG(start_key_index);

  return std::min(1.0f, strip_retiming_evaluate_arc_segment(start_key, frame_index));
}

static SeqRetimingKey *strip_retiming_add_key(Strip *strip, float frame_index)
{
  if (!retiming_is_allowed(strip)) {
    return nullptr;
  }
  /* Clamp timeline frame to strip content range. */
  if (frame_index <= 0) {
    return &strip->retiming_keys[0];
  }
  if (frame_index >= retiming_last_key_get(strip)->strip_frame_index) {
    return retiming_last_key_get(strip); /* This is expected for strips with no offsets. */
  }

  SeqRetimingKey *start_key = retiming_find_segment_start_key(strip, frame_index);

  if (start_key->strip_frame_index == frame_index) {
    return start_key; /* Retiming key already exists. */
  }

  if ((start_key->flag & SEQ_SPEED_TRANSITION_IN) != 0 ||
      (start_key->flag & SEQ_FREEZE_FRAME_IN) != 0)
  {
    return nullptr;
  }

  float value = strip_retiming_evaluate(strip, frame_index);

  SeqRetimingKey *keys = strip->retiming_keys;
  const int keys_count = retiming_keys_count(strip);
  const int new_key_index = start_key - keys + 1;
  BLI_assert(new_key_index >= 0);
  BLI_assert(new_key_index < keys_count);

  SeqRetimingKey *new_keys = MEM_calloc_arrayN<SeqRetimingKey>(keys_count + 1, __func__);
  if (new_key_index > 0) {
    memcpy(new_keys, keys, new_key_index * sizeof(SeqRetimingKey));
  }
  if (new_key_index < keys_count) {
    memcpy(new_keys + new_key_index + 1,
           keys + new_key_index,
           (keys_count - new_key_index) * sizeof(SeqRetimingKey));
  }
  MEM_freeN(keys);
  strip->retiming_keys = new_keys;
  strip->retiming_keys_num++;

  SeqRetimingKey *added_key = (new_keys + new_key_index);
  added_key->strip_frame_index = frame_index;
  added_key->retiming_factor = value;

  return added_key;
}

SeqRetimingKey *retiming_add_key(const Scene *scene, Strip *strip, const int timeline_frame)
{
  return strip_retiming_add_key(strip, content_frame_index_get(scene, strip, timeline_frame));
}

void retiming_transition_key_frame_set(const Scene *scene,
                                       const Strip *strip,
                                       SeqRetimingKey *key,
                                       const int timeline_frame)
{
  SeqRetimingKey *key_start = retiming_transition_start_get(key);
  SeqRetimingKey *key_end = key_start + 1;
  const float start_frame_index = key_start->strip_frame_index;
  const float midpoint = key_start->original_strip_frame_index;
  const float new_frame_index = content_frame_index_get(scene, strip, timeline_frame);
  float new_midpoint_offset = new_frame_index - midpoint;
  const float prev_segment_step = strip_retiming_segment_step_get(key_start - 1);
  const float next_segment_step = strip_retiming_segment_step_get(key_end);

  /* Prevent keys crossing eachother. */
  SeqRetimingKey *prev_segment_end = key_start - 1, *next_segment_start = key_end + 1;
  const float offset_max_left = midpoint - prev_segment_end->strip_frame_index - 1;
  const float offset_max_right = next_segment_start->strip_frame_index - midpoint - 1;
  new_midpoint_offset = fabs(new_midpoint_offset);
  new_midpoint_offset = min_fff(new_midpoint_offset, offset_max_left, offset_max_right);
  new_midpoint_offset = max_ff(new_midpoint_offset, 1);

  key_start->strip_frame_index = midpoint - new_midpoint_offset;
  key_end->strip_frame_index = midpoint + new_midpoint_offset;

  const float offset = key_start->strip_frame_index - start_frame_index;
  key_start->retiming_factor += offset * prev_segment_step;
  key_end->retiming_factor -= offset * next_segment_step;
}

static void strip_retiming_cleanup_freeze_frame(SeqRetimingKey *key)
{
  if ((key->flag & SEQ_FREEZE_FRAME_IN) != 0) {
    SeqRetimingKey *next_key = key + 1;
    key->flag &= ~SEQ_FREEZE_FRAME_IN;
    next_key->flag &= ~SEQ_FREEZE_FRAME_OUT;
  }
  if ((key->flag & SEQ_FREEZE_FRAME_OUT) != 0) {
    SeqRetimingKey *previous_key = key - 1;
    key->flag &= ~SEQ_FREEZE_FRAME_OUT;
    previous_key->flag &= ~SEQ_FREEZE_FRAME_IN;
  }
}

void retiming_remove_multiple_keys(Strip *strip, blender::Vector<SeqRetimingKey *> &keys_to_remove)
{
  /* Transitions need special treatment, so separate these from `keys_to_remove`. */
  blender::Vector<SeqRetimingKey *> transitions;

  /* Cleanup freeze frames and extract transition keys. */
  for (SeqRetimingKey *key : keys_to_remove) {
    if (retiming_key_is_freeze_frame(key)) {
      strip_retiming_cleanup_freeze_frame(key);
    }
    if ((key->flag & SEQ_SPEED_TRANSITION_IN) != 0) {
      transitions.append_non_duplicates(key);
      transitions.append_non_duplicates(key + 1);
    }
    if ((key->flag & SEQ_SPEED_TRANSITION_OUT) != 0) {
      transitions.append_non_duplicates(key);
      transitions.append_non_duplicates(key - 1);
    }
  }

  /* Sanitize keys to be removed. */
  keys_to_remove.remove_if([&](const SeqRetimingKey *key) {
    return key->strip_frame_index == 0 || retiming_is_last_key(strip, key) ||
           retiming_key_is_transition_type(key);
  });

  const size_t keys_count = retiming_keys_count(strip);
  size_t new_keys_count = keys_count - keys_to_remove.size() - transitions.size() / 2;
  SeqRetimingKey *new_keys = MEM_calloc_arrayN<SeqRetimingKey>(new_keys_count, __func__);
  int keys_copied = 0;

  /* Copy keys to new array. */
  for (SeqRetimingKey &key : retiming_keys_get(strip)) {
    /* Create key that was used to make transition in new array. */
    if (transitions.contains(&key) && retiming_key_is_transition_start(&key)) {
      SeqRetimingKey *new_key = new_keys + keys_copied;
      new_key->strip_frame_index = key.original_strip_frame_index;
      new_key->retiming_factor = key.original_retiming_factor;
      keys_copied++;
      continue;
    }
    if (keys_to_remove.contains(&key) || transitions.contains(&key)) {
      continue;
    }
    memcpy(new_keys + keys_copied, &key, sizeof(SeqRetimingKey));
    keys_copied++;
  }

  MEM_freeN(strip->retiming_keys);
  strip->retiming_keys = new_keys;
  strip->retiming_keys_num = new_keys_count;
}

static void strip_retiming_remove_key_ex(Strip *strip, SeqRetimingKey *key)
{
  if (key->strip_frame_index == 0 || retiming_is_last_key(strip, key)) {
    return; /* First and last key can not be removed. */
  }

  if (retiming_key_is_freeze_frame(key)) {
    strip_retiming_cleanup_freeze_frame(key);
  }

  size_t keys_count = retiming_keys_count(strip);
  SeqRetimingKey *keys = MEM_calloc_arrayN<SeqRetimingKey>(keys_count - 1, __func__);

  const int key_index = key - strip->retiming_keys;
  memcpy(keys, strip->retiming_keys, (key_index) * sizeof(SeqRetimingKey));
  memcpy(keys + key_index,
         strip->retiming_keys + key_index + 1,
         (keys_count - key_index - 1) * sizeof(SeqRetimingKey));
  MEM_freeN(strip->retiming_keys);
  strip->retiming_keys = keys;
  strip->retiming_keys_num--;
}

/* This function removes transition segment and creates retiming key where it originally was. */
static SeqRetimingKey *strip_retiming_remove_transition(Strip *strip, SeqRetimingKey *key)
{
  SeqRetimingKey *transition_start = key;
  if ((key->flag & SEQ_SPEED_TRANSITION_OUT) != 0) {
    transition_start = key - 1;
  }

  const float orig_frame_index = transition_start->original_strip_frame_index;
  const float orig_retiming_factor = transition_start->original_retiming_factor;

  /* Remove both keys defining transition. */
  int key_index = retiming_key_index_get(strip, transition_start);
  strip_retiming_remove_key_ex(strip, transition_start);
  strip_retiming_remove_key_ex(strip, strip->retiming_keys + key_index);

  /* Create original linear key. */
  SeqRetimingKey *orig_key = strip_retiming_add_key(strip, orig_frame_index);
  orig_key->retiming_factor = orig_retiming_factor;
  return orig_key;
}

void retiming_remove_key(Strip *strip, SeqRetimingKey *key)
{

  if (retiming_key_is_transition_type(key)) {
    strip_retiming_remove_transition(strip, key);
    return;
  }

  strip_retiming_remove_key_ex(strip, key);
}

static float strip_retiming_clamp_create_offset(const Scene *scene,
                                                const Strip *strip,
                                                SeqRetimingKey *key,
                                                int offset)
{
  SeqRetimingKey *prev_key = key - 1;
  SeqRetimingKey *next_key = key + 1;
  const int prev_dist = retiming_key_timeline_frame_get(scene, strip, prev_key) -
                        retiming_key_timeline_frame_get(scene, strip, key);
  const int next_dist = retiming_key_timeline_frame_get(scene, strip, next_key) -
                        retiming_key_timeline_frame_get(scene, strip, key);
  return std::clamp(offset, prev_dist + 1, next_dist - 1);
}

SeqRetimingKey *retiming_add_freeze_frame(const Scene *scene,
                                          Strip *strip,
                                          SeqRetimingKey *key,
                                          const int offset)
{
  /* First offset old key, then add new key to original place with same fac
   * This is not great way to do things, but it's done in order to be able to freeze last key. */
  if (retiming_key_is_transition_type(key)) {
    return nullptr;
  }

  const float scene_fps = float(scene->r.frs_sec) / float(scene->r.frs_sec_base);
  const int clamped_offset = strip_retiming_clamp_create_offset(
      scene, strip, key, offset * time_media_playback_rate_factor_get(strip, scene_fps));

  const int orig_timeline_frame = retiming_key_timeline_frame_get(scene, strip, key);
  const float orig_retiming_factor = key->retiming_factor;
  key->strip_frame_index += clamped_offset;
  key->flag |= SEQ_FREEZE_FRAME_OUT;

  SeqRetimingKey *new_key = retiming_add_key(scene, strip, orig_timeline_frame);

  if (new_key == nullptr) {
    key->strip_frame_index -= clamped_offset;
    key->flag &= ~SEQ_FREEZE_FRAME_OUT;
    return nullptr;
  }

  new_key->retiming_factor = orig_retiming_factor;
  new_key->flag |= SEQ_FREEZE_FRAME_IN;

  /* Tag previous key as freeze frame key. This is only a convenient way to prevent creating
   * speed transitions. When freeze frame is deleted, this flag should be cleared. */
  return new_key + 1;
}

SeqRetimingKey *retiming_add_transition(const Scene *scene,
                                        Strip *strip,
                                        SeqRetimingKey *key,
                                        float offset)
{
  BLI_assert(!retiming_is_last_key(strip, key));
  BLI_assert(key->strip_frame_index != 0);

  SeqRetimingKey *prev_key = key - 1;
  if ((key->flag & SEQ_SPEED_TRANSITION_IN) != 0 ||
      (prev_key->flag & SEQ_SPEED_TRANSITION_IN) != 0)
  {
    return nullptr;
  }

  if ((key->flag & SEQ_FREEZE_FRAME_IN) != 0 || (prev_key->flag & SEQ_FREEZE_FRAME_IN) != 0) {
    return nullptr;
  }

  float clamped_offset = strip_retiming_clamp_create_offset(scene, strip, key, offset);

  const int orig_key_index = retiming_key_index_get(strip, key);
  const float orig_frame_index = key->strip_frame_index;
  const float orig_retiming_factor = key->retiming_factor;

  SeqRetimingKey *transition_out = strip_retiming_add_key(strip,
                                                          orig_frame_index + clamped_offset);
  transition_out->flag |= SEQ_SPEED_TRANSITION_OUT;

  SeqRetimingKey *transition_in = strip_retiming_add_key(strip, orig_frame_index - clamped_offset);
  transition_in->flag |= SEQ_SPEED_TRANSITION_IN;
  transition_in->original_strip_frame_index = orig_frame_index;
  transition_in->original_retiming_factor = orig_retiming_factor;

  strip_retiming_remove_key_ex(strip, strip->retiming_keys + orig_key_index + 1);
  return strip->retiming_keys + orig_key_index + 1;
}

static float strip_retiming_clamp_transition_offset(const Scene *scene,
                                                    const Strip *strip,
                                                    SeqRetimingKey *start_key,
                                                    float offset)
{
  SeqRetimingKey *end_key = start_key + 1;
  SeqRetimingKey *prev_key = start_key - 1;
  SeqRetimingKey *next_key = start_key + 2;
  const float prev_max_offset = prev_key->strip_frame_index - start_key->strip_frame_index;
  const float next_max_offset = next_key->strip_frame_index - end_key->strip_frame_index;
  const float scene_fps = float(scene->r.frs_sec) / float(scene->r.frs_sec_base);
  const float min_step = time_media_playback_rate_factor_get(strip, scene_fps);

  return std::clamp(offset, prev_max_offset + min_step, next_max_offset - min_step);
}

static void strip_retiming_transition_offset(const Scene *scene,
                                             Strip *strip,
                                             SeqRetimingKey *key,
                                             const float offset)
{
  float clamped_offset = strip_retiming_clamp_transition_offset(scene, strip, key, offset);
  const float scene_fps = float(scene->r.frs_sec) / float(scene->r.frs_sec_base);
  const float duration = (key->original_strip_frame_index - key->strip_frame_index) /
                         time_media_playback_rate_factor_get(strip, scene_fps);
  const bool was_selected = retiming_selection_contains(editing_get(scene), key);

  SeqRetimingKey *original_key = strip_retiming_remove_transition(strip, key);
  original_key->strip_frame_index += clamped_offset;

  SeqRetimingKey *transition_out = retiming_add_transition(scene, strip, original_key, duration);

  if (was_selected) {
    retiming_selection_append(transition_out);
    retiming_selection_append(transition_out - 1);
  }
}

static int strip_retiming_clamp_timeline_frame(const Scene *scene,
                                               Strip *strip,
                                               SeqRetimingKey *key,
                                               const int timeline_frame)
{
  if ((key->flag & SEQ_SPEED_TRANSITION_IN) != 0) {
    return timeline_frame;
  }

  int prev_key_timeline_frame = -MAXFRAME;
  int next_key_timeline_frame = MAXFRAME;

  if (key->strip_frame_index > 0) {
    SeqRetimingKey *prev_key = key - 1;
    prev_key_timeline_frame = retiming_key_timeline_frame_get(scene, strip, prev_key);
  }

  if (!retiming_is_last_key(strip, key)) {
    SeqRetimingKey *next_key = key + 1;
    next_key_timeline_frame = retiming_key_timeline_frame_get(scene, strip, next_key);
  }

  return std::clamp(timeline_frame, prev_key_timeline_frame + 1, next_key_timeline_frame - 1);
}

/* Remove and re-create transition. This way transition won't change length.
 * Alternative solution is to find where in arc segment the `y` value is closest to key
 * retiming factor, then trim transition to that point. This would change transition length. */

static void strip_retiming_fix_transition(const Scene *scene, Strip *strip, SeqRetimingKey *key)
{
  const int keys_num = strip->retiming_keys_num;
  const float scene_fps = float(scene->r.frs_sec) / float(scene->r.frs_sec_base);
  const float transition_duration = (key->original_strip_frame_index - key->strip_frame_index) /
                                    time_media_playback_rate_factor_get(strip, scene_fps);
  SeqRetimingKey *orig_key = strip_retiming_remove_transition(strip, key);
  retiming_add_transition(scene, strip, orig_key, transition_duration);
  BLI_assert(keys_num == strip->retiming_keys_num);
  UNUSED_VARS_NDEBUG(keys_num);
}

static void strip_retiming_fix_transitions(const Scene *scene, Strip *strip, SeqRetimingKey *key)
{
  /* Store value, since handles array will be reallocated. */
  const int key_index = retiming_key_index_get(strip, key);

  if (key_index > 1) {
    SeqRetimingKey *prev_key = key - 2;
    if (retiming_key_is_transition_start(prev_key)) {
      strip_retiming_fix_transition(scene, strip, prev_key);
    }
  }

  if (!retiming_is_last_key(strip, key)) {
    SeqRetimingKey *next_key = &retiming_keys_get(strip)[key_index + 1];
    if (retiming_key_is_transition_start(next_key)) {
      strip_retiming_fix_transition(scene, strip, next_key);
    }
  }
}

static void strip_retiming_key_offset(const Scene *scene,
                                      Strip *strip,
                                      SeqRetimingKey *key,
                                      const float offset)
{
  if ((key->flag & SEQ_SPEED_TRANSITION_IN) != 0) {
    strip_retiming_transition_offset(scene, strip, key, offset);
  }
  else {
    key->strip_frame_index += offset;
    strip_retiming_fix_transitions(scene, strip, key);
  }
}

int retiming_key_timeline_frame_get(const Scene *scene,
                                    const Strip *strip,
                                    const SeqRetimingKey *key)
{
  const float scene_fps = float(scene->r.frs_sec) / float(scene->r.frs_sec_base);
  const int sound_offset = time_get_rounded_sound_offset(strip, scene_fps);
  return round_fl_to_int(time_start_frame_get(strip) + sound_offset +
                         key->strip_frame_index /
                             time_media_playback_rate_factor_get(strip, scene_fps));
}

void retiming_key_timeline_frame_set(const Scene *scene,
                                     Strip *strip,
                                     SeqRetimingKey *key,
                                     const int timeline_frame)
{
  if ((key->flag & SEQ_SPEED_TRANSITION_OUT) != 0) {
    return;
  }

  const int orig_timeline_frame = retiming_key_timeline_frame_get(scene, strip, key);
  const int clamped_timeline_frame = strip_retiming_clamp_timeline_frame(
      scene, strip, key, timeline_frame);
  const float scene_fps = float(scene->r.frs_sec) / float(scene->r.frs_sec_base);
  const float offset = (clamped_timeline_frame - orig_timeline_frame) *
                       time_media_playback_rate_factor_get(strip, scene_fps);

  const int key_count = retiming_keys_get(strip).size();
  const int key_index = retiming_key_index_get(strip, key);

  if (orig_timeline_frame == time_right_handle_frame_get(scene, strip)) {
    for (int i = key_index; i < key_count; i++) {
      SeqRetimingKey *key_iter = &retiming_keys_get(strip)[i];
      strip_retiming_key_offset(scene, strip, key_iter, offset);
    }
  }
  else if (orig_timeline_frame == time_left_handle_frame_get(scene, strip) ||
           key->strip_frame_index == 0)
  {
    strip->start += clamped_timeline_frame - orig_timeline_frame;
    for (int i = key_index + 1; i < key_count; i++) {
      SeqRetimingKey *key_iter = &retiming_keys_get(strip)[i];
      strip_retiming_key_offset(scene, strip, key_iter, -offset);
    }
  }
  else {
    strip_retiming_key_offset(scene, strip, key, offset);
  }

  blender::Span<Strip *> effects = SEQ_lookup_effects_by_strip(scene->ed, strip);
  strip_time_update_effects_strip_range(scene, effects);
  time_update_meta_strip_range(scene, lookup_meta_by_strip(scene->ed, strip));
}

float retiming_key_speed_get(const Strip *strip, const SeqRetimingKey *key)
{
  if (key->strip_frame_index == 0) {
    return 1.0f;
  }

  const SeqRetimingKey *key_prev = key - 1;
  const int frame_index_max = strip->len - 1;
  const float frame_index_start = round_fl_to_int(key_prev->retiming_factor * frame_index_max);
  const float frame_index_end = round_fl_to_int(key->retiming_factor * frame_index_max);
  const float segment_content_frame_count = frame_index_end - frame_index_start;
  const float segment_length = key->strip_frame_index - key_prev->strip_frame_index;
  const float speed = segment_content_frame_count / segment_length;
  return speed;
}

void retiming_key_speed_set(
    const Scene *scene, Strip *strip, SeqRetimingKey *key, const float speed, bool keep_retiming)
{
  if (key->strip_frame_index == 0) {
    return;
  }

  const SeqRetimingKey *key_prev = key - 1;

  const int frame_index_max = strip->len - 1;
  const float frame_index_prev = round_fl_to_int(key_prev->retiming_factor * frame_index_max);
  const float frame_index = round_fl_to_int(key->retiming_factor * frame_index_max);

  const float scene_fps = float(scene->r.frs_sec) / float(scene->r.frs_sec_base);
  const float segment_timeline_duration = (frame_index - frame_index_prev) /
                                          time_media_playback_rate_factor_get(strip, scene_fps);
  const float new_timeline_duration = segment_timeline_duration / speed;

  const float orig_timeline_frame = retiming_key_timeline_frame_get(scene, strip, key);
  const float new_timeline_frame = std::round(
      retiming_key_timeline_frame_get(scene, strip, key_prev) + new_timeline_duration);

  retiming_key_timeline_frame_set(scene, strip, key, new_timeline_frame);

  if (keep_retiming) {
    const int key_index = retiming_key_index_get(strip, key);
    const int offset = new_timeline_frame - orig_timeline_frame;
    for (int i = key_index + 1; i < retiming_keys_count(strip); i++) {
      SeqRetimingKey *key_iter = &retiming_keys_get(strip)[i];
      strip_retiming_key_offset(scene, strip, key_iter, offset);
    }
  }
}

enum eRangeType {
  LINEAR = 0,
  TRANSITION = 1,
};

enum eIntersectType {
  FULL,
  PARTIAL_START,
  PARTIAL_END,
  INSIDE,
  NONE,
};

class RetimingRange {
 public:
  int start, end;
  float speed;
  blender::Vector<float> speed_table;

  eRangeType type;
  RetimingRange(const Strip *strip, int start_frame, int end_frame, float speed, eRangeType type)
      : start(start_frame), end(end_frame), speed(speed), type(type)
  {
    if (type == TRANSITION) {
      this->speed = 1.0f;
      claculate_speed_table_from_seq(strip);
    }
  }

  RetimingRange(int start_frame, int end_frame, float speed, eRangeType type)
      : start(start_frame), end(end_frame), speed(speed), type(type)
  {
  }

  RetimingRange duplicate()
  {
    RetimingRange new_range = RetimingRange(start, end, speed, type);
    for (int i = 0; i < speed_table.size(); i++) {
      new_range.speed_table.append(speed_table[i]);
    }
    return new_range;
  }

  /* Create new range representing overlap of 2 ranges.
   * Returns overlapping range. */
  RetimingRange operator*(const RetimingRange &rhs_range)
  {
    RetimingRange new_range = RetimingRange(0, 0, 0, LINEAR);

    /* Offsets to merge speed tables. */
    int range_offset = 0, rhs_range_offset = 0;
    if (intersect_type(rhs_range) == FULL) {
      new_range.start = start;
      new_range.end = end;
      rhs_range_offset = start - rhs_range.start;
    }
    else if (intersect_type(rhs_range) == PARTIAL_START) {
      new_range.start = start;
      new_range.end = rhs_range.end;
      rhs_range_offset = start - rhs_range.start;
    }
    else if (intersect_type(rhs_range) == PARTIAL_END) {
      new_range.start = rhs_range.start;
      new_range.end = end;
      range_offset = rhs_range.start - start;
    }
    else if (intersect_type(rhs_range) == INSIDE) {
      new_range.start = rhs_range.start;
      new_range.end = rhs_range.end;
      range_offset = rhs_range.start - start;
    }

    if (type != TRANSITION && rhs_range.type != TRANSITION) {
      new_range.speed = speed * rhs_range.speed;
      return new_range;
    }

    /* One of ranges is transition type, so speed tables has to be copied. */
    new_range.type = TRANSITION;
    new_range.speed = 1.0f;
    const int new_range_len = new_range.end - new_range.start;

    if (type == TRANSITION && rhs_range.type == TRANSITION) {
      for (int i = 0; i < new_range_len; i++) {
        const float range_speed = speed_table[i + range_offset];
        const float rhs_range_speed = rhs_range.speed_table[i + rhs_range_offset];
        new_range.speed_table.append(range_speed * rhs_range_speed);
      }
    }
    else if (type == TRANSITION) {
      for (int i = 0; i < new_range_len; i++) {
        const float range_speed = speed_table[i + range_offset];
        new_range.speed_table.append(range_speed * rhs_range.speed);
      }
    }
    else if (rhs_range.type == TRANSITION) {
      for (int i = 0; i < new_range_len; i++) {
        const float rhs_range_speed = rhs_range.speed_table[i + rhs_range_offset];
        new_range.speed_table.append(speed * rhs_range_speed);
      }
    }

    return new_range;
  }

  void claculate_speed_table_from_seq(const Strip *strip)
  {
    for (int timeline_frame = start; timeline_frame <= end; timeline_frame++) {
      /* We need number actual number of frames here. */
      const double normal_step = 1 / double(strip->len - 1);

      const int frame_index = timeline_frame - time_start_frame_get(strip);
      /* Who needs calculus, when you can have slow code? */
      const double val_prev = strip_retiming_evaluate(strip, frame_index - 1);
      const double val = strip_retiming_evaluate(strip, frame_index);
      const double speed_at_frame = (val - val_prev) / normal_step;
      speed_table.append(speed_at_frame);
    }
  }

  eIntersectType intersect_type(const RetimingRange &other) const
  {
    if (other.start <= start && other.end >= end) {
      return FULL;
    }
    if (other.start > start && other.start < end && other.end > start && other.end < end) {
      return INSIDE;
    }
    if (other.start > start && other.start < end) {
      return PARTIAL_END;
    }
    if (other.end > start && other.end < end) {
      return PARTIAL_START;
    }
    return NONE;
  }
};

class RetimingRangeData {
 public:
  blender::Vector<RetimingRange> ranges;
  RetimingRangeData(const Strip *strip)
  {
    for (const SeqRetimingKey &key : retiming_keys_get(strip)) {
      if (key.strip_frame_index == 0) {
        continue;
      }
      const SeqRetimingKey *key_prev = &key - 1;
      float speed = retiming_key_speed_get(strip, &key);
      int frame_start = time_start_frame_get(strip) + key_prev->strip_frame_index;
      int frame_end = time_start_frame_get(strip) + key.strip_frame_index;

      eRangeType type = retiming_key_is_transition_start(key_prev) ? TRANSITION : LINEAR;
      RetimingRange range = RetimingRange(strip, frame_start, frame_end, speed, type);
      ranges.append(range);
    }
  }

  RetimingRangeData &operator*=(const RetimingRangeData &rhs)
  {
    if (ranges.is_empty()) {
      for (const RetimingRange &rhs_range : rhs.ranges) {
        RetimingRange range = RetimingRange(
            rhs_range.start, rhs_range.end, rhs_range.speed, rhs_range.type);
        ranges.append(range);
      }
      return *this;
    }

    for (int i = 0; i < ranges.size(); i++) {
      RetimingRange &range = ranges[i];
      for (const RetimingRange &rhs_range : rhs.ranges) {
        if (range.intersect_type(rhs_range) == NONE) {
          continue;
        }
        if (range.intersect_type(rhs_range) == FULL) {
          RetimingRange isect = range * rhs_range;
          ranges.remove(i);
          ranges.insert(i, isect);
        }
        if (range.intersect_type(rhs_range) == PARTIAL_START) {
          ranges.insert(i, range * rhs_range);
          ranges.insert(i, range * rhs_range);
          range.start = rhs_range.end + 1;
        }
        else if (range.intersect_type(rhs_range) == PARTIAL_END) {
          ranges.insert(i, range * rhs_range);
          range.end = rhs_range.start;
        }
        else if (range.intersect_type(rhs_range) == INSIDE) {
          RetimingRange left_range = range.duplicate();
          left_range.end = rhs_range.start;
          range.start = rhs_range.end + 1;

          ranges.insert(i, left_range);
          ranges.insert(i, range * rhs_range);
        }
      }
    }
    return *this;
  }
};

static RetimingRangeData strip_retiming_range_data_get(const Scene *scene, const Strip *strip)
{
  RetimingRangeData strip_retiming_data = RetimingRangeData(strip);

  const Strip *meta_parent = lookup_meta_by_strip(scene->ed, strip);
  if (meta_parent == nullptr) {
    return strip_retiming_data;
  }

  RetimingRangeData meta_retiming_data = RetimingRangeData(meta_parent);
  strip_retiming_data *= meta_retiming_data;
  return strip_retiming_data;
}

void retiming_sound_animation_data_set(const Scene *scene, const Strip *strip)
{
  /* Content cut off by `anim_startofs` is as if it does not exist for sequencer. But Audaspace
   * seeking relies on having animation buffer initialized for whole sequence. */
  if (strip->anim_startofs > 0) {
    const int strip_start = time_start_frame_get(strip);
    BKE_sound_set_scene_sound_pitch_constant_range(
        strip->scene_sound, strip_start - strip->anim_startofs, strip_start, 1.0f);
  }

  const float scene_fps = float(scene->r.frs_sec) / float(scene->r.frs_sec_base);
  const int sound_offset = time_get_rounded_sound_offset(strip, scene_fps);

  RetimingRangeData retiming_data = strip_retiming_range_data_get(scene, strip);
  for (int i = 0; i < retiming_data.ranges.size(); i++) {
    RetimingRange range = retiming_data.ranges[i];
    if (range.type == TRANSITION) {

      const int range_length = range.end - range.start;
      for (int i = 0; i <= range_length; i++) {
        const int frame = range.start + i;
        BKE_sound_set_scene_sound_pitch_at_frame(
            strip->scene_sound, frame + sound_offset, range.speed_table[i], true);
      }
    }
    else {
      BKE_sound_set_scene_sound_pitch_constant_range(
          strip->scene_sound, range.start + sound_offset, range.end + sound_offset, range.speed);
    }
  }
}

bool retiming_selection_clear(const Editing *ed)
{
  bool was_empty = true;

  LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
    for (SeqRetimingKey &key : retiming_keys_get(strip)) {
      was_empty &= (key.flag & SEQ_KEY_SELECTED) == 0;
      key.flag &= ~SEQ_KEY_SELECTED;
    }
  }
  return !was_empty;
}

void retiming_selection_append(

    SeqRetimingKey *key)
{
  key->flag |= SEQ_KEY_SELECTED;
}

void retiming_selection_remove(SeqRetimingKey *key)
{
  key->flag &= ~SEQ_KEY_SELECTED;
}

void retiming_selection_copy(SeqRetimingKey *dst, const SeqRetimingKey *src)
{
  retiming_selection_remove(dst);
  dst->flag |= (src->flag & SEQ_KEY_SELECTED);
}

blender::Map<SeqRetimingKey *, Strip *> retiming_selection_get(const Editing *ed)
{
  blender::Map<SeqRetimingKey *, Strip *> selection;
  if (!ed) {
    return selection;
  }
  LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
    for (SeqRetimingKey &key : retiming_keys_get(strip)) {
      if ((key.flag & SEQ_KEY_SELECTED) != 0) {
        selection.add(&key, strip);
      }
    }
  }
  return selection;
}

bool retiming_selection_contains(const Editing *ed, const SeqRetimingKey *key)
{
  LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
    for (const SeqRetimingKey &key_iter : retiming_keys_get(strip)) {
      if ((key_iter.flag & SEQ_KEY_SELECTED) != 0 && &key_iter == key) {
        return true;
      }
    }
  }
  return false;
}

bool retiming_selection_has_whole_transition(const Editing *ed, SeqRetimingKey *key)
{
  SeqRetimingKey *key_start = retiming_transition_start_get(key);
  SeqRetimingKey *key_end = key_start + 1;
  bool has_start = false, has_end = false;

  blender::Map<SeqRetimingKey *, Strip *> selection = retiming_selection_get(ed);

  for (auto item : selection.items()) {
    if (item.key == key_start) {
      has_start = true;
    }
    if (item.key == key_end) {
      has_end = true;
    }
    if (has_start && has_end) {
      return true;
    }
  }
  return false;
}

}  // namespace blender::seq
