/*
 * 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) 2009 Blender Foundation, Joshua Leung
 * All rights reserved.
 */

/** \file
 * \ingroup bke
 */

#include <float.h>
#include <math.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>

#include "MEM_guardedalloc.h"

#include "CLG_log.h"

#include "DNA_anim_types.h"
#include "DNA_screen_types.h"

#include "BLT_translation.h"

#include "BLI_blenlib.h"
#include "BLI_ghash.h"
#include "BLI_math.h" /* windows needs for M_PI */
#include "BLI_noise.h"
#include "BLI_utildefines.h"

#include "BKE_fcurve.h"
#include "BKE_idprop.h"

static CLG_LogRef LOG = {"bke.fmodifier"};

/* -------------------------------------------------------------------- */
/** \name F-Curve Modifier Types
 * \{ */

/* Info ------------------------------- */

/* F-Modifiers are modifiers which operate on F-Curves. However, they can also be defined
 * on NLA-Strips to affect all of the F-Curves referenced by the NLA-Strip.
 */

/* Template --------------------------- */

/* Each modifier defines a set of functions, which will be called at the appropriate
 * times. In addition to this, each modifier should have a type-info struct, where
 * its functions are attached for use.
 */

/* Template for type-info data:
 * - make a copy of this when creating new modifiers, and just change the functions
 *   pointed to as necessary
 * - although the naming of functions doesn't matter, it would help for code
 *   readability, to follow the same naming convention as is presented here
 * - any functions that a constraint doesn't need to define, don't define
 *   for such cases, just use NULL
 * - these should be defined after all the functions have been defined, so that
 *   forward-definitions/prototypes don't need to be used!
 * - keep this copy #if-def'd so that future modifier can get based off this
 */
#if 0
static FModifierTypeInfo FMI_MODNAME = {
    FMODIFIER_TYPE_MODNAME,        /* type */
    sizeof(FMod_ModName),          /* size */
    FMI_TYPE_SOME_ACTION,          /* action type */
    FMI_REQUIRES_SOME_REQUIREMENT, /* requirements */
    "Modifier Name",               /* name */
    "FMod_ModName",                /* struct name */
    0,                             /* storage size */
    fcm_modname_free,              /* free data */
    fcm_modname_relink,            /* relink data */
    fcm_modname_copy,              /* copy data */
    fcm_modname_new_data,          /* new data */
    fcm_modname_verify,            /* verify */
    fcm_modname_time,              /* evaluate time */
    fcm_modname_evaluate,          /* evaluate */
};
#endif

/* Generator F-Curve Modifier --------------------------- */

/* Generators available:
 *  1) simple polynomial generator:
 *     - Expanded form:
 *       (y = C[0]*(x^(n)) + C[1]*(x^(n-1)) + ... + C[n])
 *     - Factorized form:
 *       (y = (C[0][0]*x + C[0][1]) * (C[1][0]*x + C[1][1]) * ... * (C[n][0]*x + C[n][1]))
 */

static void fcm_generator_free(FModifier *fcm)
{
  FMod_Generator *data = (FMod_Generator *)fcm->data;

  /* free polynomial coefficients array */
  if (data->coefficients) {
    MEM_freeN(data->coefficients);
  }
}

static void fcm_generator_copy(FModifier *fcm, const FModifier *src)
{
  FMod_Generator *gen = (FMod_Generator *)fcm->data;
  FMod_Generator *ogen = (FMod_Generator *)src->data;

  /* copy coefficients array? */
  if (ogen->coefficients) {
    gen->coefficients = MEM_dupallocN(ogen->coefficients);
  }
}

static void fcm_generator_new_data(void *mdata)
{
  FMod_Generator *data = (FMod_Generator *)mdata;
  float *cp;

  /* set default generator to be linear 0-1 (gradient = 1, y-offset = 0) */
  data->poly_order = 1;
  data->arraysize = 2;
  cp = data->coefficients = MEM_callocN(sizeof(float) * 2, "FMod_Generator_Coefs");
  cp[0] = 0; /* y-offset */
  cp[1] = 1; /* gradient */
}

static void fcm_generator_verify(FModifier *fcm)
{
  FMod_Generator *data = (FMod_Generator *)fcm->data;

  /* requirements depend on mode */
  switch (data->mode) {
    case FCM_GENERATOR_POLYNOMIAL: /* expanded polynomial expression */
    {
      const int arraysize_new = data->poly_order + 1;
      /* arraysize needs to be order+1, so resize if not */
      if (data->arraysize != arraysize_new) {
        data->coefficients = MEM_recallocN(data->coefficients, sizeof(float) * arraysize_new);
        data->arraysize = arraysize_new;
      }
      break;
    }
    case FCM_GENERATOR_POLYNOMIAL_FACTORISED: /* expanded polynomial expression */
    {
      const int arraysize_new = data->poly_order * 2;
      /* arraysize needs to be (2 * order), so resize if not */
      if (data->arraysize != arraysize_new) {
        data->coefficients = MEM_recallocN(data->coefficients, sizeof(float) * arraysize_new);
        data->arraysize = arraysize_new;
      }
      break;
    }
  }
}

static void fcm_generator_evaluate(
    FCurve *UNUSED(fcu), FModifier *fcm, float *cvalue, float evaltime, void *UNUSED(storage))
{
  FMod_Generator *data = (FMod_Generator *)fcm->data;

  /* behavior depends on mode
   * NOTE: the data in its default state is fine too
   */
  switch (data->mode) {
    case FCM_GENERATOR_POLYNOMIAL: /* expanded polynomial expression */
    {
      /* we overwrite cvalue with the sum of the polynomial */
      float *powers = MEM_callocN(sizeof(float) * data->arraysize, "Poly Powers");
      float value = 0.0f;

      /* for each x^n, precalculate value based on previous one first... this should be
       * faster that calling pow() for each entry
       */
      for (uint i = 0; i < data->arraysize; i++) {
        /* first entry is x^0 = 1, otherwise, calculate based on previous */
        if (i) {
          powers[i] = powers[i - 1] * evaltime;
        }
        else {
          powers[0] = 1;
        }
      }

      /* for each coefficient, add to value, which we'll write to *cvalue in one go */
      for (uint i = 0; i < data->arraysize; i++) {
        value += data->coefficients[i] * powers[i];
      }

      /* only if something changed, write *cvalue in one go */
      if (data->poly_order) {
        if (data->flag & FCM_GENERATOR_ADDITIVE) {
          *cvalue += value;
        }
        else {
          *cvalue = value;
        }
      }

      /* cleanup */
      if (powers) {
        MEM_freeN(powers);
      }
      break;
    }
    case FCM_GENERATOR_POLYNOMIAL_FACTORISED: /* Factorized polynomial */
    {
      float value = 1.0f, *cp = NULL;
      unsigned int i;

      /* For each coefficient pair,
       * solve for that bracket before accumulating in value by multiplying. */
      for (cp = data->coefficients, i = 0; (cp) && (i < (uint)data->poly_order); cp += 2, i++) {
        value *= (cp[0] * evaltime + cp[1]);
      }

      /* only if something changed, write *cvalue in one go */
      if (data->poly_order) {
        if (data->flag & FCM_GENERATOR_ADDITIVE) {
          *cvalue += value;
        }
        else {
          *cvalue = value;
        }
      }
      break;
    }
  }
}

