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

/**
 * @file camera.hpp
 * @brief カメラモデル (DRW-63〜64)
 *
 * DRW-63: 薄レンズカメラ (被写界深度・ボケ)
 * DRW-64: パノラマ / 魚眼 / 正距円筒投影
 */

#ifndef CALX_IMAGE_RENDER_CAMERA_HPP
#define CALX_IMAGE_RENDER_CAMERA_HPP

#include <image/geometry/primitives.hpp>
#include <cmath>
#include <random>

namespace calx::image {

/// 薄レンズカメラ (DRW-63)
template<typename T>
struct ThinLensCamera {
    Point3D<T> position;
    Point3D<T> lookAt;
    Point3D<T> up{T(0), T(1), T(0)};
    T fovDeg = T(60);
    T aperture = T(0);       // 絞り径 (0 = ピンホール)
    T focusDistance = T(10);  // フォーカス距離

    /// ピクセル座標 (u,v ∈ [0,1]) からレイを生成
    /// u1, u2: レンズ上のランダムサンプル [0,1]
    struct CameraRay { Point3D<T> origin, direction; };

    CameraRay generateRay(T u, T v, T lensU = T(0.5), T lensV = T(0.5)) const {
        constexpr T pi = T(3.14159265358979323846);
        T halfH = std::tan(fovDeg * pi / T(360));
        T aspect = T(1);  // 呼び出し側でアスペクト比調整

        auto forward = (lookAt - position).normalized();
        auto right = cross(forward, up).normalized();
        auto camUp = cross(right, forward);

        // ピンホール方向
        T px = (T(2) * u - T(1)) * halfH * aspect;
        T py = (T(1) - T(2) * v) * halfH;
        auto dir = (forward + right * px + camUp * py).normalized();

        if (aperture <= T(0)) {
            return {position, dir};
        }

        // 薄レンズ: フォーカス点を計算してレンズ上からレイを発射
        auto focusPoint = position + dir * (focusDistance / dot(dir, forward));

        // レンズ上のランダム点 (同心円マッピング)
        T r = aperture * T(0.5) * std::sqrt(lensU);
        T theta = T(2) * pi * lensV;
        auto lensOffset = right * (r * std::cos(theta)) + camUp * (r * std::sin(theta));
        auto origin = position + lensOffset;
        auto newDir = (focusPoint - origin).normalized();
        return {origin, newDir};
    }
};

/// 投影タイプ (DRW-64)
enum class ProjectionType {
    Perspective,     // 通常の透視投影
    Orthographic,    // 正射影
    Fisheye,         // 魚眼 (等距離射影)
    Equirectangular, // 正距円筒 (パノラマ)
    Stereographic    // ステレオ投影
};

/// パノラマ / 魚眼カメラ (DRW-64)
template<typename T>
struct PanoramaCamera {
    Point3D<T> position;
    Point3D<T> lookAt;
    Point3D<T> up{T(0), T(1), T(0)};
    ProjectionType projection = ProjectionType::Equirectangular;
    T fovDeg = T(180);  // 魚眼の視野角

    struct CameraRay { Point3D<T> origin, direction; };

    /// ピクセル座標 (u,v ∈ [0,1]) からレイを生成
    CameraRay generateRay(T u, T v) const {
        constexpr T pi = T(3.14159265358979323846);
        auto forward = (lookAt - position).normalized();
        auto right = cross(forward, up).normalized();
        auto camUp = cross(right, forward);

        Point3D<T> dir;

        switch (projection) {
        case ProjectionType::Equirectangular: {
            // 経度 = u * 2π, 緯度 = v * π
            T lon = (u - T(0.5)) * T(2) * pi;
            T lat = (T(0.5) - v) * pi;
            T cosLat = std::cos(lat);
            dir = forward * (cosLat * std::cos(lon))
                + camUp * std::sin(lat)
                + right * (cosLat * std::sin(lon));
            break;
        }
        case ProjectionType::Fisheye: {
            T maxAngle = fovDeg * pi / T(360);
            T cx = T(2) * u - T(1), cy = T(1) - T(2) * v;
            T r = std::sqrt(cx*cx + cy*cy);
            if (r > T(1)) return {position, forward};  // 視野外
            T theta = r * maxAngle;
            T phi = std::atan2(cy, cx);
            dir = forward * std::cos(theta)
                + (right * std::cos(phi) + camUp * std::sin(phi)) * std::sin(theta);
            break;
        }
        case ProjectionType::Stereographic: {
            T maxAngle = fovDeg * pi / T(360);
            T cx = T(2) * u - T(1), cy = T(1) - T(2) * v;
            T r = std::sqrt(cx*cx + cy*cy);
            T theta = T(2) * std::atan(r * std::tan(maxAngle / T(2)));
            T phi = std::atan2(cy, cx);
            dir = forward * std::cos(theta)
                + (right * std::cos(phi) + camUp * std::sin(phi)) * std::sin(theta);
            break;
        }
        default: // Perspective
            T halfH = std::tan(fovDeg * pi / T(360));
            T px = (T(2) * u - T(1)) * halfH;
            T py = (T(1) - T(2) * v) * halfH;
            dir = (forward + right * px + camUp * py).normalized();
            break;
        }
        return {position, dir.normalized()};
    }
};

} // namespace calx::image

#endif
