/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 */

/** \file
 * \ingroup edtransform
 */

#include "DNA_space_types.h"

#include "MEM_guardedalloc.h"

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

#include "BKE_context.h"
#include "BKE_report.h"

#include "ED_markers.h"

#include "SEQ_relations.h"
#include "SEQ_sequencer.h"
#include "SEQ_time.h"
#include "SEQ_transform.h"
#include "SEQ_utils.h"

#include "UI_view2d.h"

#include "transform.h"
#include "transform_convert.h"

/** Used for sequencer transform. */
typedef struct TransDataSeq {
  struct Sequence *seq;
  /** A copy of #Sequence.flag that may be modified for nested strips. */
  int flag;
  /** Use this so we can have transform data at the strips start,
   * but apply correctly to the start frame. */
  int start_offset;
  /** one of #SELECT, #SEQ_LEFTSEL and #SEQ_RIGHTSEL. */
  short sel_flag;

} TransDataSeq;

/**
 * Sequencer transform customdata (stored in #TransCustomDataContainer).
 */
typedef struct TransSeq {
  TransDataSeq *tdseq;
  int min;
  int max;
  bool snap_left;
  int selection_channel_range_min;
  int selection_channel_range_max;
} TransSeq;

/* -------------------------------------------------------------------- */
/** \name Sequencer Transform Creation
 * \{ */

/* This function applies the rules for transforming a strip so duplicate
 * checks don't need to be added in multiple places.
 *
 * recursive, count and flag MUST be set.
 *
 * seq->depth must be set before running this function so we know if the strips
 * are root level or not
 */
static void SeqTransInfo(TransInfo *t, Sequence *seq, int *r_recursive, int *r_count, int *r_flag)
{
  /* for extend we need to do some tricks */
  if (t->mode == TFM_TIME_EXTEND) {

    /* *** Extend Transform *** */

    Scene *scene = t->scene;
    int cfra = CFRA;
    int left = SEQ_transform_get_left_handle_frame(seq, false);
    int right = SEQ_transform_get_right_handle_frame(seq, false);

    if (seq->depth == 0 && ((seq->flag & SELECT) == 0 || (seq->flag & SEQ_LOCK))) {
      *r_recursive = false;
      *r_count = 0;
      *r_flag = 0;
    }
    else {
      *r_recursive = false;
      *r_count = 1; /* unless its set to 0, extend will never set 2 handles at once */
      *r_flag = (seq->flag | SELECT) & ~(SEQ_LEFTSEL | SEQ_RIGHTSEL);

      if (t->frame_side == 'R') {
        if (right <= cfra) {
          *r_count = *r_flag = 0;
        } /* ignore */
        else if (left > cfra) {
        } /* keep the selection */
        else {
          *r_flag |= SEQ_RIGHTSEL;
        }
      }
      else {
        if (left >= cfra) {
          *r_count = *r_flag = 0;
        } /* ignore */
        else if (right < cfra) {
        } /* keep the selection */
        else {
          *r_flag |= SEQ_LEFTSEL;
        }
      }
    }
  }
  else {

    t->frame_side = 'B';

    /* *** Normal Transform *** */

    if (seq->depth == 0) {

      /* Count */

      /* Non nested strips (resect selection and handles) */
      if ((seq->flag & SELECT) == 0 || (seq->flag & SEQ_LOCK)) {
        *r_recursive = false;
        *r_count = 0;
        *r_flag = 0;
      }
      else {
        if ((seq->flag & (SEQ_LEFTSEL | SEQ_RIGHTSEL)) == (SEQ_LEFTSEL | SEQ_RIGHTSEL)) {
          *r_flag = seq->flag;
          *r_count = 2; /* we need 2 transdata's */
        }
        else {
          *r_flag = seq->flag;
          *r_count = 1; /* selected or with a handle selected */
        }

        /* Recursive */

        if ((seq->type == SEQ_TYPE_META) && ((seq->flag & (SEQ_LEFTSEL | SEQ_RIGHTSEL)) == 0)) {
          /* if any handles are selected, don't recurse */
          *r_recursive = true;
        }
        else {
          *r_recursive = false;
        }
      }
    }
    else {
      /* Nested, different rules apply */

      *r_flag = (seq->flag | SELECT) & ~(SEQ_LEFTSEL | SEQ_RIGHTSEL);
      *r_count = 1; /* ignore the selection for nested */
      *r_recursive = (seq->type == SEQ_TYPE_META);
    }
  }
}

