#ifndef __ACCEL_SYNCHRONOUS_H_
#define __ACCEL_SYNCHRONOUS_H_

#include "../fixedptc.h"

struct synchronous_curve_args {
  fpt gamma;
  fpt smooth;
  fpt motivity;
  fpt sync_speed;
};

/**
 * Sensitivity Function for `Synchronous` Acceleration
 */
static inline fpt __synchronous_sens_fun(fpt input_speed,
                                         struct synchronous_curve_args args) {
  fpt log_motivity = fpt_ln(args.motivity);
  fpt gamma_const = fpt_div(args.gamma, log_motivity);
  fpt log_syncspeed = fpt_ln(args.sync_speed);
  fpt syncspeed = args.sync_speed;
  fpt sharpness = args.smooth == 0 ? fpt_rconst(16.0)
                                   : fpt_div(fpt_rconst(0.5), args.smooth);
  int use_linear_clamp = sharpness >= fpt_rconst(16.0);
  fpt sharpness_recip = fpt_div(FIXEDPT_ONE, sharpness);
  fpt minimum_sens = fpt_div(FIXEDPT_ONE, args.motivity);
  fpt maximum_sens = args.motivity;

  // if sharpness >= 16, use linear clamp for activation function.
  // linear clamp means: fpt_clamp(input_speed, -1, 1).
  if (use_linear_clamp) {
    fpt log_space = fpt_mul(gamma_const, (fpt_ln(input_speed) - log_syncspeed));

    if (log_space < -FIXEDPT_ONE) {
      return minimum_sens;
    }

    if (log_space > FIXEDPT_ONE) {
      return maximum_sens;
    }

    return fpt_exp(fpt_mul(log_space, log_motivity));
  }

  if (input_speed == syncspeed) {
    return FIXEDPT_ONE;
  }

  fpt log_x = fpt_ln(input_speed);
  fpt log_diff = log_x - log_syncspeed;

  if (log_diff > 0) {
    fpt log_space = fpt_mul(gamma_const, log_diff);
    fpt exponent =
        fpt_pow(fpt_tanh(fpt_pow(log_space, sharpness)), sharpness_recip);
    return fpt_exp(fpt_mul(exponent, log_motivity));
  } else {
    fpt log_space = fpt_mul(-gamma_const, log_diff);
    fpt exponent =
        -fpt_pow(fpt_tanh(fpt_pow(log_space, sharpness)), sharpness_recip);
    return fpt_exp(fpt_mul(exponent, log_motivity));
  }
}
#endif
