    // Copyright (C) 2006  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_MATH_WINDOWS
    #define DLIB_MATH_WINDOWS

    #include <type_traits>
    #include "bessel.h"

    namespace dlib
    {
        // ----------------------------------------------------------------------------------------

        /*! Strong types !*/

        struct attenuation_t
        {
            /*!
                WHAT THIS OBJECT REPRESENTS
                    This object represents a desired attenuation in dB.
                    This is automatically converted into a beta_t value suitable
                    for constructing a kaiser window.
                    See https://www.mathworks.com/help/signal/ug/kaiser-window.html on
                    filter design.
            !*/
            attenuation_t() = default;
            explicit attenuation_t(double attenuation_db) : v{attenuation_db} {}
            double v = 0.0;
        };

        struct beta_t
        {
            /*!
                WHAT THIS OBJECT REPRESENTS
                    This value determines the shape of the kaiser window.
                    See https://en.wikipedia.org/wiki/Kaiser_window#Definition for more details.
            !*/
            beta_t() = default;
            explicit beta_t(double beta) : v{beta} {}
            beta_t(attenuation_t attenuation_db)
            {
                if (attenuation_db.v > 50.0)
                    v = 0.1102*(attenuation_db.v - 8.7);
                else if (attenuation_db.v >= 21.0)
                    v = 0.5842*std::pow(attenuation_db.v - 21, 0.4) + 0.07886*(attenuation_db.v - 21);
            }
            double v = 0.0;
        };

        enum window_symmetry
        {
            /*!
                WHAT THIS OBJECT REPRESENTS
                    This enum controls whether the window is a symmetric or periodic window.
                    See https://en.wikipedia.org/wiki/Window_function#Symmetry for a discussion on
                    symmetric vs periodic windows. This is using the same nomenclature as Matlab and Scipy
                    when describing windows as either symmetric or periodic.
            !*/
            SYMMETRIC,
            PERIODIC
        };

        // ----------------------------------------------------------------------------------------

        inline double kaiser(double x, double L, beta_t beta)
        /*!
            This computes the kaiser window function or kaiser-bessel window function.
            See https://en.wikipedia.org/wiki/Kaiser_window.

            ensures
                - returns the kaiser window function when |x| <= L/2 where L is the window duration
                - returns 0 otherwise
        !*/
        {
            if (std::abs(x) <= L/2.0)
            {
                const double r = 2*x/L;
                const double a = dlib::cyl_bessel_i(0, beta.v*std::sqrt(1-r*r));
                const double b = dlib::cyl_bessel_i(0, beta.v);
                return a / b;
            }
            else
            {
                return 0.0;
            }
        }

        inline double kaiser(std::size_t i, std::size_t N, beta_t beta, window_symmetry type)
        /*!
            This computes the kaiser window function or kaiser-bessel window function.
            See https://en.wikipedia.org/wiki/Kaiser_window
            This variant is a short-cut for computing a window function and storing it
            in an array of size N where 0 <= i < N is the array index.

            requires
                - 0 <= i < N
            ensures
                - returns kaiser(i - (N-1)/2, window_duration{N-1}, beta)
        !*/
        {
            DLIB_ASSERT(i < N, "index out of range");
            const std::size_t L = type == SYMMETRIC ? N-1 : N;
            return kaiser(i - L / 2.0, (double)L, beta);
        }

        // ----------------------------------------------------------------------------------------

        inline double hann(std::size_t i, std::size_t N, window_symmetry type)
        /*!
            This computes the hann window function.
            See https://en.wikipedia.org/wiki/Window_function#Hann_and_Hamming_windows.

            requires
                - 0 <= i < N
        !*/
        {
            DLIB_ASSERT(i < N, "index out of range");
            const std::size_t size = type == SYMMETRIC ? N-1 : N;
            const double phi = (2.0 * pi * i) / size;
            return 0.5 - 0.5 * std::cos(phi);
        }

        // ----------------------------------------------------------------------------------------

        inline double blackman(std::size_t i, std::size_t N, window_symmetry type)
        /*!
            This computes the Blackman window function.
            See https://en.wikipedia.org/wiki/Window_function#Blackman_window and
            https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.windows.blackman.html.

            requires
                - 0 <= i < N
        !*/
        {
            DLIB_ASSERT(i < N, "index out of range");
            const std::size_t size = type == SYMMETRIC ? N-1 : N;
            const double phi = (2.0 * pi * i) / size;
            return 0.42 -
                   0.5 * std::cos(phi) +
                   0.08 * std::cos(2.0 * phi);
        }

        // ----------------------------------------------------------------------------------------

        inline double blackman_nuttall(std::size_t i, std::size_t N, window_symmetry type)
        /*!
            This computes the Blackman-Nuttall window function.
            See https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Nuttall_window.

            requires
                - 0 <= i < N
        !*/
        {
            DLIB_ASSERT(i < N, "index out of range");
            const std::size_t size = type == SYMMETRIC ? N-1 : N;
            const double phi = (2.0 * pi * i) / size;
            return 0.3635819 -
                   0.4891775 * std::cos(phi) +
                   0.1365995 * std::cos(2*phi) -
                   0.0106411 * std::cos(3*phi);
        }

        // ----------------------------------------------------------------------------------------

        inline double blackman_harris(std::size_t i, std::size_t N, window_symmetry type)
        /*!
            This computes the Blackman-Harris window function.
            See https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Harris_window.

            requires
                - R is float, double, or long double
                - 0 <= i < N
        !*/
        {
            DLIB_ASSERT(i < N, "index out of range");
            const std::size_t size = type == SYMMETRIC ? N-1 : N;
            const double phi = (2.0 * pi * i) / size;
            return 0.35875 -
                   0.48829 * std::cos(phi) +
                   0.14128 * std::cos(2*phi) -
                   0.01168 * std::cos(3*phi);
        }

        // ----------------------------------------------------------------------------------------

        inline double blackman_harris7(std::size_t i, std::size_t N, window_symmetry type)
        /*!
            This computes the 7-order Blackman-Harris window function.

            requires
                - R is float, double, or long double
                - 0 <= i < N
        !*/
        {
            DLIB_ASSERT(i < N, "index out of range");
            const std::size_t size = type == SYMMETRIC ? N-1 : N;
            const double phi = (2.0 * pi * i) / size;
            return 0.27105 -
                   0.43329 * std::cos(phi) +
                   0.21812 * std::cos(2*phi) -
                   0.06592 * std::cos(3*phi) +
                   0.01081 * std::cos(4*phi) -
                   0.00077 * std::cos(5*phi) +
                   0.00001 * std::cos(6*phi);
        }

        // ----------------------------------------------------------------------------------------
    }

    #endif //DLIB_MATH_WINDOWS