static FModifierTypeInfo FMI_GENERATOR = {
    FMODIFIER_TYPE_GENERATOR, /* type */
    sizeof(FMod_Generator),   /* size */
    FMI_TYPE_GENERATE_CURVE,  /* action type */
    FMI_REQUIRES_NOTHING,     /* requirements */
    N_("Generator"),          /* name */
    "FMod_Generator",         /* struct name */
    0,                        /* storage size */
    fcm_generator_free,       /* free data */
    fcm_generator_copy,       /* copy data */
    fcm_generator_new_data,   /* new data */
    fcm_generator_verify,     /* verify */
    NULL,                     /* evaluate time */
    fcm_generator_evaluate,   /* evaluate */
};

/* Built-In Function Generator F-Curve Modifier --------------------------- */

/* This uses the general equation for equations:
 *   y = amplitude * fn(phase_multiplier * x + phase_offset) + y_offset
 *
 * where amplitude, phase_multiplier/offset, y_offset are user-defined coefficients,
 * x is the evaluation 'time', and 'y' is the resultant value
 *
 * Functions available are
 * sin, cos, tan, sinc (normalized sin), natural log, square root
 */

static void fcm_fn_generator_new_data(void *mdata)
{
  FMod_FunctionGenerator *data = (FMod_FunctionGenerator *)mdata;

  /* set amplitude and phase multiplier to 1.0f so that something is generated */
  data->amplitude = 1.0f;
  data->phase_multiplier = 1.0f;
}

/* Unary 'normalized sine' function
 * y = sin(PI + x) / (PI * x),
 * except for x = 0 when y = 1.
 */
static double sinc(double x)
{
  if (fabs(x) < 0.0001) {
    return 1.0;
  }

  return sin(M_PI * x) / (M_PI * x);
}

static void fcm_fn_generator_evaluate(
    FCurve *UNUSED(fcu), FModifier *fcm, float *cvalue, float evaltime, void *UNUSED(storage))
{
  FMod_FunctionGenerator *data = (FMod_FunctionGenerator *)fcm->data;
  double arg = data->phase_multiplier * evaltime + data->phase_offset;
  double (*fn)(double v) = NULL;

  /* get function pointer to the func to use:
   * WARNING: must perform special argument validation hereto guard against crashes
   */
  switch (data->type) {
    /* simple ones */
    case FCM_GENERATOR_FN_SIN: /* sine wave */
      fn = sin;
      break;
    case FCM_GENERATOR_FN_COS: /* cosine wave */
      fn = cos;
      break;
    case FCM_GENERATOR_FN_SINC: /* normalized sine wave */
      fn = sinc;
      break;

    /* validation required */
    case FCM_GENERATOR_FN_TAN: /* tangent wave */
    {
      /* check that argument is not on one of the discontinuities (i.e. 90deg, 270 deg, etc) */
      if (IS_EQ(fmod((arg - M_PI_2), M_PI), 0.0)) {
        if ((data->flag & FCM_GENERATOR_ADDITIVE) == 0) {
          *cvalue = 0.0f; /* no value possible here */
        }
      }
      else {
        fn = tan;
      }
      break;
    }
    case FCM_GENERATOR_FN_LN: /* natural log */
    {
      /* check that value is greater than 1? */
      if (arg > 1.0) {
        fn = log;
      }
      else {
        if ((data->flag & FCM_GENERATOR_ADDITIVE) == 0) {
          *cvalue = 0.0f; /* no value possible here */
        }
      }
      break;
    }
    case FCM_GENERATOR_FN_SQRT: /* square root */
    {
      /* no negative numbers */
      if (arg > 0.0) {
        fn = sqrt;
      }
      else {
        if ((data->flag & FCM_GENERATOR_ADDITIVE) == 0) {
          *cvalue = 0.0f; /* no value possible here */
        }
      }
      break;
    }
    default:
      CLOG_ERROR(&LOG, "Invalid Function-Generator for F-Modifier - %d", data->type);
      break;
  }

  /* execute function callback to set value if appropriate */
  if (fn) {
    float value = (float)(data->amplitude * (float)fn(arg) + data->value_offset);

    if (data->flag & FCM_GENERATOR_ADDITIVE) {
      *cvalue += value;
    }
    else {
      *cvalue = value;
    }
  }
}

static FModifierTypeInfo FMI_FN_GENERATOR = {
    FMODIFIER_TYPE_FN_GENERATOR,    /* type */
    sizeof(FMod_FunctionGenerator), /* size */
    FMI_TYPE_GENERATE_CURVE,        /* action type */
    FMI_REQUIRES_NOTHING,           /* requirements */
    N_("Built-In Function"),        /* name */
    "FMod_FunctionGenerator",       /* struct name */
    0,                              /* storage size */
    NULL,                           /* free data */
    NULL,                           /* copy data */
    fcm_fn_generator_new_data,      /* new data */
    NULL,                           /* verify */
    NULL,                           /* evaluate time */
    fcm_fn_generator_evaluate,      /* evaluate */
};

/* Envelope F-Curve Modifier --------------------------- */

static void fcm_envelope_free(FModifier *fcm)
{
  FMod_Envelope *env = (FMod_Envelope *)fcm->data;

  /* free envelope data array */
  if (env->data) {
    MEM_freeN(env->data);
  }
}

static void fcm_envelope_copy(FModifier *fcm, const FModifier *src)
{
  FMod_Envelope *env = (FMod_Envelope *)fcm->data;
  FMod_Envelope *oenv = (FMod_Envelope *)src->data;

  /* copy envelope data array */
  if (oenv->data) {
    env->data = MEM_dupallocN(oenv->data);
  }
}

static void fcm_envelope_new_data(void *mdata)
{
  FMod_Envelope *env = (FMod_Envelope *)mdata;

  /* set default min/max ranges */
  env->min = -1.0f;
  env->max = 1.0f;
}