static int SeqTransCount(TransInfo *t, Sequence *parent, ListBase *seqbase, int depth)
{
  Sequence *seq;
  int tot = 0, recursive, count, flag;

  for (seq = seqbase->first; seq; seq = seq->next) {
    seq->depth = depth;

    /* 'seq->tmp' is used by seq_tx_get_final_{left, right}
     * to check sequence's range and clamp to it if needed.
     * It's first place where digging into sequences tree, so store link to parent here. */
    seq->tmp = parent;

    SeqTransInfo(t, seq, &recursive, &count, &flag); /* ignore the flag */
    tot += count;

    if (recursive) {
      tot += SeqTransCount(t, seq, &seq->seqbase, depth + 1);
    }
  }

  return tot;
}

static TransData *SeqToTransData(
    TransData *td, TransData2D *td2d, TransDataSeq *tdsq, Sequence *seq, int flag, int sel_flag)
{
  int start_left;

  switch (sel_flag) {
    case SELECT:
      /* Use seq_tx_get_final_left() and an offset here
       * so transform has the left hand location of the strip.
       * tdsq->start_offset is used when flushing the tx data back */
      start_left = SEQ_transform_get_left_handle_frame(seq, false);
      td2d->loc[0] = start_left;
      tdsq->start_offset = start_left - seq->start; /* use to apply the original location */
      break;
    case SEQ_LEFTSEL:
      start_left = SEQ_transform_get_left_handle_frame(seq, false);
      td2d->loc[0] = start_left;
      break;
    case SEQ_RIGHTSEL:
      td2d->loc[0] = SEQ_transform_get_right_handle_frame(seq, false);
      break;
  }

  td2d->loc[1] = seq->machine; /* channel - Y location */
  td2d->loc[2] = 0.0f;
  td2d->loc2d = NULL;

  tdsq->seq = seq;

  /* Use instead of seq->flag for nested strips and other
   * cases where the selection may need to be modified */
  tdsq->flag = flag;
  tdsq->sel_flag = sel_flag;

  td->extra = (void *)tdsq; /* allow us to update the strip from here */

  td->flag = 0;
  td->loc = td2d->loc;
  copy_v3_v3(td->center, td->loc);
  copy_v3_v3(td->iloc, td->loc);

  memset(td->axismtx, 0, sizeof(td->axismtx));
  td->axismtx[2][2] = 1.0f;

  td->ext = NULL;
  td->val = NULL;

  td->flag |= TD_SELECTED;
  td->dist = 0.0;

  unit_m3(td->mtx);
  unit_m3(td->smtx);

  /* Time Transform (extend) */
  td->val = td2d->loc;
  td->ival = td2d->loc[0];

  return td;
}

static int SeqToTransData_Recursive(
    TransInfo *t, ListBase *seqbase, TransData *td, TransData2D *td2d, TransDataSeq *tdsq)
{
  Sequence *seq;
  int recursive, count, flag;
  int tot = 0;

  for (seq = seqbase->first; seq; seq = seq->next) {

    SeqTransInfo(t, seq, &recursive, &count, &flag);

    /* add children first so recalculating metastrips does nested strips first */
    if (recursive) {
      int tot_children = SeqToTransData_Recursive(t, &seq->seqbase, td, td2d, tdsq);

      td = td + tot_children;
      td2d = td2d + tot_children;
      tdsq = tdsq + tot_children;

      tot += tot_children;
    }

    /* use 'flag' which is derived from seq->flag but modified for special cases */
    if (flag & SELECT) {
      if (flag & (SEQ_LEFTSEL | SEQ_RIGHTSEL)) {
        if (flag & SEQ_LEFTSEL) {
          SeqToTransData(td++, td2d++, tdsq++, seq, flag, SEQ_LEFTSEL);
          tot++;
        }
        if (flag & SEQ_RIGHTSEL) {
          SeqToTransData(td++, td2d++, tdsq++, seq, flag, SEQ_RIGHTSEL);
          tot++;
        }
      }
      else {
        SeqToTransData(td++, td2d++, tdsq++, seq, flag, SELECT);
        tot++;
      }
    }
  }
  return tot;
}

