/********************************************************
 *  ██████╗  ██████╗████████╗██╗
 * ██╔════╝ ██╔════╝╚══██╔══╝██║
 * ██║  ███╗██║        ██║   ██║
 * ██║   ██║██║        ██║   ██║
 * ╚██████╔╝╚██████╗   ██║   ███████╗
 *  ╚═════╝  ╚═════╝   ╚═╝   ╚══════╝
 * Geophysical Computational Tools & Library (GCTL)
 *
 * Copyright (c) 2023  Yi Zhang (yizhang-geo@zju.edu.cn)
 *
 * GCTL is distributed under a dual licensing scheme. 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 2 
 * of the License, or (at your option) any later version. You should have 
 * received a copy of the GNU Lesser General Public License along with this 
 * program. If not, see <http://www.gnu.org/licenses/>.
 * 
 * If the terms and conditions of the LGPL v.2. would prevent you from using 
 * the GCTL, please consider the option to obtain a commercial license for a 
 * fee. These licenses are offered by the GCTL's original author. As a rule, 
 * licenses are provided "as-is", unlimited in time for a one time fee. Please 
 * send corresponding requests to: yizhang-geo@zju.edu.cn. Please do not forget 
 * to include some description of your company and the realm of its activities. 
 * Also add information on how to contact you by electronic and paper mail.
 ******************************************************/

#ifndef _GCTL_KERNEL_DENSITY_ESTIMATION_H
#define _GCTL_KERNEL_DENSITY_ESTIMATION_H

#include "../core/array.h"

namespace gctl
{
    enum kde_kernel_e
    {
        KDE_Gaussian,
        KDE_Epanechnikov,
        KDE_Rectangular,
        KDE_Triangular,
    };

    enum kde_norm_e
    {
        KDE_MAX2ONE,
        KDE_SUM2ONE,
        KDE_CUMSTOM,
    };

    class kde
    {
    public:
        kde();
        virtual ~kde();

        /**
         * @brief Construct a new kde object
         * 
         * @param h 核函数的带宽
         * @param s 核密度估计的采样点序列
         */
        kde(double h, const array<double> &s);

        /**
         * @brief 初始化核密度估计对象
         * 
         * @param h 核函数的带宽
         * @param x 核密度估计的采样点序列
         */
        void init(double h, const array<double> &s);

        /**
         * @brief 计算集合的概率密度分布
         * 
         * @param m 待计算的数据集合
         * @param d 采样点处的概率分布
         * @param k_type 核函数类型
         */
        void get_distribution(const array<double> &m, array<double> &d, 
            kde_kernel_e k_type = KDE_Gaussian);

        /**
         * @brief 计算集合的概率密度分布相对于集合元素的偏导数
         * 
         * @param m_id 待计算集合元素的索引
         * @param m 待计算的数据集合
         * @param dm 概率分布相对于集合元素m_id的偏导数
         * @param k_type 核函数类型
         */
        void get_gradient_at(size_t m_id, const array<double> &m, array<double> &dm, 
            kde_kernel_e k_type = KDE_Gaussian);

    private:
        size_t xs_;
        double h_;
        array<double> x_;

        double gaussian_kernel(double x);
        double epanechnikov_kernel(double x, bool gradient = false);
        double rectangular_kernel(double x, bool gradient = false);
        double triangular_kernel(double x, bool gradient = false);
    };

    class kde2d
    {
    public:
        kde2d();
        virtual ~kde2d();

        /**
         * @brief Construct a new kde2d object
         * 
         * @param hx
         * @param hy
         * @param x 
         * @param y 
         */
        kde2d(double hx, double hy, const array<double> &x, const array<double> &y);
        
        /**
         * @brief 
         * 
         * @param hx
         * @param hy 
         * @param x 
         * @param y 
         */
        void init(double hx, double hy, const array<double> &x, const array<double> &y);

        /**
         * @brief Get the distribution object
         * 
         * @param mx 
         * @param my 
         * @param dxy 
         * @param k_type 
         */
        void get_distribution(const array<double> &mx, const array<double> &my, 
            array<double> &dxy, kde_kernel_e k_type = KDE_Gaussian);

        /**
         * @brief Get the gradient at object
         * 
         * @param m_id 
         * @param mx 
         * @param my 
         * @param dmx
         * @param k_type 
         */
        void get_gradient_x_at(size_t m_id, const array<double> &mx, 
            const array<double> &my, array<double> &dmx, kde_kernel_e k_type = KDE_Gaussian);

        /**
         * @brief Get the gradient y at object
         * 
         * @param m_id 
         * @param mx 
         * @param my 
         * @param dmy 
         * @param k_type 
         */
        void get_gradient_y_at(size_t m_id, const array<double> &mx, 
            const array<double> &my, array<double> &dmy, kde_kernel_e k_type = KDE_Gaussian);
    
    private:
        size_t xs_, ys_;
        double hx_, hy_;
        array<double> x_, y_;

        double gaussian_kernel(double x, double y);
    };
};

#endif // _GCTL_KERNEL_DENSITY_ESTIMATION_H