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

// auto_tuning.hpp
//
// 自動パラメータ調整ユーティリティ
//
// このファイルでは、計算ポリシーの閾値を自動的に調整するためのユーティリティを提供します。
// 各環境での最適なパフォーマンスを実現するために、実測ベースでのパラメータ調整機能を
// 実装しています。
//
// 主な機能:
// - 演算タイプごとの最適閾値のベンチマーク
// - 閾値データの永続化
// - 環境検出と自動設定

#ifndef CALX_AUTO_TUNING_HPP
#define CALX_AUTO_TUNING_HPP

#include "math/core/common.hpp"
#include "math/core/traits.hpp"
#include "mkl_backend.hpp"
#include "simd_backend.hpp"

#include <chrono>
#include <cstddef>
#include <fstream>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
#include <vector>

// ベンチマーク用の最適化防止バリア（MSVC/GCC両対応）
#ifdef _MSC_VER
#include <math/core/mp/Int/compiler_intrinsics.hpp>
#define CALX_PREVENT_OPTIMIZE(var) \
    do { volatile auto _sink = (var); (void)_sink; _ReadWriteBarrier(); } while(0)
#else
#define CALX_PREVENT_OPTIMIZE(var) \
    asm volatile("" : "+g" (var) : :)
#endif

namespace calx {
namespace computation {

// 演算タイプを表す列挙型
enum class OperationType {
    DotProduct,
    Norm2,
    Axpy,
    Scale,
    Add,
    Subtract,
    ElementWiseMultiply,
    // 将来的に追加される演算タイプ
};

// ベンチマークの結果を格納する構造体
struct BenchmarkResult {
    std::size_t threshold;  // 最適な閾値
    double mkl_time;        // MKL実行時間（ナノ秒）
    double simd_time;       // SIMD実行時間（ナノ秒）
    double standard_time;   // 標準実装実行時間（ナノ秒）
};

// 環境情報を表す構造体
struct EnvironmentInfo {
    std::string cpu_model;
    std::string compiler;
    std::string compiler_version;
    std::string os;
    bool has_mkl;
    simd::SimdInstructionSet simd_level;
    
    // ハッシュ値を生成（一意な環境識別用）
    std::string hash() const {
        return cpu_model + "_" + compiler + "_" + compiler_version + "_" + os + 
               (has_mkl ? "_mkl" : "_no_mkl") + "_simd" + 
               std::to_string(static_cast<int>(simd_level));
    }
};

// 閾値キャッシュを管理するクラス
class ThresholdCache {
public:
    static ThresholdCache& instance() {
        static ThresholdCache cache;
        return cache;
    }
    
    // キャッシュからの閾値取得
    template<typename T>
    std::size_t get_threshold(OperationType op_type) {
        std::lock_guard<std::mutex> lock(mutex_);
        
        const auto key = create_key<T>(op_type);
        auto it = thresholds_.find(key);
        
        if (it != thresholds_.end()) {
            return it->second;
        }
        
        // 利用可能なキャッシュがない場合は既定値を返す
        return get_default_threshold<T>(op_type);
    }
    
    // キャッシュへの閾値設定
    template<typename T>
    void set_threshold(OperationType op_type, std::size_t threshold) {
        std::lock_guard<std::mutex> lock(mutex_);
        
        const auto key = create_key<T>(op_type);
        thresholds_[key] = threshold;
    }
    
    // キャッシュデータの読み込み
    bool load(const std::string& filename = "threshold_cache.dat") {
        std::lock_guard<std::mutex> lock(mutex_);
        
        std::ifstream file(filename, std::ios::binary);
        if (!file) {
            return false;
        }
        
        try {
            // 環境ハッシュを読み込み、現在の環境と一致するか確認
            std::string env_hash;
            std::getline(file, env_hash);
            
            if (env_hash != env_info_.hash()) {
                // 環境が異なる場合は読み込まない
                return false;
            }
            
            // エントリ数を読み込み
            std::size_t num_entries;
            file.read(reinterpret_cast<char*>(&num_entries), sizeof(num_entries));
            
            // 各エントリを読み込み
            for (std::size_t i = 0; i < num_entries; ++i) {
                std::string key;
                std::size_t key_size;
                file.read(reinterpret_cast<char*>(&key_size), sizeof(key_size));
                key.resize(key_size);
                file.read(&key[0], key_size);
                
                std::size_t threshold;
                file.read(reinterpret_cast<char*>(&threshold), sizeof(threshold));
                
                thresholds_[key] = threshold;
            }
            
            return true;
        } catch (...) {
            return false;
        }
    }
    