static void SeqTransDataBounds(TransInfo *t, ListBase *seqbase, TransSeq *ts)
{
  Sequence *seq;
  int recursive, count, flag;
  int max = INT32_MIN, min = INT32_MAX;

  for (seq = seqbase->first; seq; seq = seq->next) {

    /* just to get the flag since there are corner cases where this isn't totally obvious */
    SeqTransInfo(t, seq, &recursive, &count, &flag);

    /* use 'flag' which is derived from seq->flag but modified for special cases */
    if (flag & SELECT) {
      if (flag & (SEQ_LEFTSEL | SEQ_RIGHTSEL)) {
        if (flag & SEQ_LEFTSEL) {
          min = min_ii(seq->startdisp, min);
          max = max_ii(seq->startdisp, max);
        }
        if (flag & SEQ_RIGHTSEL) {
          min = min_ii(seq->enddisp, min);
          max = max_ii(seq->enddisp, max);
        }
      }
      else {
        min = min_ii(seq->startdisp, min);
        max = max_ii(seq->enddisp, max);
      }
    }
  }

  if (ts) {
    ts->max = max;
    ts->min = min;
  }
}

static void freeSeqData(TransInfo *t, TransDataContainer *tc, TransCustomData *custom_data)
{
  Editing *ed = SEQ_editing_get(t->scene, false);

  if (ed != NULL) {

    ListBase *seqbasep = ed->seqbasep;
    TransData *td = tc->data;
    int a;

    /* prevent updating the same seq twice
     * if the transdata order is changed this will mess up
     * but so will TransDataSeq */
    Sequence *seq_prev = NULL;
    Sequence *seq;

    if (!(t->state == TRANS_CANCEL)) {

#if 0 /* Default 2.4 behavior. */

      /* flush to 2d vector from internally used 3d vector */
      for (a = 0; a < t->total; a++, td++) {
        if ((seq != seq_prev) && (seq->depth == 0) && (seq->flag & SEQ_OVERLAP)) {
          seq = ((TransDataSeq *)td->extra)->seq;
          SEQ_transform_seqbase_shuffle(seqbasep, seq, t->scene);
        }

        seq_prev = seq;
      }

#else /* durian hack */
      {
        int overlap = 0;

        for (a = 0, seq_prev = NULL; a < tc->data_len; a++, td++, seq_prev = seq) {
          seq = ((TransDataSeq *)td->extra)->seq;
          if ((seq != seq_prev) && (seq->depth == 0) && (seq->flag & SEQ_OVERLAP)) {
            overlap = 1;
            break;
          }
        }

        if (overlap) {
          const bool use_sync_markers = (((SpaceSeq *)t->area->spacedata.first)->flag &
                                         SEQ_MARKER_TRANS) != 0;
          ListBase *markers = &t->scene->markers;

          bool has_effect_root = false, has_effect_any = false;
          for (seq = seqbasep->first; seq; seq = seq->next) {
            seq->tmp = NULL;
          }

          td = tc->data;
          for (a = 0, seq_prev = NULL; a < tc->data_len; a++, td++, seq_prev = seq) {
            seq = ((TransDataSeq *)td->extra)->seq;
            if ((seq != seq_prev)) {
              /* check effects strips, we cant change their time */
              if ((seq->type & SEQ_TYPE_EFFECT) && seq->seq1) {
                has_effect_any = true;
                if (seq->depth == 0) {
                  has_effect_root = true;
                }
              }
              else {
                /* Tag seq with a non zero value, used by
                 * SEQ_transform_seqbase_shuffle_time to identify the ones to shuffle */
                if (seq->depth == 0) {
                  seq->tmp = (void *)1;
                }
              }
            }
          }

          if (t->flag & T_ALT_TRANSFORM) {
            int minframe = MAXFRAME;
            td = tc->data;
            for (a = 0, seq_prev = NULL; a < tc->data_len; a++, td++, seq_prev = seq) {
              seq = ((TransDataSeq *)td->extra)->seq;
              if ((seq != seq_prev) && (seq->depth == 0)) {
                minframe = min_ii(minframe, seq->startdisp);
              }
            }

            for (seq = seqbasep->first; seq; seq = seq->next) {
              if (!(seq->flag & SELECT)) {
                if (seq->startdisp >= minframe) {
                  seq->machine += MAXSEQ * 2;
                }
              }
            }

            SEQ_transform_seqbase_shuffle_time(seqbasep, t->scene, markers, use_sync_markers);

            for (seq = seqbasep->first; seq; seq = seq->next) {
              if (seq->machine >= MAXSEQ * 2) {
                seq->machine -= MAXSEQ * 2;
                seq->tmp = (void *)1;
              }
              else {
                seq->tmp = NULL;
              }
            }

            SEQ_transform_seqbase_shuffle_time(seqbasep, t->scene, markers, use_sync_markers);
          }
          else {
            SEQ_transform_seqbase_shuffle_time(seqbasep, t->scene, markers, use_sync_markers);
          }

          if (has_effect_any) {
            /* update effects strips based on strips just moved in time */
            td = tc->data;
            for (a = 0, seq_prev = NULL; a < tc->data_len; a++, td++, seq_prev = seq) {
              seq = ((TransDataSeq *)td->extra)->seq;
              if ((seq != seq_prev)) {
                if ((seq->type & SEQ_TYPE_EFFECT) && seq->seq1) {
                  SEQ_time_update_sequence(t->scene, seq);
                }
              }
            }
          }

          if (has_effect_root) {
            /* now if any effects _still_ overlap, we need to move them up */
            td = tc->data;
            for (a = 0, seq_prev = NULL; a < tc->data_len; a++, td++, seq_prev = seq) {
              seq = ((TransDataSeq *)td->extra)->seq;
              if ((seq != seq_prev) && (seq->depth == 0)) {
                if ((seq->type & SEQ_TYPE_EFFECT) && seq->seq1) {
                  if (SEQ_transform_test_overlap(seqbasep, seq)) {
                    SEQ_transform_seqbase_shuffle(seqbasep, seq, t->scene);
                  }
                }
              }
            }
            /* done with effects */
          }
        }
      }
#endif

      for (seq = seqbasep->first; seq; seq = seq->next) {
        /* We might want to build a list of effects that need to be updated during transform */
        if (seq->type & SEQ_TYPE_EFFECT) {
          if (seq->seq1 && seq->seq1->flag & SELECT) {
            SEQ_time_update_sequence(t->scene, seq);
          }
          else if (seq->seq2 && seq->seq2->flag & SELECT) {
            SEQ_time_update_sequence(t->scene, seq);
          }
          else if (seq->seq3 && seq->seq3->flag & SELECT) {
            SEQ_time_update_sequence(t->scene, seq);
          }
        }
      }

      SEQ_sort(t->scene);
    }
    else {
      /* Canceled, need to update the strips display */
      for (a = 0; a < tc->data_len; a++, td++) {
        seq = ((TransDataSeq *)td->extra)->seq;
        if ((seq != seq_prev) && (seq->depth == 0)) {
          if (seq->flag & SEQ_OVERLAP) {
            SEQ_transform_seqbase_shuffle(seqbasep, seq, t->scene);
          }

          SEQ_time_update_sequence_bounds(t->scene, seq);
        }
        seq_prev = seq;
      }
    }
  }

  if ((custom_data->data != NULL) && custom_data->use_free) {
    TransSeq *ts = custom_data->data;
    MEM_freeN(ts->tdseq);
    MEM_freeN(custom_data->data);
    custom_data->data = NULL;
  }

  DEG_id_tag_update(&t->scene->id, ID_RECALC_SEQUENCER_STRIPS);
}

