/*
 * Copyright (C) 2020 Linux Studio Plugins Project <https://lsp-plug.in/>
 *           (C) 2020 Vladimir Sadovnikov <sadko4u@gmail.com>
 *
 * This file is part of lsp-plugins
 * Created on: 20 февр. 2016 г.
 *
 * lsp-plugins is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * lsp-plugins 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with lsp-plugins. If not, see <https://www.gnu.org/licenses/>.
 */

#include <lsp-plug.in/dsp-units/misc/windows.h>
#include <lsp-plug.in/stdlib/math.h>

namespace lsp
{
    namespace dspu
    {
        namespace windows
        {
            void window(float *dst, size_t n, window_t type)
            {
                switch (type)
                {
                    case HANN: hann(dst, n); break;
                    case HAMMING: hamming(dst, n); break;
                    case BLACKMAN: blackman(dst, n); break;
                    case LANCZOS: lanczos(dst, n); break;
                    case GAUSSIAN: gaussian(dst, n); break;
                    case POISSON: poisson(dst, n); break;
                    case PARZEN: parzen(dst, n); break;
                    case TUKEY: tukey(dst, n); break;
                    case WELCH: welch(dst, n); break;
                    case NUTTALL: nuttall(dst, n); break;
                    case BLACKMAN_NUTTALL: blackman_nuttall(dst, n); break;
                    case BLACKMAN_HARRIS: blackman_harris(dst, n); break;
                    case HANN_POISSON: hann_poisson(dst, n); break;
                    case BARTLETT_HANN: bartlett_hann(dst, n); break;
                    case BARTLETT_FEJER: bartlett_fejer(dst, n); break;
                    case TRIANGULAR: triangular(dst, n); break;
                    case RECTANGULAR: rectangular(dst, n); break;
                    case FLAT_TOP: flat_top(dst, n); break;
                    case COSINE: cosine(dst, n); break;
                    case SQR_COSINE: sqr_cosine(dst, n); break;
                    case CUBIC: cubic(dst, n); break;
                    default:
                        break;
                }
            }

            void rectangular(float *dst, size_t n)
            {
                while (n--)
                    (*dst++)    = 1.0f;
            }

            void triangular_general(float *dst, size_t n, int dn)
            {
                if (n == 0)
                    return;

                float l =  (dn > 0) ? n + 1 :
                            (dn < 0) ? n - 1 :
                            n;
                if (l == 0.0f)
                {
                    *dst = 0.0f;
                    return;
                }
                l           = 2.0f / l;
                float c     = (n - 1) * 0.5;

                for (size_t i=0; i<n; ++i)
                    dst[i]      = 1.0f - fabs((i - c) * l);
            }

            void bartlett_fejer(float *dst, size_t n)
            {
                triangular_general(dst, n, -1);
            }

            void triangular(float *dst, size_t n)
            {
                triangular_general(dst, n, 0);
            }

            void parzen(float *dst, size_t n)
            {
                if (n == 0)
                    return;
                float n_2       = 0.5 * n;
                float n_4       = 0.25 * n;
                float n__2      = 1.0 / n_2;

                for (size_t i=0; i<n; ++i)
                {
                    float x     = fabs(i - n_2);
                    float k     = x * n__2;
                    float p     = 1.0f - k;

                    if (x <= n_4)
                        dst[i]      = 1.0f - 6.0f * k * k * p;
                    else
                        dst[i]      = 2.0f * p * p * p;
                }
            }

            void welch(float *dst, size_t n)
            {
                if (n == 0)
                    return;

                float c     = (n - 1) * 0.5f;
                float mc    = 1.0f / c;

                for (size_t i=0; i<n; ++i)
                {
                    float t     = (i - c) * mc;
                    dst[i]      = 1.0f - t * t;
                }
            }

            void hamming_general(float *dst, size_t n, float a, float b)
            {
                if (n == 0)
                    return;

                float f = 2.0f * M_PI / (n - 1);
                for (size_t i=0; i<n; ++i)
                    dst[i]  = a - b * cosf(i * f);
            }

            void hann(float *dst, size_t n)
            {
                hamming_general(dst, n, 0.5f, 0.5f);
            }

            void hamming(float *dst, size_t n)
            {
                hamming_general(dst, n, 0.54f, 0.46f);
            }

            void blackman_general(float *dst, size_t n, float a)
            {
                if (n == 0)
                    return;

                float a2        = a * 0.5f;
                float a0        = 0.5f - a2;
                float f1        = 2.0f * M_PI / (n - 1);
                float f2        = f1 * 2.0f;

                for (size_t i=0; i<n; ++i)
                    dst[i]  = a0 - 0.5 * cosf(i * f1) + a2 * cosf(i * f2);
            }

            void blackman(float *dst, size_t n)
            {
                return blackman_general(dst, n, 0.16f);
            }

            void nuttall_general(float *dst, size_t n, float a0, float a1, float a2, float a3)
            {
                if (n == 0)
                    return;

                float f1        = 2.0f * M_PI / (n - 1);
                float f2        = f1 * 2.0f;
                float f3        = f1 * 3.0f;

                for (size_t i=0; i<n; ++i)
                    dst[i]  = a0 - a1 * cosf(i * f1) + a2 * cosf(i * f2) - a3 * cosf(i * f3);
            }

            void nuttall(float *dst, size_t n)
            {
                return nuttall_general(dst, n, 0.355768f, 0.487396f, 0.144232f, 0.012604f);
            }