    // キャッシュデータの保存
    bool save(const std::string& filename = "threshold_cache.dat") const {
        std::lock_guard<std::mutex> lock(mutex_);
        
        std::ofstream file(filename, std::ios::binary);
        if (!file) {
            return false;
        }
        
        try {
            // 環境ハッシュを書き込み
            const std::string env_hash = env_info_.hash();
            file << env_hash << '\n';
            
            // エントリ数を書き込み
            const std::size_t num_entries = thresholds_.size();
            file.write(reinterpret_cast<const char*>(&num_entries), sizeof(num_entries));
            
            // 各エントリを書き込み
            for (const auto& [key, threshold] : thresholds_) {
                const std::size_t key_size = key.size();
                file.write(reinterpret_cast<const char*>(&key_size), sizeof(key_size));
                file.write(key.c_str(), key_size);
                
                file.write(reinterpret_cast<const char*>(&threshold), sizeof(threshold));
            }
            
            return true;
        } catch (...) {
            return false;
        }
    }
    
    // 現在の環境情報を取得
    const EnvironmentInfo& get_environment_info() const {
        return env_info_;
    }
    
private:
    ThresholdCache() {
        // 環境情報の初期化
        env_info_.cpu_model = detect_cpu_model();
        env_info_.compiler = detect_compiler();
        env_info_.compiler_version = detect_compiler_version();
        env_info_.os = detect_os();
        env_info_.has_mkl = CALX_HAS_MKL;
        env_info_.simd_level = simd::available_simd_level;
        
        // 既存のキャッシュを読み込み
        load();
    }
    
    ~ThresholdCache() {
        // キャッシュを保存
        save();
    }
    
    // コピー禁止
    ThresholdCache(const ThresholdCache&) = delete;
    ThresholdCache& operator=(const ThresholdCache&) = delete;
    
    // キャッシュキーの生成
    template<typename T>
    std::string create_key(OperationType op_type) const {
        return std::string(typeid(T).name()) + "_" + 
               std::to_string(static_cast<int>(op_type));
    }
    
    // 既定の閾値を取得
    template<typename T>
    std::size_t get_default_threshold(OperationType op_type) const {
        // 型と演算に基づく既定値
        if constexpr (std::is_same_v<T, float>) {
            switch (op_type) {
                case OperationType::DotProduct:        return 256;
                case OperationType::Norm2:             return 256;
                case OperationType::Axpy:              return 512;
                case OperationType::Scale:             return 1024;
                case OperationType::Add:               return 512;
                case OperationType::Subtract:          return 512;
                case OperationType::ElementWiseMultiply: return 512;
                default:                               return 256;
            }
        } 
        else if constexpr (std::is_same_v<T, double>) {
            switch (op_type) {
                case OperationType::DotProduct:        return 128;
                case OperationType::Norm2:             return 128;
                case OperationType::Axpy:              return 256;
                case OperationType::Scale:             return 512;
                case OperationType::Add:               return 256;
                case OperationType::Subtract:          return 256;
                case OperationType::ElementWiseMultiply: return 256;
                default:                               return 128;
            }
        }
        else {
            return 64;  // その他の型のデフォルト値
        }
    }
    
    // CPU情報の検出
    static std::string detect_cpu_model() {
        // 実装例（簡易的な実装）
        #if defined(_WIN32)
        return "Unknown_Win";
        #elif defined(__APPLE__)
        return "Unknown_Mac";
        #elif defined(__linux__)
        return "Unknown_Linux";
        #else
        return "Unknown";
        #endif
    }
    
    // コンパイラの検出
    static std::string detect_compiler() {
        #if defined(_MSC_VER)
        return "MSVC";
        #elif defined(__clang__)
        return "Clang";
        #elif defined(__GNUC__)
        return "GCC";
        #else
        return "Unknown";
        #endif
    }
    
    // コンパイラバージョンの検出
    static std::string detect_compiler_version() {
        #if defined(_MSC_VER)
        return std::to_string(_MSC_VER);
        #elif defined(__clang__)
        return std::to_string(__clang_major__) + "." + 
               std::to_string(__clang_minor__);
        #elif defined(__GNUC__)
        return std::to_string(__GNUC__) + "." + 
               std::to_string(__GNUC_MINOR__);
        #else
        return "0.0";
        #endif
    }
    