static void fcm_envelope_verify(FModifier *fcm)
{
  FMod_Envelope *env = (FMod_Envelope *)fcm->data;

  /* if the are points, perform bubble-sort on them, as user may have changed the order */
  if (env->data) {
    /* XXX todo... */
  }
}

static void fcm_envelope_evaluate(
    FCurve *UNUSED(fcu), FModifier *fcm, float *cvalue, float evaltime, void *UNUSED(storage))
{
  FMod_Envelope *env = (FMod_Envelope *)fcm->data;
  FCM_EnvelopeData *fed, *prevfed, *lastfed;
  float min = 0.0f, max = 0.0f, fac = 0.0f;
  int a;

  /* get pointers */
  if (env->data == NULL) {
    return;
  }
  prevfed = env->data;
  fed = prevfed + 1;
  lastfed = prevfed + (env->totvert - 1);

  /* get min/max values for envelope at evaluation time (relative to mid-value) */
  if (prevfed->time >= evaltime) {
    /* before or on first sample, so just extend value */
    min = prevfed->min;
    max = prevfed->max;
  }
  else if (lastfed->time <= evaltime) {
    /* after or on last sample, so just extend value */
    min = lastfed->min;
    max = lastfed->max;
  }
  else {
    /* evaltime occurs somewhere between segments */
    /* TODO: implement binary search for this to make it faster? */
    for (a = 0; prevfed && fed && (a < env->totvert - 1); a++, prevfed = fed, fed++) {
      /* evaltime occurs within the interval defined by these two envelope points */
      if ((prevfed->time <= evaltime) && (fed->time >= evaltime)) {
        float afac, bfac, diff;

        diff = fed->time - prevfed->time;
        afac = (evaltime - prevfed->time) / diff;
        bfac = (fed->time - evaltime) / diff;

        min = bfac * prevfed->min + afac * fed->min;
        max = bfac * prevfed->max + afac * fed->max;

        break;
      }
    }
  }

  /* adjust *cvalue
   * - fac is the ratio of how the current y-value corresponds to the reference range
   * - thus, the new value is found by mapping the old range to the new!
   */
  fac = (*cvalue - (env->midval + env->min)) / (env->max - env->min);
  *cvalue = min + fac * (max - min);
}

static FModifierTypeInfo FMI_ENVELOPE = {
    FMODIFIER_TYPE_ENVELOPE, /* type */
    sizeof(FMod_Envelope),   /* size */
    FMI_TYPE_REPLACE_VALUES, /* action type */
    0,                       /* requirements */
    N_("Envelope"),          /* name */
    "FMod_Envelope",         /* struct name */
    0,                       /* storage size */
    fcm_envelope_free,       /* free data */
    fcm_envelope_copy,       /* copy data */
    fcm_envelope_new_data,   /* new data */
    fcm_envelope_verify,     /* verify */
    NULL,                    /* evaluate time */
    fcm_envelope_evaluate,   /* evaluate */
};

/* exported function for finding points */

/* Binary search algorithm for finding where to insert Envelope Data Point.
 * Returns the index to insert at (data already at that index will be offset if replace is 0)
 */
#define BINARYSEARCH_FRAMEEQ_THRESH 0.0001f

int BKE_fcm_envelope_find_index(FCM_EnvelopeData array[],
                                float frame,
                                int arraylen,
                                bool *r_exists)
{
  int start = 0, end = arraylen;
  int loopbreaker = 0, maxloop = arraylen * 2;

  /* initialize exists-flag first */
  *r_exists = false;

  /* sneaky optimizations (don't go through searching process if...):
   * - keyframe to be added is to be added out of current bounds
   * - keyframe to be added would replace one of the existing ones on bounds
   */
  if ((arraylen <= 0) || (array == NULL)) {
    CLOG_WARN(&LOG, "encountered invalid array");
    return 0;
  }

  /* check whether to add before/after/on */
  float framenum;

  /* 'First' Point (when only one point, this case is used) */
  framenum = array[0].time;
  if (IS_EQT(frame, framenum, BINARYSEARCH_FRAMEEQ_THRESH)) {
    *r_exists = true;
    return 0;
  }
  if (frame < framenum) {
    return 0;
  }

  /* 'Last' Point */
  framenum = array[(arraylen - 1)].time;
  if (IS_EQT(frame, framenum, BINARYSEARCH_FRAMEEQ_THRESH)) {
    *r_exists = true;
    return (arraylen - 1);
  }
  if (frame > framenum) {
    return arraylen;
  }

  /* most of the time, this loop is just to find where to put it
   * - 'loopbreaker' is just here to prevent infinite loops
   */
  for (loopbreaker = 0; (start <= end) && (loopbreaker < maxloop); loopbreaker++) {
    /* compute and get midpoint */

    /* we calculate the midpoint this way to avoid int overflows... */
    int mid = start + ((end - start) / 2);

    float midfra = array[mid].time;

    /* check if exactly equal to midpoint */
    if (IS_EQT(frame, midfra, BINARYSEARCH_FRAMEEQ_THRESH)) {
      *r_exists = true;
      return mid;
    }

    /* repeat in upper/lower half */
    if (frame > midfra) {
      start = mid + 1;
    }
    else if (frame < midfra) {
      end = mid - 1;
    }
  }

  /* print error if loop-limit exceeded */
  if (loopbreaker == (maxloop - 1)) {
    CLOG_ERROR(&LOG, "binary search was taking too long");

    // include debug info
    CLOG_ERROR(&LOG,
               "\tround = %d: start = %d, end = %d, arraylen = %d",
               loopbreaker,
               start,
               end,
               arraylen);
  }

  /* not found, so return where to place it */
  return start;
}
#undef BINARYSEARCH_FRAMEEQ_THRESH

/* Cycles F-Curve Modifier  --------------------------- */

/* This modifier changes evaltime to something that exists within the curve's frame-range,
 * then re-evaluates modifier stack up to this point using the new time. This re-entrant behavior
 * is very likely to be more time-consuming than the original approach...
 * (which was tightly integrated into the calculation code...).
 *
 * NOTE: this needs to be at the start of the stack to be of use,
 * as it needs to know the extents of the keyframes/sample-data.
 *
 * Possible TODO - store length of cycle information that can be initialized from the extents of
 * the keyframes/sample-data, and adjusted as appropriate.
 */

/* temp data used during evaluation */
typedef struct tFCMED_Cycles {
  float cycyofs; /* y-offset to apply */
} tFCMED_Cycles;

