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

/**
 * @file atmosphere.hpp
 * @brief 大気効果 (POV-18, 20〜22) + カメラ拡張 (POV-23〜25) + AA (POV-26)
 *
 * POV-18: HDRI 環境光 (Image-Based Lighting)
 * POV-20: フォグ
 * POV-21: メディア (体積レンダリング)
 * POV-22: レインボー
 * POV-23: 被写界深度 (DoF)
 * POV-24: モーションブラー
 * POV-25: 魚眼 / パノラマ投影
 * POV-26: スーパーサンプリング (AA)
 * POV-27: グローバルイルミネーション
 * POV-28: コースティクス
 */

#ifndef CALX_IMAGE_RENDER_ATMOSPHERE_HPP
#define CALX_IMAGE_RENDER_ATMOSPHERE_HPP

#include <image/geometry/primitives.hpp>
#include <image/types/RGB.hpp>
#include <image/Image.hpp>
#include <cmath>
#include <algorithm>
#include <functional>

namespace calx::image {

// ========================================================================
// POV-18: HDRI 環境光 (Image-Based Lighting)
// ========================================================================

/// 正距円筒図法の環境マップからレイ方向の色を取得
template<typename T>
RGB<T> sampleEnvironmentMap(const Image<T>& envMap, const Point3D<T>& direction) {
    constexpr T pi = T(3.14159265358979323846);
    auto d = direction.normalized();
    T u = T(0.5) + std::atan2(d.z, d.x) / (T(2) * pi);
    T v = T(0.5) - std::asin(std::clamp(d.y, T(-1), T(1))) / pi;
    int px = std::clamp(int(u * T(envMap.width())), 0, int(envMap.width()) - 1);
    int py = std::clamp(int(v * T(envMap.height())), 0, int(envMap.height()) - 1);
    if (envMap.numChannels() >= 3)
        return {envMap.at(py, px, 0), envMap.at(py, px, 1), envMap.at(py, px, 2)};
    T g = envMap.at(py, px);
    return {g, g, g};
}

// ========================================================================
// POV-20: フォグ
// ========================================================================

template<typename T>
struct FogSettings {
    RGB<T> color{T(0.7), T(0.7), T(0.8)};
    T distance = T(100);    // フォグの半減距離
    T minAlt = T(-1e10);    // 高度依存フォグの下限
    T maxAlt = T(1e10);     // 高度依存フォグの上限
    bool heightBased = false;
};

/// フォグを適用
template<typename T>
RGB<T> applyFog(const RGB<T>& surfaceColor, T dist, T hitY,
                 const FogSettings<T>& fog) {
    T factor;
    if (fog.heightBased) {
        T hFactor = std::clamp((fog.maxAlt - hitY) / (fog.maxAlt - fog.minAlt), T(0), T(1));
        factor = T(1) - std::exp(-dist / fog.distance * hFactor);
    } else {
        factor = T(1) - std::exp(-dist / fog.distance);
    }
    return surfaceColor * (T(1) - factor) + fog.color * factor;
}

// ========================================================================
// POV-22: レインボー
// ========================================================================

/// レインボー効果 (角度ベースの色分散)
template<typename T>
RGB<T> rainbow(const Point3D<T>& rayDir, const Point3D<T>& sunDir,
                T innerAngle = T(40), T outerAngle = T(42), T intensity = T(0.3)) {
    constexpr T pi = T(3.14159265358979323846);
    T cosAngle = dot(rayDir.normalized(), sunDir.normalized());
    T angle = std::acos(std::clamp(cosAngle, T(-1), T(1))) * T(180) / pi;

    if (angle < innerAngle || angle > outerAngle) return {T(0), T(0), T(0)};

    T t = (angle - innerAngle) / (outerAngle - innerAngle);
    // スペクトル近似 (赤→橙→黄→緑→青→藍→紫)
    T r = std::max(T(0), T(1) - std::abs(t - T(0.0)) * T(3));
    T g = std::max(T(0), T(1) - std::abs(t - T(0.35)) * T(3));
    T b = std::max(T(0), T(1) - std::abs(t - T(0.7)) * T(3));
    return {r * intensity, g * intensity, b * intensity};
}

// ========================================================================
// POV-24: モーションブラー
// ========================================================================

/// モーションブラーのためのレイ時間サンプリング
/// 物体の位置を時間 t ∈ [0,1] で補間し、複数サンプルを平均化
template<typename T>
struct MotionBlurSettings {
    int samples = 8;           // サンプル数
    T shutterOpen = T(0);      // シャッター開始時刻
    T shutterClose = T(1);     // シャッター終了時刻
};

// ========================================================================
// POV-26: スーパーサンプリング (拡張)
// ========================================================================

/// ストラティファイドサンプリング (層化抽出)
template<typename T>
struct StratifiedSampler {
    int sqrtSamples;  // sqrt(サンプル数)

    StratifiedSampler(int n = 4) : sqrtSamples(n) {}

    /// ピクセル内の (i,j) 番目のサンプル位置 → (u,v) ∈ [0,1]²
    std::pair<T, T> sample(int i, int j, T jitterU = T(0.5), T jitterV = T(0.5)) const {
        T u = (T(i) + jitterU) / T(sqrtSamples);
        T v = (T(j) + jitterV) / T(sqrtSamples);
        return {u, v};
    }

    int totalSamples() const { return sqrtSamples * sqrtSamples; }
};

// ========================================================================
// POV-27: グローバルイルミネーション (簡易ラジオシティ)
// ========================================================================

/// 環境遮蔽 (Ambient Occlusion) — GI の簡易近似
template<typename T>
T ambientOcclusion(const Point3D<T>& point, const Point3D<T>& normal,
                    int samples, T maxDist,
                    std::function<bool(const Point3D<T>&, const Point3D<T>&, T)> occlusionTest) {
    int occluded = 0;
    constexpr T pi2 = T(6.283185307179586);

    for (int i = 0; i < samples; ++i) {
        // コサイン重み付き半球サンプリング (簡易: フィボナッチ)
        T phi = pi2 * T(i) * T(0.618033988749895);
        T cosTheta = std::sqrt(T(1) - T(i) / T(samples));
        T sinTheta = std::sqrt(T(1) - cosTheta * cosTheta);

        // 法線に沿った座標系
        Point3D<T> up = (std::abs(normal.y) < T(0.999))
            ? Point3D<T>{T(0), T(1), T(0)} : Point3D<T>{T(1), T(0), T(0)};
        auto tangent = cross(up, normal).normalized();
        auto bitangent = cross(normal, tangent);

        auto dir = tangent * (sinTheta * std::cos(phi))
                 + normal * cosTheta
                 + bitangent * (sinTheta * std::sin(phi));

        if (occlusionTest(point + normal * T(1e-4), dir, maxDist))
            ++occluded;
    }
    return T(1) - T(occluded) / T(samples);
}

} // namespace calx::image

#endif