            void blackman_nuttall(float *dst, size_t n)
            {
                return nuttall_general(dst, n, 0.3635819f, 0.4891775f, 0.1365995f, 0.0106411f);
            }

            void blackman_harris(float *dst, size_t n)
            {
                return nuttall_general(dst, n, 0.35875f, 0.48829f, 0.14128f, 0.01168f);
            }

            void flat_top_general(float *dst, size_t n, float a0, float a1, float a2, float a3, float a4)
            {
                if (n == 0)
                    return;
                float f1        = 2.0f * M_PI / (n - 1);
                float f2        = f1 * 2.0f;
                float f3        = f1 * 3.0f;
                float f4        = f1 * 4.0f;
                float norm      = 1.0f / (a0 - a1 * cosf(n * 0.5 * f1) + a2 * cosf(n * 0.5 * f2) - a3 * cosf(n * 0.5 * f3) + a4 * cosf(n * 0.5 * f4));

                for (size_t i=0; i<n; ++i)
                    dst[i]  = norm * (a0 - a1 * cosf(i * f1) + a2 * cosf(i * f2) - a3 * cosf(i * f3) + a4 * cosf(i * f4));
            }

            void flat_top(float *dst, size_t n)
            {
                return flat_top_general(dst, n, 1.0f, 1.93f, 1.29f, 0.388f, 0.028f);
            }

            void cosine(float *dst, size_t n)
            {
                if (n == 0)
                    return;

                float f         = M_PI / (n - 1);
                for (size_t i=0; i<n; ++i)
                    dst[i]      = sinf(f * i);
            }

            void sqr_cosine(float *dst, size_t n)
            {
                if (n == 0)
                    return;

                float f         = M_PI / n;
                for (size_t i=0; i<n; ++i)
                {
                    float a     = sinf(f * i);
                    dst[i]      = a*a;
                }
            }

            void cubic(float *dst, size_t n)
            {
                if (n <= 1)
                {
                    if (n == 1)
                        dst[n] = 1.0f;
                    return;
                }

                size_t middle   = n >> 1;
                float kx        = 1.0f / middle;
                size_t i        = 0;
                for (; i < middle; ++i)
                {
                    float x         = i * kx;
                    dst[i]          = x*x*(3.0f - 2.0f*x);
                }
                middle          = n - 1;
                for (; i<n; ++i)
                    dst[i]          = 1.0f - dst[middle - i];
            }

            void gaussian_general(float *dst, size_t n, float s)
            {
                if ((n == 0) || (s > 0.5))
                    return;
                float c     = (n - 1) * 0.5f;
                float sc    = 1.0f / (c * s);
                for (size_t i=0; i<n; ++i)
                {
                    float v     = (i - c) * sc;
                    dst[i]      = expf(-0.5f * v * v);
                }
            }

            void gaussian(float *dst, size_t n)
            {
                return gaussian_general(dst, n, 0.4);
            }

            void poisson_general(float *dst, size_t n, float t)
            {
                float c = (n - 1) * 0.5f;
                t       = - 1.0f / t;
                for (size_t i=0; i<n; ++i)
                    dst[i] = expf(t * fabs(i - c));
            }

            void poisson(float *dst, size_t n)
            {
                poisson_general(dst, n, n * 0.5f);
            }

            void bartlett_hann_general(float *dst, size_t n, float a0, float a1, float a2)
            {
                if (n == 0)
                    return;
                float k1    = 1.0f / (n - 1);
                float k2    = 2.0f * M_PI * k1;
                for (size_t i=0; i<n; ++i)
                    dst[i] = a0 - a1 * fabs(i * k1 - 0.5f) - a2 * cosf(i * k2);
            }

            void bartlett_hann(float *dst, size_t n)
            {
                return bartlett_hann_general(dst, n, 0.62f, 0.48f, 0.38f);
            }

            void hann_poisson_general(float *dst, size_t n, float a)
            {
                if (n == 0)
                    return;

                float f     = 2.0f * M_PI / (n - 1);
                float k1    = (n - 1) * 0.5;
                float k2    = - a / k1;
                for (size_t i=0; i<n; ++i)
                    dst[i]  = (0.5 - 0.5 * cosf(i * f)) * expf(k2 * fabs(k1 - i));
            }

            void hann_poisson(float *dst, size_t n)
            {
                return hann_poisson_general(dst, n, 2.0f);
            }

            void lanczos(float *dst, size_t n)
            {
                if (n == 0)
                    return;

                float k = 2.0f * M_PI / (n - 1);
                for (size_t i=0; i<n; ++i)
                {
                    float x = k * i - M_PI;
                    dst[i]  = (x == 0.0f) ? 1.0f : sinf(x) / x;
                }
            }

            void tukey_general(float *dst, size_t n, float a)
            {
                if (n == 0)
                    return;
                if (a == 0.0f)
                {
                    rectangular(dst, n);
                    return;
                }

                size_t last     = n - 1;
                size_t b1       = 0.5 * a * last;
                size_t b2       = last - b1;
                float k         = M_PI * 2.0f / (a * last);
                float x         = M_PI - 2.0f * M_PI / a;
                for (size_t i=0; i<n; ++i)
                {
                    if (i <= b1)
                        dst[i]      = 0.5f + 0.5f * cosf(k * i - M_PI);
                    else if (i > b2)
                        dst[i]      = 0.5f + 0.5f * cosf(k * i + x);
                    else
                        dst[i]      = 1.0f;
                }
            }

            void tukey(float *dst, size_t n)
            {
                return tukey_general(dst, n, 0.5f);
            }

        }
    }
}