static void fcm_cycles_new_data(void *mdata)
{
  FMod_Cycles *data = (FMod_Cycles *)mdata;

  /* turn on cycles by default */
  data->before_mode = data->after_mode = FCM_EXTRAPOLATE_CYCLIC;
}

static float fcm_cycles_time(
    FCurve *fcu, FModifier *fcm, float UNUSED(cvalue), float evaltime, void *storage_)
{
  const FMod_Cycles *data = (FMod_Cycles *)fcm->data;
  tFCMED_Cycles *storage = storage_;
  float prevkey[2], lastkey[2], cycyofs = 0.0f;
  short side = 0, mode = 0;
  int cycles = 0;
  float ofs = 0;

  /* Initialize storage. */
  storage->cycyofs = 0;

  /* check if modifier is first in stack, otherwise disable ourself... */
  /* FIXME... */
  if (fcm->prev) {
    fcm->flag |= FMODIFIER_FLAG_DISABLED;
    return evaltime;
  }

  if (fcu == NULL || (fcu->bezt == NULL && fcu->fpt == NULL)) {
    return evaltime;
  }

  /* calculate new evaltime due to cyclic interpolation */
  if (fcu->bezt) {
    const BezTriple *prevbezt = fcu->bezt;
    const BezTriple *lastbezt = prevbezt + fcu->totvert - 1;

    prevkey[0] = prevbezt->vec[1][0];
    prevkey[1] = prevbezt->vec[1][1];

    lastkey[0] = lastbezt->vec[1][0];
    lastkey[1] = lastbezt->vec[1][1];
  }
  else {
    BLI_assert(fcu->fpt != NULL);
    const FPoint *prevfpt = fcu->fpt;
    const FPoint *lastfpt = prevfpt + fcu->totvert - 1;

    prevkey[0] = prevfpt->vec[0];
    prevkey[1] = prevfpt->vec[1];

    lastkey[0] = lastfpt->vec[0];
    lastkey[1] = lastfpt->vec[1];
  }

  /* check if modifier will do anything
   * 1) if in data range, definitely don't do anything
   * 2) if before first frame or after last frame, make sure some cycling is in use
   */
  if (evaltime < prevkey[0]) {
    if (data->before_mode) {
      side = -1;
      mode = data->before_mode;
      cycles = data->before_cycles;
      ofs = prevkey[0];
    }
  }
  else if (evaltime > lastkey[0]) {
    if (data->after_mode) {
      side = 1;
      mode = data->after_mode;
      cycles = data->after_cycles;
      ofs = lastkey[0];
    }
  }
  if ((ELEM(0, side, mode))) {
    return evaltime;
  }

  /* find relative place within a cycle */
  {
    /* calculate period and amplitude (total height) of a cycle */
    const float cycdx = lastkey[0] - prevkey[0];
    const float cycdy = lastkey[1] - prevkey[1];

    /* check if cycle is infinitely small, to be point of being impossible to use */
    if (cycdx == 0) {
      return evaltime;
    }

    /* calculate the 'number' of the cycle */
    const float cycle = ((float)side * (evaltime - ofs) / cycdx);

    /* calculate the time inside the cycle */
    const float cyct = fmod(evaltime - ofs, cycdx);

    /* check that cyclic is still enabled for the specified time */
    if (cycles == 0) {
      /* catch this case so that we don't exit when we have (cycles = 0)
       * as this indicates infinite cycles...
       */
    }
    else if (cycle > cycles) {
      /* we are too far away from range to evaluate
       * TODO: but we should still hold last value...
       */
      return evaltime;
    }

    /* check if 'cyclic extrapolation', and thus calculate y-offset for this cycle */
    if (mode == FCM_EXTRAPOLATE_CYCLIC_OFFSET) {
      if (side < 0) {
        cycyofs = (float)floor((evaltime - ofs) / cycdx);
      }
      else {
        cycyofs = (float)ceil((evaltime - ofs) / cycdx);
      }
      cycyofs *= cycdy;
    }

    /* special case for cycle start/end */
    if (cyct == 0.0f) {
      evaltime = (side == 1 ? lastkey[0] : prevkey[0]);

      if ((mode == FCM_EXTRAPOLATE_MIRROR) && ((int)cycle % 2)) {
        evaltime = (side == 1 ? prevkey[0] : lastkey[0]);
      }
    }
    /* calculate where in the cycle we are (overwrite evaltime to reflect this) */
    else if ((mode == FCM_EXTRAPOLATE_MIRROR) && ((int)(cycle + 1) % 2)) {
      /* When 'mirror' option is used and cycle number is odd, this cycle is played in reverse
       * - for 'before' extrapolation, we need to flip in a different way, otherwise values past
       *   then end of the curve get referenced
       *   (result of fmod will be negative, and with different phase).
       */
      if (side < 0) {
        evaltime = prevkey[0] - cyct;
      }
      else {
        evaltime = lastkey[0] - cyct;
      }
    }
    else {
      /* the cycle is played normally... */
      evaltime = prevkey[0] + cyct;
    }
    if (evaltime < prevkey[0]) {
      evaltime += cycdx;
    }
  }

  /* store temp data if needed */
  if (mode == FCM_EXTRAPOLATE_CYCLIC_OFFSET) {
    storage->cycyofs = cycyofs;
  }

  /* return the new frame to evaluate */
  return evaltime;
}

static void fcm_cycles_evaluate(FCurve *UNUSED(fcu),
                                FModifier *UNUSED(fcm),
                                float *cvalue,
                                float UNUSED(evaltime),
                                void *storage_)
{
  tFCMED_Cycles *storage = storage_;
  *cvalue += storage->cycyofs;
}

static FModifierTypeInfo FMI_CYCLES = {
    FMODIFIER_TYPE_CYCLES,      /* type */
    sizeof(FMod_Cycles),        /* size */
    FMI_TYPE_EXTRAPOLATION,     /* action type */
    FMI_REQUIRES_ORIGINAL_DATA, /* requirements */
    N_("Cycles"),               /* name */
    "FMod_Cycles",              /* struct name */
    sizeof(tFCMED_Cycles),      /* storage size */
    NULL,                       /* free data */
    NULL,                       /* copy data */
    fcm_cycles_new_data,        /* new data */
    NULL /*fcm_cycles_verify*/, /* verify */
    fcm_cycles_time,            /* evaluate time */
    fcm_cycles_evaluate,        /* evaluate */
};

/* Noise F-Curve Modifier  --------------------------- */

static void fcm_noise_new_data(void *mdata)
{
  FMod_Noise *data = (FMod_Noise *)mdata;

  /* defaults */
  data->size = 1.0f;
  data->strength = 1.0f;
  data->phase = 1.0f;
  data->offset = 0.0f;
  data->depth = 0;
  data->modification = FCM_NOISE_MODIF_REPLACE;
}

