// Copyright (C) 2026 Kiyotsugu Arai
// SPDX-License-Identifier: LGPL-3.0-or-later

// Perceptron.hpp — 単純パーセプトロン
//
// 移植元: lib++20 機械学習/パーセプトロン/パーセプトロン.h
// 改良点:
//   - calx Vector<T> ベース
//   - dot() 関数使用
//   - 英語 API

#ifndef CALX_ML_NEURAL_PERCEPTRON_HPP
#define CALX_ML_NEURAL_PERCEPTRON_HPP

#include <math/core/matrix.hpp>
#include <math/linalg/blas.hpp>
#include <cassert>

namespace calx::ml {

    /// 単純パーセプトロン (二値分類: +1/-1)
    template<typename T>
    class Perceptron {
    public:
        Vector<T> weights;
        T bias;
        T learningRate;

        Perceptron(std::size_t dim, T lr = T{0.01})
            : weights(dim, T{1})
            , bias(T{1})
            , learningRate(lr)
        {}

        /// 1 サンプルで学習 (オンライン学習)
        /// @param x      入力ベクトル
        /// @param target  目標値 (+1 or -1)
        /// @return        予測値
        T train(const Vector<T>& x, int target) {
            assert(target == +1 || target == -1);

            T y = blas::dot(weights, x) + bias;
            T loss = std::max(T{0}, -static_cast<T>(target) * y);

            if (loss != T{0}) {
                T d = learningRate * static_cast<T>(target);
                blas::axpy(d, x, weights);
                bias += d;
                return y;
            }
            return static_cast<T>(target);
        }

        /// 分類 (+1, -1, or 0)
        int predict(const Vector<T>& x) const {
            T y = blas::dot(weights, x) + bias;
            if (y > T{0}) return +1;
            if (y < T{0}) return -1;
            return 0;
        }
    };

    /// マルチクラスパーセプトロン (One-vs-All)
    template<typename T>
    class MultiClassPerceptron {
    public:
        /// @param dim        特徴量次元
        /// @param numClasses クラス数
        /// @param lr         学習率
        MultiClassPerceptron(size_t dim, size_t numClasses, T lr = T{0.01})
            : dim_(dim), nClasses_(numClasses), lr_(lr)
        {
            // 各クラスの重みベクトル (dim+1 for bias)
            weights_.resize(numClasses, std::vector<T>(dim + 1, T{0}));
        }

        /// 1 サンプルで学習
        void train(const Vector<T>& x, int target) {
            int predicted = predict(x);
            if (predicted != target) {
                for (size_t d = 0; d < dim_; ++d) {
                    weights_[target][d] += lr_ * x[d];
                    weights_[predicted][d] -= lr_ * x[d];
                }
                weights_[target][dim_] += lr_;
                weights_[predicted][dim_] -= lr_;
            }
        }

        /// 分類 (最大スコアのクラスを返す)
        int predict(const Vector<T>& x) const {
            int bestClass = 0;
            T bestScore = score(x, 0);
            for (size_t c = 1; c < nClasses_; ++c) {
                T s = score(x, c);
                if (s > bestScore) { bestScore = s; bestClass = static_cast<int>(c); }
            }
            return bestClass;
        }

        T score(const Vector<T>& x, size_t classIdx) const {
            T s = weights_[classIdx][dim_];  // bias
            for (size_t d = 0; d < dim_; ++d) s += weights_[classIdx][d] * x[d];
            return s;
        }

    private:
        size_t dim_, nClasses_;
        T lr_;
        std::vector<std::vector<T>> weights_;
    };

} // namespace calx::ml

#endif // CALX_ML_NEURAL_PERCEPTRON_HPP
