#include "lmath_fft.h"
#include "lmath_internal.h"
#include "mkl.h"


struct st_lmath_fft_r2c_ctx {
    int size;
    int fft_size;
    double *buf;
    DFTI_DESCRIPTOR_HANDLE hfft;
};

t_lmath_fft_r2c_ctx lmath_fft_r2c_ctx_create(int size, int flags) {
    t_lmath_fft_r2c_ctx ctx = malloc(sizeof(*ctx));
    if (ctx) {
        ctx->size = size;
        ctx->fft_size = size/2 + 1;
        ctx->buf = mkl_malloc(ctx->fft_size * sizeof(MKL_Complex16), 64);
        if (ctx->buf) {
             MKL_LONG status = DftiCreateDescriptor(&ctx->hfft, DFTI_DOUBLE, DFTI_REAL, 1, size);
            if (status == 0) {
                status = DftiSetValue(ctx->hfft, DFTI_CONJUGATE_EVEN_STORAGE,  DFTI_COMPLEX_COMPLEX);
                if (status == 0)
                    status = DftiSetValue(ctx->hfft, DFTI_PLACEMENT,  DFTI_INPLACE);
                if (status == 0) {
                    status = DftiCommitDescriptor(ctx->hfft);
                }
                if (status != 0) {
                    DftiFreeDescriptor(&ctx->hfft);
                }
            }

            if (status != 0) {
                mkl_free(ctx->buf);
                free(ctx);
                ctx = NULL;
            }
        } else {
            free(ctx);
            ctx = NULL;
        }
    }

    return  ctx;
}


void lmath_fft_r2c_ctx_destroy(t_lmath_fft_r2c_ctx ctx) {
    if (ctx) {
        DftiFreeDescriptor(&ctx->hfft);
        mkl_free(ctx->buf);
        free(ctx);
    }
}



double *lmath_fft_r2c_ctx_in_buf(const t_lmath_fft_r2c_ctx ctx) {
    return ctx->buf;
}

int lmath_fft_r2c_ctx_in_size(const t_lmath_fft_r2c_ctx ctx) {
    return ctx->size;
}

int lmath_fft_r2c_ctx_fft_size(const t_lmath_fft_r2c_ctx ctx) {
    return ctx->fft_size;
}

double lmath_fft_r2c_ctx_df(const t_lmath_fft_r2c_ctx ctx, double dt) {
    return 1./(dt*ctx->size);
}

t_lmath_errs lmath_fft_r2c_calc_amp_pha_spectrum_peak(t_lmath_fft_r2c_ctx ctx,
                                                      t_lmath_phase_mode pha_mode,
                                                      double *amp, double *pha) {
    int i;
    DftiComputeForward(ctx->hfft, ctx->buf);
    for (i=0; i < ctx->fft_size; i++) {
        double re = ctx->buf[2*i];
        double im = ctx->buf[2*i + 1];
        if (amp != NULL)
            amp[i] = sqrt(re*re + im * im)*
                     ((i == 0) || (i == (ctx->fft_size-1)) ? 1 : 2)/ctx->size;
        if (pha != NULL)
            pha[i] = lmath_phase_calc(re, im, pha_mode);
    }
    return LMATH_ERR_OK;
}

t_lmath_errs lmath_fft_r2c_calc_amp_pha_spectrum_rms(t_lmath_fft_r2c_ctx ctx,
                                                     t_lmath_phase_mode pha_mode,
                                                     double *amp, double *pha) {
    int ret = lmath_fft_r2c_calc_amp_pha_spectrum_peak(ctx, pha_mode, amp, pha);
    if ((ret == 0) && (amp != NULL)) {
        int i;
        for (i=1; i < ctx->fft_size; i++) {
            amp[i] /= sqrt(2);
        }
    }
    return LMATH_ERR_OK;
}