static void fcm_noise_evaluate(
    FCurve *UNUSED(fcu), FModifier *fcm, float *cvalue, float evaltime, void *UNUSED(storage))
{
  FMod_Noise *data = (FMod_Noise *)fcm->data;
  float noise;

  /* generate noise using good old Blender Noise
   * - 0.1 is passed as the 'z' value, otherwise evaluation fails for size = phase = 1
   *   with evaltime being an integer (which happens when evaluating on frame by frame basis)
   */
  noise = BLI_noise_turbulence(
      data->size, evaltime - data->offset, data->phase, 0.1f, data->depth);

  /* combine the noise with existing motion data */
  switch (data->modification) {
    case FCM_NOISE_MODIF_ADD:
      *cvalue = *cvalue + noise * data->strength;
      break;
    case FCM_NOISE_MODIF_SUBTRACT:
      *cvalue = *cvalue - noise * data->strength;
      break;
    case FCM_NOISE_MODIF_MULTIPLY:
      *cvalue = *cvalue * noise * data->strength;
      break;
    case FCM_NOISE_MODIF_REPLACE:
    default:
      *cvalue = *cvalue + (noise - 0.5f) * data->strength;
      break;
  }
}

static FModifierTypeInfo FMI_NOISE = {
    FMODIFIER_TYPE_NOISE,      /* type */
    sizeof(FMod_Noise),        /* size */
    FMI_TYPE_REPLACE_VALUES,   /* action type */
    0,                         /* requirements */
    N_("Noise"),               /* name */
    "FMod_Noise",              /* struct name */
    0,                         /* storage size */
    NULL,                      /* free data */
    NULL,                      /* copy data */
    fcm_noise_new_data,        /* new data */
    NULL /*fcm_noise_verify*/, /* verify */
    NULL,                      /* evaluate time */
    fcm_noise_evaluate,        /* evaluate */
};

/* Python F-Curve Modifier --------------------------- */

static void fcm_python_free(FModifier *fcm)
{
  FMod_Python *data = (FMod_Python *)fcm->data;

  /* id-properties */
  IDP_FreeProperty(data->prop);
}

static void fcm_python_new_data(void *mdata)
{
  FMod_Python *data = (FMod_Python *)mdata;

  /* everything should be set correctly by calloc, except for the prop->type constant.*/
  data->prop = MEM_callocN(sizeof(IDProperty), "PyFModifierProps");
  data->prop->type = IDP_GROUP;
}

static void fcm_python_copy(FModifier *fcm, const FModifier *src)
{
  FMod_Python *pymod = (FMod_Python *)fcm->data;
  FMod_Python *opymod = (FMod_Python *)src->data;

  pymod->prop = IDP_CopyProperty(opymod->prop);
}

static void fcm_python_evaluate(FCurve *UNUSED(fcu),
                                FModifier *UNUSED(fcm),
                                float *UNUSED(cvalue),
                                float UNUSED(evaltime),
                                void *UNUSED(storage))
{
#ifdef WITH_PYTHON
  // FMod_Python *data = (FMod_Python *)fcm->data;

  /* FIXME... need to implement this modifier...
   * It will need it execute a script using the custom properties
   */
#endif /* WITH_PYTHON */
}

static FModifierTypeInfo FMI_PYTHON = {
    FMODIFIER_TYPE_PYTHON,      /* type */
    sizeof(FMod_Python),        /* size */
    FMI_TYPE_GENERATE_CURVE,    /* action type */
    FMI_REQUIRES_RUNTIME_CHECK, /* requirements */
    N_("Python"),               /* name */
    "FMod_Python",              /* struct name */
    0,                          /* storage size */
    fcm_python_free,            /* free data */
    fcm_python_copy,            /* copy data */
    fcm_python_new_data,        /* new data */
    NULL /*fcm_python_verify*/, /* verify */
    NULL /*fcm_python_time*/,   /* evaluate time */
    fcm_python_evaluate,        /* evaluate */
};

/* Limits F-Curve Modifier --------------------------- */

static float fcm_limits_time(FCurve *UNUSED(fcu),
                             FModifier *fcm,
                             float UNUSED(cvalue),
                             float evaltime,
                             void *UNUSED(storage))
{
  FMod_Limits *data = (FMod_Limits *)fcm->data;

  /* check for the time limits */
  if ((data->flag & FCM_LIMIT_XMIN) && (evaltime < data->rect.xmin)) {
    return data->rect.xmin;
  }
  if ((data->flag & FCM_LIMIT_XMAX) && (evaltime > data->rect.xmax)) {
    return data->rect.xmax;
  }

  /* modifier doesn't change time */
  return evaltime;
}

static void fcm_limits_evaluate(FCurve *UNUSED(fcu),
                                FModifier *fcm,
                                float *cvalue,
                                float UNUSED(evaltime),
                                void *UNUSED(storage))
{
  FMod_Limits *data = (FMod_Limits *)fcm->data;

  /* value limits now */
  if ((data->flag & FCM_LIMIT_YMIN) && (*cvalue < data->rect.ymin)) {
    *cvalue = data->rect.ymin;
  }
  if ((data->flag & FCM_LIMIT_YMAX) && (*cvalue > data->rect.ymax)) {
    *cvalue = data->rect.ymax;
  }
}

static FModifierTypeInfo FMI_LIMITS = {
    FMODIFIER_TYPE_LIMITS, /* type */
    sizeof(FMod_Limits),   /* size */
    FMI_TYPE_GENERATE_CURVE,
    /* action type */           /* XXX... err... */
    FMI_REQUIRES_RUNTIME_CHECK, /* requirements */
    N_("Limits"),               /* name */
    "FMod_Limits",              /* struct name */
    0,                          /* storage size */
    NULL,                       /* free data */
    NULL,                       /* copy data */
    NULL,                       /* new data */
    NULL,                       /* verify */
    fcm_limits_time,            /* evaluate time */
    fcm_limits_evaluate,        /* evaluate */
};

/* Stepped F-Curve Modifier --------------------------- */

static void fcm_stepped_new_data(void *mdata)
{
  FMod_Stepped *data = (FMod_Stepped *)mdata;

  /* just need to set the step-size to 2-frames by default */
  /* XXX: or would 5 be more normal? */
  data->step_size = 2.0f;
}