void createTransSeqData(TransInfo *t)
{
#define XXX_DURIAN_ANIM_TX_HACK

  Scene *scene = t->scene;
  Editing *ed = SEQ_editing_get(t->scene, false);
  TransData *td = NULL;
  TransData2D *td2d = NULL;
  TransDataSeq *tdsq = NULL;
  TransSeq *ts = NULL;

  int count = 0;

  TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t);

  if (ed == NULL) {
    tc->data_len = 0;
    return;
  }

  tc->custom.type.free_cb = freeSeqData;
  t->frame_side = transform_convert_frame_side_dir_get(t, (float)CFRA);

#ifdef XXX_DURIAN_ANIM_TX_HACK
  {
    Sequence *seq;
    for (seq = ed->seqbasep->first; seq; seq = seq->next) {
      /* hack */
      if ((seq->flag & SELECT) == 0 && seq->type & SEQ_TYPE_EFFECT) {
        Sequence *seq_user;
        int i;
        for (i = 0; i < 3; i++) {
          seq_user = *((&seq->seq1) + i);
          if (seq_user && (seq_user->flag & SELECT) && !(seq_user->flag & SEQ_LOCK) &&
              !(seq_user->flag & (SEQ_LEFTSEL | SEQ_RIGHTSEL))) {
            seq->flag |= SELECT;
          }
        }
      }
    }
  }
