#include "lmath_window.h"
#include "lmath_internal.h"
#include <stdlib.h>



struct st_lmath_win_ctx {
    double *coefs;
    int size;
    t_lmath_window_params params;
};


typedef double (*t_win_point_func)(int i, int n, void* param);

static double f_rectangle_win_point(int i, int n, void *param);
static double f_hanning_win_point(int i, int n, void *param);
static double f_hamming_win_point(int i, int n, void *param);
static double f_exact_blkm_win_point(int i, int n, void *param);
static double f_blkm_win_point(int i, int n, void *param);
static double f_bh4_win_point(int i, int n, void *param);
static double f_bh7_win_point(int i, int n, void *param);
static double f_blknut_win_point(int i, int n, void *param);

typedef struct {
    int span;
    t_win_point_func ptfunc;
} t_win_type_params;

static const t_win_type_params f_win_types[] = {
    { 9, f_rectangle_win_point },
    { 3, f_hanning_win_point },
    { 3, f_hamming_win_point },
    { 0, NULL},
    { 5, f_exact_blkm_win_point},
    { 5, f_blkm_win_point},
    { 0, NULL},
    { 7, f_bh4_win_point},
    { 13, f_bh7_win_point},
    { 0, NULL},
    { 0, NULL},
    { 7, f_blknut_win_point}
};



static double f_rectangle_win_point(int i, int n, void *param) {
    return 1.;
}


static double f_hanning_win_point(int i, int n, void *param) {
    const double a = (2.*M_PI*i)/n;
    return 0.5 * (1 - cos(a));
}

static double f_hamming_win_point(int i, int n, void *param) {
    const double a = (2.*M_PI*i)/n;
    return 0.54-0.46*cos(a);
}


static double f_exact_blkm_win_point(int i, int n, void *param) {
    static const double a0 = 7938./18608;
    static const double a1 = 9240./18608;
    static const double a2 = 1430./18608;

    const double a = (2.*M_PI*i)/n;
    return a0 - a1*cos(a) + a2*cos(2*a);
}

static double f_blkm_win_point(int i, int n, void *param) {
    static const double a0 = 0.42;
    static const double a1 = 0.5;
    static const double a2 = 0.08;

    const double a = (2.*M_PI*i)/n;
    return a0 - a1*cos(a) + a2*cos(2*a);
}


static double f_blknut_win_point(int i, int n, void *param) {
    static const double a0 = 0.3635819;
    static const double a1 = 0.4891775;
    static const double a2 = 0.1365995;
    static const double a3 = 0.0106411;
    double a = (2.*M_PI*i)/n;
    return a0 - a1*cos(a) + a2*cos(2*a) - a3*cos(3*a);
}


static double f_bh4_win_point(int i, int n, void *param) {
    static const double a0 = 0.35875;
    static const double a1 = 0.48829;
    static const double a2 = 0.14128;
    static const double a3 = 0.01168;
    double a = (2.*M_PI*i)/n;
    return a0 - a1*cos(a) + a2*cos(2*a) - a3*cos(3*a);
}


static double f_bh7_win_point(int i, int n, void *param) {
    static const double a0 = 0.27105140069342415;
    static const double a1 = 0.433297939234486060;
    static const double a2 = 0.218122999543110620;
    static const double a3 = 0.065925446388030898;
    static const double a4 = 0.010811742098372268;
    static const double a5 = 7.7658482522509342E-4;
    static const double a6 = 1.3887217350903198E-5;
    double a = (2.*M_PI*i)/n;
    return a0 - a1*cos(a) + a2*cos(2*a) - a3*cos(3*a) + a4*cos(4*a) - a5*cos(5*a) + a6*cos(6*a);
}

t_lmath_window_ctx lmath_window_ctx_create(t_lmath_wintype wintype, void *param, int size) {
    t_lmath_window_ctx ctx = NULL;
    if ((wintype < (int)(sizeof(f_win_types)/sizeof(f_win_types[0]))) &&
            (f_win_types[wintype].ptfunc != NULL)) {
        ctx = (t_lmath_window_ctx)malloc(sizeof(*ctx));
        if (ctx != NULL) {
            ctx->size = size;
            ctx->coefs = (double*)malloc((unsigned)size*sizeof(ctx->coefs[0]));
            if (ctx->coefs == NULL) {
                free(ctx);
                ctx = NULL;
            } else {
                double sum =0.0;
                double sum2 = 0;
                int i;
                const t_win_type_params *wintype_params = &f_win_types[wintype];
                t_win_point_func func = wintype_params->ptfunc;
                for (i=0; i < size; i++) {
                    double val = func(i, size, param);
                    sum+=val;
                    sum2 += (val*val);
                    ctx->coefs[i] = val;
                }

                ctx->params.coherent_gain_sqrt =  sum/size;
                ctx->params.coherent_gain = ctx->params.coherent_gain_sqrt * ctx->params.coherent_gain_sqrt;
                ctx->params.inherent_gain = sum2/size;
                ctx->params.enbw = ctx->params.inherent_gain/ctx->params.coherent_gain;
                ctx->params.freq_span = wintype_params->span;
            }
        }
    }
    return ctx;
}

void lmath_window_ctx_destroy(t_lmath_window_ctx ctx) {
    if (ctx) {
        free(ctx->coefs);
        free(ctx);
    }
}

int lmath_window_size(const t_lmath_window_ctx winctx) {
    return winctx->size;
}

double lmath_window_inherent_gain(const t_lmath_window_ctx winctx) {
    return winctx->params.inherent_gain;
}

double lmath_window_coherent_gain(const t_lmath_window_ctx winctx) {
    return winctx->params.coherent_gain;
}

double lmath_window_coherent_gain_sqrt(const t_lmath_window_ctx winctx) {
    return winctx->params.coherent_gain_sqrt;
}


double lmath_window_enbw(const t_lmath_window_ctx winctx) {
    return winctx->params.enbw;
}


t_lmath_errs lmath_window_apply(const double *src, const t_lmath_window_ctx winctx, double *res) {
    int i;
    for (i = 0; i < winctx->size; i++) {
        res[i] = src[i]*winctx->coefs[i];
    }
    return  LMATH_ERR_OK;
}

t_lmath_errs lmath_window_apply_scaled(const double *src, t_lmath_window_ctx winctx, double *res) {
    double del = winctx->params.coherent_gain_sqrt;
    int i;
    for (i = 0; i < winctx->size; i++) {
        res[i] = src[i]*winctx->coefs[i]/del;
    }
    return  LMATH_ERR_OK;
}


int lmath_window_freq_span(const t_lmath_window_ctx winctx) {
    return winctx->params.freq_span;
}


const t_lmath_window_params *lmath_window_params(const t_lmath_window_ctx winctx) {
    return &winctx->params;
}

const double *lmath_window_coefs(const t_lmath_window_ctx winctx) {
    return winctx->coefs;
}