static float fcm_stepped_time(FCurve *UNUSED(fcu),
                              FModifier *fcm,
                              float UNUSED(cvalue),
                              float evaltime,
                              void *UNUSED(storage))
{
  FMod_Stepped *data = (FMod_Stepped *)fcm->data;
  int snapblock;

  /* check range clamping to see if we should alter the timing to achieve the desired results */
  if (data->flag & FCM_STEPPED_NO_BEFORE) {
    if (evaltime < data->start_frame) {
      return evaltime;
    }
  }
  if (data->flag & FCM_STEPPED_NO_AFTER) {
    if (evaltime > data->end_frame) {
      return evaltime;
    }
  }

  /* we snap to the start of the previous closest block of 'step_size' frames
   * after the start offset has been discarded
   * - i.e. round down
   */
  snapblock = (int)((evaltime - data->offset) / data->step_size);

  /* reapply the offset, and multiple the snapblock by the size of the steps to get
   * the new time to evaluate at
   */
  return ((float)snapblock * data->step_size) + data->offset;
}

static FModifierTypeInfo FMI_STEPPED = {
    FMODIFIER_TYPE_STEPPED, /* type */
    sizeof(FMod_Limits),    /* size */
    FMI_TYPE_GENERATE_CURVE,
    /* action type */           /* XXX... err... */
    FMI_REQUIRES_RUNTIME_CHECK, /* requirements */
    N_("Stepped"),              /* name */
    "FMod_Stepped",             /* struct name */
    0,                          /* storage size */
    NULL,                       /* free data */
    NULL,                       /* copy data */
    fcm_stepped_new_data,       /* new data */
    NULL,                       /* verify */
    fcm_stepped_time,           /* evaluate time */
    NULL,                       /* evaluate */
};

/** \} */

/* -------------------------------------------------------------------- */
/** \name F-Curve Modifier Type API
 *
 * all of the f-curve modifier api functions use #fmodifiertypeinfo structs to carry out
 * and operations that involve f-curve modifier specific code.
 * \{ */

/* These globals only ever get directly accessed in this file */
static FModifierTypeInfo *fmodifiersTypeInfo[FMODIFIER_NUM_TYPES];
static short FMI_INIT = 1; /* when non-zero, the list needs to be updated */

/** This function only gets called when #FMI_INIT is non-zero. */
static void fmods_init_typeinfo(void)
{
  fmodifiersTypeInfo[0] = NULL;              /* 'Null' F-Curve Modifier */
  fmodifiersTypeInfo[1] = &FMI_GENERATOR;    /* Generator F-Curve Modifier */
  fmodifiersTypeInfo[2] = &FMI_FN_GENERATOR; /* Built-In Function Generator F-Curve Modifier */
  fmodifiersTypeInfo[3] = &FMI_ENVELOPE;     /* Envelope F-Curve Modifier */
  fmodifiersTypeInfo[4] = &FMI_CYCLES;       /* Cycles F-Curve Modifier */
  fmodifiersTypeInfo[5] = &FMI_NOISE;        /* Apply-Noise F-Curve Modifier */
  fmodifiersTypeInfo[6] = NULL /*&FMI_FILTER*/;
  /* Filter F-Curve Modifier */         /* XXX unimplemented. */
  fmodifiersTypeInfo[7] = &FMI_PYTHON;  /* Custom Python F-Curve Modifier */
  fmodifiersTypeInfo[8] = &FMI_LIMITS;  /* Limits F-Curve Modifier */
  fmodifiersTypeInfo[9] = &FMI_STEPPED; /* Stepped F-Curve Modifier */
}

/**
 * This function should be used for getting the appropriate type-info when only
 * a F-Curve modifier type is known.
 */
const FModifierTypeInfo *get_fmodifier_typeinfo(const int type)
{
  /* initialize the type-info list? */
  if (FMI_INIT) {
    fmods_init_typeinfo();
    FMI_INIT = 0;
  }

  /* only return for valid types */
  if ((type >= FMODIFIER_TYPE_NULL) && (type < FMODIFIER_NUM_TYPES)) {
    /* there shouldn't be any segfaults here... */
    return fmodifiersTypeInfo[type];
  }

  CLOG_ERROR(&LOG, "No valid F-Curve Modifier type-info data available. Type = %i", type);

  return NULL;
}

/**
 * This function should always be used to get the appropriate type-info,
 * as it has checks which prevent segfaults in some weird cases.
 */