#endif

  count = SeqTransCount(t, NULL, ed->seqbasep, 0);

  /* allocate memory for data */
  tc->data_len = count;

  /* stop if trying to build list if nothing selected */
  if (count == 0) {
    return;
  }

  tc->custom.type.data = ts = MEM_callocN(sizeof(TransSeq), "transseq");
  tc->custom.type.use_free = true;
  td = tc->data = MEM_callocN(tc->data_len * sizeof(TransData), "TransSeq TransData");
  td2d = tc->data_2d = MEM_callocN(tc->data_len * sizeof(TransData2D), "TransSeq TransData2D");
  ts->tdseq = tdsq = MEM_callocN(tc->data_len * sizeof(TransDataSeq), "TransSeq TransDataSeq");

  /* loop 2: build transdata array */
  SeqToTransData_Recursive(t, ed->seqbasep, td, td2d, tdsq);
  SeqTransDataBounds(t, ed->seqbasep, ts);

  if (t->flag & T_MODAL) {
    /* set the snap mode based on how close the mouse is at the end/start points */
    int xmouse = (int)UI_view2d_region_to_view_x((View2D *)t->view, t->mouse.imval[0]);
    if (abs(xmouse - ts->max) > abs(xmouse - ts->min)) {
      ts->snap_left = true;
    }
  }

  ts->selection_channel_range_min = MAXSEQ + 1;
  LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) {
    if ((seq->flag & SELECT) != 0) {
      ts->selection_channel_range_min = min_ii(ts->selection_channel_range_min, seq->machine);
      ts->selection_channel_range_max = max_ii(ts->selection_channel_range_max, seq->machine);
    }
  }

