/*
    SPDX-FileCopyrightText: 2003-2010 Mark Borgerding <Mark@Borgerding.net>
    SPDX-License-Identifier: BSD-3-Clause
*/

#pragma once

/* kiss_fft.h
   defines kiss_fft_scalar as either short or a float type
   and defines
   typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */

#include "kiss_fft.h"
#include "kiss_fft_log.h"
#include <limits.h>

#define MAXFACTORS 32
/* e.g. an fft of length 128 has 4 factors
 as far as kissfft is concerned
 4*4*4*2
 */

struct kiss_fft_state
{
    int nfft;
    int inverse;
    int factors[2 * MAXFACTORS];
    kiss_fft_cpx twiddles[1];
};

/*
  Explanation of macros dealing with complex math:

   C_MUL(m,a,b)         : m = a*b
   C_FIXDIV( c , div )  : if a fixed point impl., c /= div. noop otherwise
   C_SUB( res, a,b)     : res = a - b
   C_SUBFROM( res , a)  : res -= a
   C_ADDTO( res , a)    : res += a
 * */
#ifdef FIXED_POINT
#include <stdint.h>
#if (FIXED_POINT == 32)
#define FRACBITS 31
#define SAMPPROD int64_t
#define SAMP_MAX INT32_MAX
#define SAMP_MIN INT32_MIN
#else
#define FRACBITS 15
#define SAMPPROD int32_t
#define SAMP_MAX INT16_MAX
#define SAMP_MIN INT16_MIN
#endif

#if defined(CHECK_OVERFLOW)
#define CHECK_OVERFLOW_OP(a, op, b)                                                                                                                            \
    if ((SAMPPROD)(a)op(SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a)op(SAMPPROD)(b) < SAMP_MIN) {                                                                  \
        KISS_FFT_WARNING("overflow (%d " #op " %d) = %ld", (a), (b), (SAMPPROD)(a)op(SAMPPROD)(b));                                                            \
    }
#endif

#define smul(a, b) ((SAMPPROD)(a) * (b))
#define sround(x) (kiss_fft_scalar)(((x) + (1 << (FRACBITS - 1))) >> FRACBITS)

#define S_MUL(a, b) sround(smul(a, b))

#define C_MUL(m, a, b)                                                                                                                                         \
    do {                                                                                                                                                       \
        (m).r = sround(smul((a).r, (b).r) - smul((a).i, (b).i));                                                                                               \
        (m).i = sround(smul((a).r, (b).i) + smul((a).i, (b).r));                                                                                               \
    } while (0)

#define DIVSCALAR(x, k) (x) = sround(smul(x, SAMP_MAX / k))

#define C_FIXDIV(c, div)                                                                                                                                       \
    do {                                                                                                                                                       \
        DIVSCALAR((c).r, div);                                                                                                                                 \
        DIVSCALAR((c).i, div);                                                                                                                                 \
    } while (0)

#define C_MULBYSCALAR(c, s)                                                                                                                                    \
    do {                                                                                                                                                       \
        (c).r = sround(smul((c).r, s));                                                                                                                        \
        (c).i = sround(smul((c).i, s));                                                                                                                        \
    } while (0)

#else /* not FIXED_POINT*/

#define S_MUL(a, b) ((a) * (b))
#define C_MUL(m, a, b)                                                                                                                                         \
    do {                                                                                                                                                       \
        (m).r = (a).r * (b).r - (a).i * (b).i;                                                                                                                 \
        (m).i = (a).r * (b).i + (a).i * (b).r;                                                                                                                 \
    } while (0)
#define C_FIXDIV(c, div) /* NOOP */
#define C_MULBYSCALAR(c, s)                                                                                                                                    \
    do {                                                                                                                                                       \
        (c).r *= (s);                                                                                                                                          \
        (c).i *= (s);                                                                                                                                          \
    } while (0)
#endif

#ifndef CHECK_OVERFLOW_OP
#define CHECK_OVERFLOW_OP(a, op, b) /* noop */
#endif

#define C_ADD(res, a, b)                                                                                                                                       \
    do {                                                                                                                                                       \
        CHECK_OVERFLOW_OP((a).r, +, (b).r)                                                                                                                     \
        CHECK_OVERFLOW_OP((a).i, +, (b).i)                                                                                                                     \
        (res).r = (a).r + (b).r;                                                                                                                               \
        (res).i = (a).i + (b).i;                                                                                                                               \
    } while (0)
#define C_SUB(res, a, b)                                                                                                                                       \
    do {                                                                                                                                                       \
        CHECK_OVERFLOW_OP((a).r, -, (b).r)                                                                                                                     \
        CHECK_OVERFLOW_OP((a).i, -, (b).i)                                                                                                                     \
        (res).r = (a).r - (b).r;                                                                                                                               \
        (res).i = (a).i - (b).i;                                                                                                                               \
    } while (0)
#define C_ADDTO(res, a)                                                                                                                                        \
    do {                                                                                                                                                       \
        CHECK_OVERFLOW_OP((res).r, +, (a).r)                                                                                                                   \
        CHECK_OVERFLOW_OP((res).i, +, (a).i)                                                                                                                   \
        (res).r += (a).r;                                                                                                                                      \
        (res).i += (a).i;                                                                                                                                      \
    } while (0)

#define C_SUBFROM(res, a)                                                                                                                                      \
    do {                                                                                                                                                       \
        CHECK_OVERFLOW_OP((res).r, -, (a).r)                                                                                                                   \
        CHECK_OVERFLOW_OP((res).i, -, (a).i)                                                                                                                   \
        (res).r -= (a).r;                                                                                                                                      \
        (res).i -= (a).i;                                                                                                                                      \
    } while (0)

#ifdef FIXED_POINT
#define KISS_FFT_COS(phase) floor(.5 + SAMP_MAX * cos(phase))
#define KISS_FFT_SIN(phase) floor(.5 + SAMP_MAX * sin(phase))
#define HALF_OF(x) ((x) >> 1)
#elif defined(USE_SIMD)
#define KISS_FFT_COS(phase) _mm_set1_ps(cos(phase))
#define KISS_FFT_SIN(phase) _mm_set1_ps(sin(phase))
#define HALF_OF(x) ((x)*_mm_set1_ps(.5))
#else
#define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase)
#define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase)
#define HALF_OF(x) ((x) * ((kiss_fft_scalar).5))
#endif

#define kf_cexp(x, phase)                                                                                                                                      \
    do {                                                                                                                                                       \
        (x)->r = KISS_FFT_COS(phase);                                                                                                                          \
        (x)->i = KISS_FFT_SIN(phase);                                                                                                                          \
    } while (0)

/* a debugging function */
#define pcpx(c) KISS_FFT_DEBUG("%g + %gi\n", (double)((c)->r), (double)((c)->i))

#ifdef KISS_FFT_USE_ALLOCA
// define this to allow use of alloca instead of malloc for temporary buffers
// Temporary buffers are used in two case:
// 1. FFT sizes that have "bad" factors. i.e. not 2,3 and 5
// 2. "in-place" FFTs.  Notice the quotes, since kissfft does not really do an in-place transform.
#include <alloca.h>
#define KISS_FFT_TMP_ALLOC(nbytes) alloca(nbytes)
#define KISS_FFT_TMP_FREE(ptr)
#else
#define KISS_FFT_TMP_ALLOC(nbytes) KISS_FFT_MALLOC(nbytes)
#define KISS_FFT_TMP_FREE(ptr) KISS_FFT_FREE(ptr)
#endif