    // OSの検出
    static std::string detect_os() {
        #if defined(_WIN32)
        return "Windows";
        #elif defined(__APPLE__)
        return "MacOS";
        #elif defined(__linux__)
        return "Linux";
        #else
        return "Unknown";
        #endif
    }
    
    mutable std::mutex mutex_;
    std::unordered_map<std::string, std::size_t> thresholds_;
    EnvironmentInfo env_info_;
};

//-----------------------------------------------------------------------------
// ベンチマーク関数
//-----------------------------------------------------------------------------

// テスト用のランダムデータ生成
template<typename T>
std::vector<T> generate_random_data(std::size_t size) {
    std::vector<T> data(size);
    
    // 簡易的な乱数生成
    for (std::size_t i = 0; i < size; ++i) {
        if constexpr (std::is_floating_point_v<T>) {
            data[i] = static_cast<T>(rand()) / RAND_MAX;
        } else if constexpr (std::is_integral_v<T>) {
            data[i] = static_cast<T>(rand());
        } else {
            // その他の型（複素数など）
            data[i] = T{};
        }
    }
    
    return data;
}

// 内積計算のベンチマーク
template<typename T>
BenchmarkResult benchmark_dot_product(std::size_t size, int num_iterations = 100) {
    // テストデータ
    auto vec1 = generate_random_data<T>(size);
    auto vec2 = generate_random_data<T>(size);
    
    // MKL実装のベンチマーク
    auto start_mkl = std::chrono::high_resolution_clock::now();
    T result_mkl = T{};
    
    for (int i = 0; i < num_iterations; ++i) {
        result_mkl = mkl::dot_product_simd(vec1.data(), vec2.data(), size);
        CALX_PREVENT_OPTIMIZE(result_mkl); // 最適化防止
    }
    
    auto end_mkl = std::chrono::high_resolution_clock::now();
    
    // SIMD実装のベンチマーク
    auto start_simd = std::chrono::high_resolution_clock::now();
    T result_simd = T{};
    
    for (int i = 0; i < num_iterations; ++i) {
        result_simd = simd::dot_product_simd(vec1.data(), vec2.data(), size);
        CALX_PREVENT_OPTIMIZE(result_simd); // 最適化防止
    }
    
    auto end_simd = std::chrono::high_resolution_clock::now();
    
    // 標準実装のベンチマーク
    auto start_std = std::chrono::high_resolution_clock::now();
    T result_std = T{};
    
    for (int i = 0; i < num_iterations; ++i) {
        result_std = T{};
        for (std::size_t j = 0; j < size; ++j) {
            result_std += vec1[j] * vec2[j];
        }
        CALX_PREVENT_OPTIMIZE(result_std); // 最適化防止
    }
    
    auto end_std = std::chrono::high_resolution_clock::now();
    
    // 経過時間の計算（ナノ秒）
    auto mkl_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(end_mkl - start_mkl).count() / num_iterations;
    auto simd_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(end_simd - start_simd).count() / num_iterations;
    auto std_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(end_std - start_std).count() / num_iterations;
    
    // 最適な閾値を決定
    std::size_t threshold = size;
    
    if (mkl_duration < simd_duration && mkl_duration < std_duration) {
        // MKLが最速の場合は閾値を小さく
        threshold = size / 2;
    } else if (simd_duration < mkl_duration && simd_duration < std_duration) {
        // SIMDが最速の場合は閾値を現在のサイズに
        threshold = size;
    } else {
        // 標準実装が最速の場合は閾値を大きく
        threshold = size * 2;
    }
    
    return {threshold, static_cast<double>(mkl_duration), 
            static_cast<double>(simd_duration), static_cast<double>(std_duration)};
}

// axpy演算のベンチマーク
template<typename T>
BenchmarkResult benchmark_axpy(std::size_t size, int num_iterations = 100) {
    // テストデータ
    auto vec_x = generate_random_data<T>(size);
    auto vec_y = generate_random_data<T>(size);
    auto vec_y_copy1 = vec_y;
    auto vec_y_copy2 = vec_y;
    T alpha = static_cast<T>(1.5);
    
    // MKL実装のベンチマーク
    auto start_mkl = std::chrono::high_resolution_clock::now();
    
    for (int i = 0; i < num_iterations; ++i) {
        vec_y_copy1 = vec_y;  // リセット
        mkl::axpy_simd(vec_y_copy1.data(), alpha, vec_x.data(), size);
        CALX_PREVENT_OPTIMIZE(vec_y_copy1[0]); // 最適化防止
    }
    
    auto end_mkl = std::chrono::high_resolution_clock::now();
    
    // SIMD実装のベンチマーク
    auto start_simd = std::chrono::high_resolution_clock::now();
    
    for (int i = 0; i < num_iterations; ++i) {
        vec_y_copy2 = vec_y;  // リセット
        simd::axpy_simd(vec_y_copy2.data(), alpha, vec_x.data(), size);
        CALX_PREVENT_OPTIMIZE(vec_y_copy2[0]); // 最適化防止
    }
    
    auto end_simd = std::chrono::high_resolution_clock::now();
    
    // 標準実装のベンチマーク
    auto start_std = std::chrono::high_resolution_clock::now();
    
    for (int i = 0; i < num_iterations; ++i) {
        auto vec_y_copy3 = vec_y;  // リセット
        for (std::size_t j = 0; j < size; ++j) {
            vec_y_copy3[j] += alpha * vec_x[j];
        }
        CALX_PREVENT_OPTIMIZE(vec_y_copy3[0]); // 最適化防止
    }
    
    auto end_std = std::chrono::high_resolution_clock::now();
    
    // 経過時間の計算（ナノ秒）
    auto mkl_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(end_mkl - start_mkl).count() / num_iterations;
    auto simd_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(end_simd - start_simd).count() / num_iterations;
    auto std_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(end_std - start_std).count() / num_iterations;
    
    // 最適な閾値を決定
    std::size_t threshold = size;
    
    if (mkl_duration < simd_duration && mkl_duration < std_duration) {
        // MKLが最速の場合は閾値を小さく
        threshold = size / 2;
    } else if (simd_duration < mkl_duration && simd_duration < std_duration) {
        // SIMDが最速の場合は閾値を現在のサイズに
        threshold = size;
    } else {
        // 標準実装が最速の場合は閾値を大きく
        threshold = size * 2;
    }
    
    return {threshold, static_cast<double>(mkl_duration), 
            static_cast<double>(simd_duration), static_cast<double>(std_duration)};
}

// 他の演算タイプのベンチマーク関数も同様に実装...

// 最適な閾値を見つける
template<typename T>
std::size_t find_optimal_threshold(OperationType op_type, 
                                  const std::vector<std::size_t>& test_sizes,
                                  int num_iterations = 100) {
    std::vector<BenchmarkResult> results;
    
    // 各テストサイズでベンチマークを実行
    for (auto size : test_sizes) {
        BenchmarkResult result;
        
        switch (op_type) {
            case OperationType::DotProduct:
                result = benchmark_dot_product<T>(size, num_iterations);
                break;
            case OperationType::Axpy:
                result = benchmark_axpy<T>(size, num_iterations);
                break;
            // 他の演算タイプも同様に...
            default:
                throw std::invalid_argument("Unsupported operation type");
        }
        
        results.push_back(result);
    }
    
    // 結果を分析して最適な閾値を決定
    // 単純なアプローチ：MKLが最速となる最小のサイズを閾値に
    for (std::size_t i = 0; i < results.size(); ++i) {
        if (results[i].mkl_time < results[i].simd_time &&
            results[i].mkl_time < results[i].standard_time) {
            return test_sizes[i];
        }
    }
    
    // MKLが常に遅い場合は、大きな閾値を返す
    return test_sizes.back() * 2;
}

// 自動調整を実行して閾値を設定
template<typename T>
void auto_tune_thresholds() {
    // ベンチマークのテストサイズ
    std::vector<std::size_t> test_sizes = {16, 32, 64, 128, 256, 512, 1024, 2048, 4096};
    
    // 各演算タイプに対して最適閾値を見つける
    std::size_t dot_product_threshold = find_optimal_threshold<T>(OperationType::DotProduct, test_sizes);
    std::size_t axpy_threshold = find_optimal_threshold<T>(OperationType::Axpy, test_sizes);
    // 他の演算タイプも同様に...
    
    // 閾値をキャッシュに保存
    auto& cache = ThresholdCache::instance();
    cache.set_threshold<T>(OperationType::DotProduct, dot_product_threshold);
    cache.set_threshold<T>(OperationType::Axpy, axpy_threshold);
    // 他の演算タイプも同様に...
    
    // キャッシュを永続化
    cache.save();
}

// 閾値取得関数（計算ポリシーから使用）
template<typename T>
std::size_t get_threshold(OperationType op_type) {
    return ThresholdCache::instance().get_threshold<T>(op_type);
}

} // namespace computation
} // namespace calx

#endif // CALX_AUTO_TUNING_HPP