#undef XXX_DURIAN_ANIM_TX_HACK
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name UVs Transform Flush
 * \{ */

/* commented _only_ because the meta may have animation data which
 * needs moving too T28158. */

BLI_INLINE void trans_update_seq(Scene *sce, Sequence *seq, int old_start, int sel_flag)
{
  if (seq->depth == 0) {
    /* Calculate this strip and all nested strips.
     * Children are ALWAYS transformed first so we don't need to do this in another loop.
     */
    SEQ_time_update_sequence(sce, seq);
  }
  else {
    SEQ_time_update_sequence_bounds(sce, seq);
  }

  if (sel_flag == SELECT) {
    SEQ_offset_animdata(sce, seq, seq->start - old_start);
  }
}

static void flushTransSeq(TransInfo *t)
{
  /* Editing null check already done */
  ListBase *seqbasep = SEQ_editing_get(t->scene, false)->seqbasep;

  int a, new_frame;
  TransData *td = NULL;
  TransData2D *td2d = NULL;
  TransDataSeq *tdsq = NULL;
  Sequence *seq;

  TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t);

  /* prevent updating the same seq twice
   * if the transdata order is changed this will mess up
   * but so will TransDataSeq */
  Sequence *seq_prev = NULL;
  int old_start_prev = 0, sel_flag_prev = 0;

  /* flush to 2d vector from internally used 3d vector */
  for (a = 0, td = tc->data, td2d = tc->data_2d; a < tc->data_len; a++, td++, td2d++) {
    int old_start;
    tdsq = (TransDataSeq *)td->extra;
    seq = tdsq->seq;
    old_start = seq->start;
    new_frame = round_fl_to_int(td2d->loc[0]);

    switch (tdsq->sel_flag) {
      case SELECT:
        if ((seq->depth != 0 || SEQ_transform_sequence_can_be_translated(seq))) {
          /* for meta's, their children move */
          seq->start = new_frame - tdsq->start_offset;
        }
        if (seq->depth == 0) {
          seq->machine = round_fl_to_int(td2d->loc[1]);
          CLAMP(seq->machine, 1, MAXSEQ);
        }
        break;
      case SEQ_LEFTSEL: /* no vertical transform  */
        SEQ_transform_set_left_handle_frame(seq, new_frame);
        SEQ_transform_handle_xlimits(seq, tdsq->flag & SEQ_LEFTSEL, tdsq->flag & SEQ_RIGHTSEL);

        /* todo - move this into aftertrans update? - old seq tx needed it anyway */
        SEQ_transform_fix_single_image_seq_offsets(seq);
        break;
      case SEQ_RIGHTSEL: /* no vertical transform  */
        SEQ_transform_set_right_handle_frame(seq, new_frame);
        SEQ_transform_handle_xlimits(seq, tdsq->flag & SEQ_LEFTSEL, tdsq->flag & SEQ_RIGHTSEL);

        /* todo - move this into aftertrans update? - old seq tx needed it anyway */
        SEQ_transform_fix_single_image_seq_offsets(seq);
        break;
    }

    /* Update *previous* seq! Else, we would update a seq after its first transform,
     * and if it has more than one (like e.g. SEQ_LEFTSEL and SEQ_RIGHTSEL),
     * the others are not updated! See T38469.
     */
    if (seq != seq_prev) {
      if (seq_prev) {
        trans_update_seq(t->scene, seq_prev, old_start_prev, sel_flag_prev);
      }

      seq_prev = seq;
      old_start_prev = old_start;
      sel_flag_prev = tdsq->sel_flag;
    }
    else {
      /* We want to accumulate *all* sel_flags for this seq! */
      sel_flag_prev |= tdsq->sel_flag;
    }
  }

  /* Don't forget to update the last seq! */
  if (seq_prev) {
    trans_update_seq(t->scene, seq_prev, old_start_prev, sel_flag_prev);
  }

  /* originally TFM_TIME_EXTEND, transform changes */
  if (ELEM(t->mode, TFM_SEQ_SLIDE, TFM_TIME_TRANSLATE)) {
    /* Special annoying case here, need to calc metas with TFM_TIME_EXTEND only */

    /* calc all meta's then effects T27953. */
    for (seq = seqbasep->first; seq; seq = seq->next) {
      if (seq->type == SEQ_TYPE_META && seq->flag & SELECT) {
        SEQ_time_update_sequence(t->scene, seq);
      }
    }
    for (seq = seqbasep->first; seq; seq = seq->next) {
      if (seq->seq1 || seq->seq2 || seq->seq3) {
        SEQ_time_update_sequence(t->scene, seq);
      }
    }

    /* update effects inside meta's */
    for (a = 0, seq_prev = NULL, td = tc->data, td2d = tc->data_2d; a < tc->data_len;
         a++, td++, td2d++, seq_prev = seq) {
      tdsq = (TransDataSeq *)td->extra;
      seq = tdsq->seq;
      if ((seq != seq_prev) && (seq->depth != 0)) {
        if (seq->seq1 || seq->seq2 || seq->seq3) {
          SEQ_time_update_sequence(t->scene, seq);
        }
      }
    }
  }

  /* need to do the overlap check in a new loop otherwise adjacent strips
   * will not be updated and we'll get false positives */
  seq_prev = NULL;
  for (a = 0, td = tc->data, td2d = tc->data_2d; a < tc->data_len; a++, td++, td2d++) {

    tdsq = (TransDataSeq *)td->extra;
    seq = tdsq->seq;

    if (seq != seq_prev) {
      if (seq->depth == 0) {
        /* test overlap, displays red outline */
        seq->flag &= ~SEQ_OVERLAP;
        if (SEQ_transform_test_overlap(seqbasep, seq)) {
          seq->flag |= SEQ_OVERLAP;
        }
      }
    }
    seq_prev = seq;
  }
}