const FModifierTypeInfo *fmodifier_get_typeinfo(const FModifier *fcm)
{
  /* only return typeinfo for valid modifiers */
  if (fcm) {
    return get_fmodifier_typeinfo(fcm->type);
  }

  return NULL;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name F-Curve Modifier Public API
 * \{ */

/**
 * Add a new F-Curve Modifier to the given F-Curve of a certain type.
 */
FModifier *add_fmodifier(ListBase *modifiers, int type, FCurve *owner_fcu)
{
  const FModifierTypeInfo *fmi = get_fmodifier_typeinfo(type);
  FModifier *fcm;

  /* sanity checks */
  if (ELEM(NULL, modifiers, fmi)) {
    return NULL;
  }

  /* special checks for whether modifier can be added */
  if ((modifiers->first) && (type == FMODIFIER_TYPE_CYCLES)) {
    /* cycles modifier must be first in stack, so for now, don't add if it can't be */
    /* TODO: perhaps there is some better way, but for now, */
    CLOG_STR_ERROR(&LOG,
                   "Cannot add 'Cycles' modifier to F-Curve, as 'Cycles' modifier can only be "
                   "first in stack.");
    return NULL;
  }

  /* add modifier itself */
  fcm = MEM_callocN(sizeof(FModifier), "F-Curve Modifier");
  fcm->type = type;
  fcm->ui_expand_flag = UI_PANEL_DATA_EXPAND_ROOT; /* Expand the main panel, not the sub-panels. */
  fcm->curve = owner_fcu;
  fcm->influence = 1.0f;
  BLI_addtail(modifiers, fcm);

  /* tag modifier as "active" if no other modifiers exist in the stack yet */
  if (BLI_listbase_is_single(modifiers)) {
    fcm->flag |= FMODIFIER_FLAG_ACTIVE;
  }

  /* add modifier's data */
  fcm->data = MEM_callocN(fmi->size, fmi->structName);

  /* init custom settings if necessary */
  if (fmi->new_data) {
    fmi->new_data(fcm->data);
  }

  /* update the fcurve if the Cycles modifier is added */
  if ((owner_fcu) && (type == FMODIFIER_TYPE_CYCLES)) {
    calchandles_fcurve(owner_fcu);
  }

  /* return modifier for further editing */
  return fcm;
}

/**
 * Make a copy of the specified F-Modifier.
 */
FModifier *copy_fmodifier(const FModifier *src)
{
  const FModifierTypeInfo *fmi = fmodifier_get_typeinfo(src);
  FModifier *dst;

  /* sanity check */
  if (src == NULL) {
    return NULL;
  }

  /* copy the base data, clearing the links */
  dst = MEM_dupallocN(src);
  dst->next = dst->prev = NULL;
  dst->curve = NULL;

  /* make a new copy of the F-Modifier's data */
  dst->data = MEM_dupallocN(src->data);

  /* only do specific constraints if required */
  if (fmi && fmi->copy_data) {
    fmi->copy_data(dst, src);
  }

  /* return the new modifier */
  return dst;
}

/**
 * Duplicate all of the F-Modifiers in the Modifier stacks.
 */
void copy_fmodifiers(ListBase *dst, const ListBase *src)
{
  FModifier *fcm, *srcfcm;

  if (ELEM(NULL, dst, src)) {
    return;
  }

  BLI_listbase_clear(dst);
  BLI_duplicatelist(dst, src);

  for (fcm = dst->first, srcfcm = src->first; fcm && srcfcm;
       srcfcm = srcfcm->next, fcm = fcm->next) {
    const FModifierTypeInfo *fmi = fmodifier_get_typeinfo(fcm);

    /* make a new copy of the F-Modifier's data */
    fcm->data = MEM_dupallocN(fcm->data);
    fcm->curve = NULL;

    /* only do specific constraints if required */
    if (fmi && fmi->copy_data) {
      fmi->copy_data(fcm, srcfcm);
    }
  }
}

/**
 * Remove and free the given F-Modifier from the given stack.
 */
bool remove_fmodifier(ListBase *modifiers, FModifier *fcm)
{
  const FModifierTypeInfo *fmi = fmodifier_get_typeinfo(fcm);

  /* sanity check */
  if (fcm == NULL) {
    return false;
  }

  /* removing the cycles modifier requires a handle update */
  FCurve *update_fcu = (fcm->type == FMODIFIER_TYPE_CYCLES) ? fcm->curve : NULL;

  /* free modifier's special data (stored inside fcm->data) */
  if (fcm->data) {
    if (fmi && fmi->free_data) {
      fmi->free_data(fcm);
    }

    /* free modifier's data (fcm->data) */
    MEM_freeN(fcm->data);
  }

  /* remove modifier from stack */
  if (modifiers) {
    BLI_freelinkN(modifiers, fcm);

    /* update the fcurve if the Cycles modifier is removed */
    if (update_fcu) {
      calchandles_fcurve(update_fcu);
    }

    return true;
  }

  /* XXX this case can probably be removed some day, as it shouldn't happen... */
  CLOG_STR_ERROR(&LOG, "no modifier stack given");
  MEM_freeN(fcm);
  return false;
}

/**
 * Remove all of a given F-Curve's modifiers.
 */
void free_fmodifiers(ListBase *modifiers)
{
  FModifier *fcm, *fmn;

  /* sanity check */
  if (modifiers == NULL) {
    return;
  }

  /* free each modifier in order - modifier is unlinked from list and freed */
  for (fcm = modifiers->first; fcm; fcm = fmn) {
    fmn = fcm->next;
    remove_fmodifier(modifiers, fcm);
  }
}

/**
 * Find the active F-Modifier.
 */
FModifier *find_active_fmodifier(ListBase *modifiers)
{
  FModifier *fcm;

  /* sanity checks */
  if (ELEM(NULL, modifiers, modifiers->first)) {
    return NULL;
  }

  /* loop over modifiers until 'active' one is found */
  for (fcm = modifiers->first; fcm; fcm = fcm->next) {
    if (fcm->flag & FMODIFIER_FLAG_ACTIVE) {
      return fcm;
    }
  }

  /* no modifier is active */
  return NULL;
}

/**
 * Set the active F-Modifier.
 */
void set_active_fmodifier(ListBase *modifiers, FModifier *fcm)
{
  FModifier *fm;

  /* sanity checks */
  if (ELEM(NULL, modifiers, modifiers->first)) {
    return;
  }

  /* deactivate all, and set current one active */
  for (fm = modifiers->first; fm; fm = fm->next) {
    fm->flag &= ~FMODIFIER_FLAG_ACTIVE;
  }

  /* make given modifier active */
  if (fcm) {
    fcm->flag |= FMODIFIER_FLAG_ACTIVE;
  }
}

/**
 * Do we have any modifiers which match certain criteria.
 *
 * \param mtype: Type of modifier (if 0, doesn't matter).
 * \param acttype: Type of action to perform (if -1, doesn't matter).
 */
bool list_has_suitable_fmodifier(ListBase *modifiers, int mtype, short acttype)
{
  FModifier *fcm;

  /* if there are no specific filtering criteria, just skip */
  if ((mtype == 0) && (acttype == 0)) {
    return (modifiers && modifiers->first);
  }

  /* sanity checks */
  if (ELEM(NULL, modifiers, modifiers->first)) {
    return false;
  }

  /* Find the first modifier fitting these criteria. */
  for (fcm = modifiers->first; fcm; fcm = fcm->next) {
    const FModifierTypeInfo *fmi = fmodifier_get_typeinfo(fcm);
    short mOk = 1, aOk = 1; /* by default 1, so that when only one test, won't fail */

    /* check if applicable ones are fulfilled */
    if (mtype) {
      mOk = (fcm->type == mtype);
    }
    if (acttype > -1) {
      aOk = (fmi->acttype == acttype);
    }

    /* if both are ok, we've found a hit */
    if (mOk && aOk) {
      return true;
    }
  }

  /* no matches */
  return false;
}

/* Evaluation API --------------------------- */

uint evaluate_fmodifiers_storage_size_per_modifier(ListBase *modifiers)
{
  /* Sanity checks. */
  if (ELEM(NULL, modifiers, modifiers->first)) {
    return 0;
  }

  uint max_size = 0;

  LISTBASE_FOREACH (FModifier *, fcm, modifiers) {
    const FModifierTypeInfo *fmi = fmodifier_get_typeinfo(fcm);

    if (fmi == NULL) {
      continue;
    }

    max_size = MAX2(max_size, fmi->storage_size);
  }

  return max_size;
}

/**
 * Helper function - calculate influence of #FModifier.
 */
static float eval_fmodifier_influence(FModifier *fcm, float evaltime)
{
  float influence;

  /* sanity check */
  if (fcm == NULL) {
    return 0.0f;
  }

  /* should we use influence stored in modifier or not
   * NOTE: this is really just a hack so that we don't need to version patch old files ;)
   */
  if (fcm->flag & FMODIFIER_FLAG_USEINFLUENCE) {
    influence = fcm->influence;
  }
  else {
    influence = 1.0f;
  }

  /* restricted range or full range? */
  if (fcm->flag & FMODIFIER_FLAG_RANGERESTRICT) {
    if ((evaltime <= fcm->sfra) || (evaltime >= fcm->efra)) {
      /* out of range */
      return 0.0f;
    }
    if ((evaltime > fcm->sfra) && (evaltime < fcm->sfra + fcm->blendin)) {
      /* blend in range */
      float a = fcm->sfra;
      float b = fcm->sfra + fcm->blendin;
      return influence * (evaltime - a) / (b - a);
    }
    if ((evaltime < fcm->efra) && (evaltime > fcm->efra - fcm->blendout)) {
      /* blend out range */
      float a = fcm->efra;
      float b = fcm->efra - fcm->blendout;
      return influence * (evaltime - a) / (b - a);
    }
  }

  /* just return the influence of the modifier */
  return influence;
}

/**
 * Evaluate time modifications imposed by some F-Curve Modifiers.
 *
 * - This step acts as an optimization to prevent the F-Curve stack being evaluated
 *   several times by modifiers requesting the time be modified, as the final result
 *   would have required using the modified time
 * - Modifiers only ever receive the unmodified time, as subsequent modifiers should be
 *   working on the 'global' result of the modified curve, not some localized segment,
 *   so \a evaltime gets set to whatever the last time-modifying modifier likes.
 * - We start from the end of the stack, as only the last one matters for now.
 *
 * \param fcu: Can be NULL.
 */
float evaluate_time_fmodifiers(FModifiersStackStorage *storage,
                               ListBase *modifiers,
                               FCurve *fcu,
                               float cvalue,
                               float evaltime)
{
  /* sanity checks */
  if (ELEM(NULL, modifiers, modifiers->last)) {
    return evaltime;
  }

  if (fcu && fcu->flag & FCURVE_MOD_OFF) {
    return evaltime;
  }

  /* Starting from the end of the stack, calculate the time effects of various stacked modifiers
   * on the time the F-Curve should be evaluated at.
   *
   * This is done in reverse order to standard evaluation, as when this is done in standard
   * order, each modifier would cause jumps to other points in the curve, forcing all
   * previous ones to be evaluated again for them to be correct. However, if we did in the
   * reverse order as we have here, we can consider them a macro to micro type of waterfall
   * effect, which should get us the desired effects when using layered time manipulations
   * (such as multiple 'stepped' modifiers in sequence, causing different stepping rates)
   */
  uint fcm_index = storage->modifier_count - 1;
  for (FModifier *fcm = modifiers->last; fcm; fcm = fcm->prev, fcm_index--) {
    const FModifierTypeInfo *fmi = fmodifier_get_typeinfo(fcm);

    if (fmi == NULL) {
      continue;
    }

    /* If modifier cannot be applied on this frame
     * (whatever scale it is on, it won't affect the results)
     * hence we shouldn't bother seeing what it would do given the chance. */
    if ((fcm->flag & FMODIFIER_FLAG_RANGERESTRICT) == 0 ||
        ((fcm->sfra <= evaltime) && (fcm->efra >= evaltime))) {
      /* only evaluate if there's a callback for this */
      if (fmi->evaluate_modifier_time) {
        if ((fcm->flag & (FMODIFIER_FLAG_DISABLED | FMODIFIER_FLAG_MUTED)) == 0) {
          void *storage_ptr = POINTER_OFFSET(storage->buffer,
                                             fcm_index * storage->size_per_modifier);

          float nval = fmi->evaluate_modifier_time(fcu, fcm, cvalue, evaltime, storage_ptr);

          float influence = eval_fmodifier_influence(fcm, evaltime);
          evaltime = interpf(nval, evaltime, influence);
        }
      }
    }
  }

  /* return the modified evaltime */
  return evaltime;
}

/**
 * Evaluates the given set of F-Curve Modifiers using the given data
 * Should only be called after evaluate_time_fmodifiers() has been called.
 */
void evaluate_value_fmodifiers(FModifiersStackStorage *storage,
                               ListBase *modifiers,
                               FCurve *fcu,
                               float *cvalue,
                               float evaltime)
{
  FModifier *fcm;

  /* sanity checks */
  if (ELEM(NULL, modifiers, modifiers->first)) {
    return;
  }

  if (fcu->flag & FCURVE_MOD_OFF) {
    return;
  }

  /* evaluate modifiers */
  uint fcm_index = 0;
  for (fcm = modifiers->first; fcm; fcm = fcm->next, fcm_index++) {
    const FModifierTypeInfo *fmi = fmodifier_get_typeinfo(fcm);

    if (fmi == NULL) {
      continue;
    }

    /* Only evaluate if there's a callback for this,
     * and if F-Modifier can be evaluated on this frame. */
    if ((fcm->flag & FMODIFIER_FLAG_RANGERESTRICT) == 0 ||
        ((fcm->sfra <= evaltime) && (fcm->efra >= evaltime))) {
      if (fmi->evaluate_modifier) {
        if ((fcm->flag & (FMODIFIER_FLAG_DISABLED | FMODIFIER_FLAG_MUTED)) == 0) {
          void *storage_ptr = POINTER_OFFSET(storage->buffer,
                                             fcm_index * storage->size_per_modifier);

          float nval = *cvalue;
          fmi->evaluate_modifier(fcu, fcm, &nval, evaltime, storage_ptr);

          float influence = eval_fmodifier_influence(fcm, evaltime);
          *cvalue = interpf(nval, *cvalue, influence);
        }
      }
    }
  }
}

/* ---------- */

/**
 * Bake modifiers for given F-Curve to curve sample data, in the frame range defined
 * by start and end (inclusive).
 */
void fcurve_bake_modifiers(FCurve *fcu, int start, int end)
{
  ChannelDriver *driver;

  /* sanity checks */
  /* TODO: make these tests report errors using reports not CLOG's */
  if (ELEM(NULL, fcu, fcu->modifiers.first)) {
    CLOG_ERROR(&LOG, "No F-Curve with F-Curve Modifiers to Bake");
    return;
  }

  /* temporarily, disable driver while we sample, so that they don't influence the outcome */
  driver = fcu->driver;
  fcu->driver = NULL;

  /* bake the modifiers, by sampling the curve at each frame */
  fcurve_store_samples(fcu, NULL, start, end, fcurve_samplingcb_evalcurve);

  /* free the modifiers now */
  free_fmodifiers(&fcu->modifiers);

  /* restore driver */
  fcu->driver = driver;
}

/** \} */