/* helper for recalcData() - for sequencer transforms */
void recalcData_sequencer(TransInfo *t)
{
  TransData *td;
  int a;
  Sequence *seq_prev = NULL;

  TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t);

  for (a = 0, td = tc->data; a < tc->data_len; a++, td++) {
    TransDataSeq *tdsq = (TransDataSeq *)td->extra;
    Sequence *seq = tdsq->seq;

    if (seq != seq_prev) {
      SEQ_relations_invalidate_cache_composite(t->scene, seq);
    }

    seq_prev = seq;
  }

  DEG_id_tag_update(&t->scene->id, ID_RECALC_SEQUENCER_STRIPS);

  flushTransSeq(t);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Special After Transform Sequencer
 * \{ */

void special_aftertrans_update__sequencer(bContext *UNUSED(C), TransInfo *t)
{
  if (t->state == TRANS_CANCEL) {
    return;
  }
  /* freeSeqData in transform_conversions.c does this
   * keep here so the else at the end wont run... */

  SpaceSeq *sseq = (SpaceSeq *)t->area->spacedata.first;

  /* Marker transform, not especially nice but we may want to move markers
   * at the same time as strips in the Video Sequencer. */
  if (sseq->flag & SEQ_MARKER_TRANS) {
    /* can't use TFM_TIME_EXTEND
     * for some reason EXTEND is changed into TRANSLATE, so use frame_side instead */

    if (t->mode == TFM_SEQ_SLIDE) {
      if (t->frame_side == 'B') {
        ED_markers_post_apply_transform(
            &t->scene->markers, t->scene, TFM_TIME_TRANSLATE, t->values[0], t->frame_side);
      }
    }
    else if (ELEM(t->frame_side, 'L', 'R')) {
      ED_markers_post_apply_transform(
          &t->scene->markers, t->scene, TFM_TIME_EXTEND, t->values[0], t->frame_side);
    }
  }
}

void transform_convert_sequencer_channel_clamp(TransInfo *t)
{
  const TransSeq *ts = (TransSeq *)TRANS_DATA_CONTAINER_FIRST_SINGLE(t)->custom.type.data;
  const int channel_offset = round_fl_to_int(t->values[1]);
  const int min_channel_after_transform = ts->selection_channel_range_min + channel_offset;
  const int max_channel_after_transform = ts->selection_channel_range_max + channel_offset;

  if (max_channel_after_transform > MAXSEQ) {
    t->values[1] -= max_channel_after_transform - MAXSEQ;
  }
  if (min_channel_after_transform < 1) {
    t->values[1] -= min_channel_after_transform - 1;
  }
}

int transform_convert_sequencer_get_snap_bound(TransInfo *t)
{
  TransSeq *ts = TRANS_DATA_CONTAINER_FIRST_SINGLE(t)->custom.type.data;
  return ts->snap_left ? ts->min : ts->max;
}

/** \} */
