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

/**
 * @file texture.hpp
 * @brief 3D プロシージャルテクスチャ・画像マッピング・バンプマッピング
 *
 * POV-Ray 互換のパターンベーステクスチャシステム:
 *   - Perlin noise (勾配ノイズ)
 *   - パターン: checker, gradient, marble, wood, granite, bozo, ripples, waves
 *   - 画像テクスチャマッピング (UV: 球面 / 円柱 / 平面)
 *   - バンプマッピング (法線摂動)
 *   - テクスチャ座標変換 (scale, rotate, translate, turbulence)
 */

#ifndef CALX_IMAGE_RENDER_TEXTURE_HPP
#define CALX_IMAGE_RENDER_TEXTURE_HPP

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

#include <cmath>
#include <array>
#include <functional>
#include <algorithm>
#include <numeric>
#include <random>

namespace calx::image {

// ========================================================================
// Perlin Noise
// ========================================================================

template<typename T>
class PerlinNoise {
public:
    PerlinNoise(unsigned seed = 0) {
        std::iota(perm_.begin(), perm_.end(), 0);
        std::mt19937 rng(seed);
        std::shuffle(perm_.begin(), perm_.end(), rng);
        // 勾配ベクトル (12方向)
        grad_ = {{
            { 1, 1, 0}, {-1, 1, 0}, { 1,-1, 0}, {-1,-1, 0},
            { 1, 0, 1}, {-1, 0, 1}, { 1, 0,-1}, {-1, 0,-1},
            { 0, 1, 1}, { 0,-1, 1}, { 0, 1,-1}, { 0,-1,-1}
        }};
    }

    /// 3D Perlin noise [-1, 1]
    T noise(T x, T y, T z) const {
        int X = static_cast<int>(std::floor(x)) & 255;
        int Y = static_cast<int>(std::floor(y)) & 255;
        int Z = static_cast<int>(std::floor(z)) & 255;
        x -= std::floor(x); y -= std::floor(y); z -= std::floor(z);
        T u = fade(x), v = fade(y), w = fade(z);

        int A  = perm_[X] + Y,     AA = perm_[A & 255] + Z,   AB = perm_[(A+1) & 255] + Z;
        int B  = perm_[(X+1) & 255] + Y, BA = perm_[B & 255] + Z, BB = perm_[(B+1) & 255] + Z;

        return lerp(w,
            lerp(v, lerp(u, grad3(perm_[AA & 255],     x,   y,   z),
                            grad3(perm_[BA & 255],     x-1, y,   z)),
                    lerp(u, grad3(perm_[AB & 255],     x,   y-1, z),
                            grad3(perm_[BB & 255],     x-1, y-1, z))),
            lerp(v, lerp(u, grad3(perm_[(AA+1) & 255], x,   y,   z-1),
                            grad3(perm_[(BA+1) & 255], x-1, y,   z-1)),
                    lerp(u, grad3(perm_[(AB+1) & 255], x,   y-1, z-1),
                            grad3(perm_[(BB+1) & 255], x-1, y-1, z-1))));
    }

    /// fBm (Fractal Brownian Motion) — 複数オクターブ重ね合わせ
    T fbm(T x, T y, T z, int octaves = 6, T lacunarity = T(2), T gain = T(0.5)) const {
        T sum = T(0), amp = T(1), freq = T(1);
        for (int i = 0; i < octaves; ++i) {
            sum += amp * noise(x * freq, y * freq, z * freq);
            freq *= lacunarity;
            amp *= gain;
        }
        return sum;
    }

    /// turbulence (絶対値ノイズの fBm)
    T turbulence(T x, T y, T z, int octaves = 6) const {
        T sum = T(0), amp = T(1), freq = T(1);
        for (int i = 0; i < octaves; ++i) {
            sum += amp * std::abs(noise(x * freq, y * freq, z * freq));
            freq *= T(2);
            amp *= T(0.5);
        }
        return sum;
    }

private:
    std::array<int, 256> perm_;
    std::array<std::array<T, 3>, 12> grad_;

    static T fade(T t) { return t * t * t * (t * (t * T(6) - T(15)) + T(10)); }
    static T lerp(T t, T a, T b) { return a + t * (b - a); }

    T grad3(int hash, T x, T y, T z) const {
        const auto& g = grad_[hash % 12];
        return g[0] * x + g[1] * y + g[2] * z;
    }
};

// ========================================================================
// テクスチャ座標変換
// ========================================================================

template<typename T>
struct TextureTransform {
    Point3D<T> scale{T(1), T(1), T(1)};
    Point3D<T> translate{T(0), T(0), T(0)};
    T turbulenceStrength = T(0);
    int turbulenceOctaves = 6;

    /// ワールド座標 → テクスチャ座標に変換
    Point3D<T> apply(const Point3D<T>& p, const PerlinNoise<T>& pn) const {
        Point3D<T> q{
            (p.x - translate.x) / scale.x,
            (p.y - translate.y) / scale.y,
            (p.z - translate.z) / scale.z
        };
        if (turbulenceStrength > T(0)) {
            q.x += turbulenceStrength * pn.turbulence(q.x, q.y, q.z, turbulenceOctaves);
            q.y += turbulenceStrength * pn.turbulence(q.x + T(100), q.y, q.z, turbulenceOctaves);
            q.z += turbulenceStrength * pn.turbulence(q.x, q.y + T(100), q.z, turbulenceOctaves);
        }
        return q;
    }
};

// ========================================================================
// パターン関数 (3D 座標 → [0,1] スカラー)
// ========================================================================

namespace pattern {

    /// 市松模様
    template<typename T>
    T checker(const Point3D<T>& p) {
        int ix = static_cast<int>(std::floor(p.x));
        int iy = static_cast<int>(std::floor(p.y));
        int iz = static_cast<int>(std::floor(p.z));
        return ((ix + iy + iz) & 1) ? T(1) : T(0);
    }

    /// グラデーション (Y 軸方向)
    template<typename T>
    T gradient(const Point3D<T>& p) {
        return std::clamp(p.y, T(0), T(1));
    }

    /// 大理石 (marble) — sin ベース + ノイズ
    template<typename T>
    T marble(const Point3D<T>& p, const PerlinNoise<T>& pn) {
        T v = p.x + pn.fbm(p.x, p.y, p.z, 6) * T(5);
        return (std::sin(v) + T(1)) * T(0.5);
    }

    /// 木目 (wood) — 同心円 + ノイズ
    template<typename T>
    T wood(const Point3D<T>& p, const PerlinNoise<T>& pn) {
        T dist = std::sqrt(p.x * p.x + p.z * p.z);
        T v = dist * T(10) + pn.fbm(p.x, p.y, p.z, 4) * T(2);
        return (std::sin(v) + T(1)) * T(0.5);
    }

    /// 花崗岩 (granite) — turbulence ベース
    template<typename T>
    T granite(const Point3D<T>& p, const PerlinNoise<T>& pn) {
        return pn.turbulence(p.x, p.y, p.z, 6);
    }

    /// agate — 層状 + turbulence
    template<typename T>
    T agate(const Point3D<T>& p, const PerlinNoise<T>& pn) {
        T v = p.y + pn.turbulence(p.x, p.y, p.z, 6) * T(0.5);
        return std::clamp((std::sin(v * T(6.28)) + T(1)) * T(0.5), T(0), T(1));
    }

    /// bozo (Perlin noise そのまま)
    template<typename T>
    T bozo(const Point3D<T>& p, const PerlinNoise<T>& pn) {
        return (pn.noise(p.x, p.y, p.z) + T(1)) * T(0.5);
    }

    /// ripples (原点からの同心球波)
    template<typename T>
    T ripples(const Point3D<T>& p) {
        T r = p.norm();
        return (std::sin(r * T(20)) + T(1)) * T(0.5);
    }

    /// waves (平面波 + ノイズ)
    template<typename T>
    T waves(const Point3D<T>& p, const PerlinNoise<T>& pn) {
        T v = p.x + pn.noise(p.x * T(0.5), p.y * T(0.5), p.z * T(0.5)) * T(2);
        return (std::sin(v * T(10)) + T(1)) * T(0.5);
    }

    /// wrinkles (高周波 turbulence)
    template<typename T>
    T wrinkles(const Point3D<T>& p, const PerlinNoise<T>& pn) {
        return pn.turbulence(p.x * T(4), p.y * T(4), p.z * T(4), 8);
    }

} // namespace pattern

// ========================================================================
// UV マッピング
// ========================================================================

namespace uv {

    /// 球面マッピング: (x,y,z) → (u,v) ∈ [0,1]²
    template<typename T>
    std::pair<T, T> spherical(const Point3D<T>& p, const Point3D<T>& center = {}) {
        auto d = (p - center).normalized();
        T u = T(0.5) + std::atan2(d.z, d.x) / (T(2) * T(3.14159265358979323846));
        T v = T(0.5) - std::asin(std::clamp(d.y, T(-1), T(1))) / T(3.14159265358979323846);
        return {u, v};
    }

    /// 円柱マッピング (Y 軸)
    template<typename T>
    std::pair<T, T> cylindrical(const Point3D<T>& p, const Point3D<T>& center = {},
                                 T height = T(1)) {
        auto d = p - center;
        T u = T(0.5) + std::atan2(d.z, d.x) / (T(2) * T(3.14159265358979323846));
        T v = d.y / height;
        return {u, std::clamp(v, T(0), T(1))};
    }

    /// 平面マッピング (XZ 平面)
    template<typename T>
    std::pair<T, T> planar(const Point3D<T>& p, T scaleU = T(1), T scaleV = T(1)) {
        return {p.x * scaleU - std::floor(p.x * scaleU),
                p.z * scaleV - std::floor(p.z * scaleV)};
    }

} // namespace uv

// ========================================================================
// テクスチャ (色を返す関数オブジェクト)
// ========================================================================

/// パターン + 2色のカラーマップによるテクスチャ
template<typename T>
struct PatternTexture {
    std::function<T(const Point3D<T>&)> patternFn;
    RGB<T> color0{T(0), T(0), T(0)};
    RGB<T> color1{T(1), T(1), T(1)};
    TextureTransform<T> transform;

    RGB<T> evaluate(const Point3D<T>& p, const PerlinNoise<T>& pn) const {
        auto tp = transform.apply(p, pn);
        T v = std::clamp(patternFn(tp), T(0), T(1));
        return color0 * (T(1) - v) + color1 * v;
    }
};

/// 画像テクスチャマッピング
template<typename T>
struct ImageTexture {
    enum class UVMode { Spherical, Cylindrical, Planar };
    const Image<T>* image = nullptr;
    UVMode uvMode = UVMode::Planar;
    Point3D<T> center{};
    T scaleU = T(1), scaleV = T(1);

    RGB<T> evaluate(const Point3D<T>& p) const {
        if (!image) return {T(1), T(0), T(1)};  // マゼンタ (エラー色)
        T u, v;
        if (uvMode == UVMode::Spherical) {
            auto [uu, vv] = uv::spherical(p, center);
            u = uu; v = vv;
        } else if (uvMode == UVMode::Cylindrical) {
            auto [uu, vv] = uv::cylindrical(p, center);
            u = uu; v = vv;
        } else {
            auto [uu, vv] = uv::planar(p, scaleU, scaleV);
            u = uu; v = vv;
        }
        // 画像座標に変換
        int px = static_cast<int>(u * T(image->cols())) % static_cast<int>(image->cols());
        int py = static_cast<int>(v * T(image->rows())) % static_cast<int>(image->rows());
        if (px < 0) px += static_cast<int>(image->cols());
        if (py < 0) py += static_cast<int>(image->rows());
        return {image->at(py, px, 0), image->at(py, px, 1), image->at(py, px, 2)};
    }
};

// ========================================================================
// バンプマッピング (法線摂動)
// ========================================================================

/// パターンベースのバンプ (法線を摂動)
template<typename T>
Point3D<T> bumpNormal(const Point3D<T>& p, const Point3D<T>& normal,
                       const std::function<T(const Point3D<T>&)>& bumpFn,
                       T strength = T(1)) {
    constexpr T h = T(1e-4);
    T fx = bumpFn({p.x + h, p.y, p.z}) - bumpFn({p.x - h, p.y, p.z});
    T fy = bumpFn({p.x, p.y + h, p.z}) - bumpFn({p.x, p.y - h, p.z});
    T fz = bumpFn({p.x, p.y, p.z + h}) - bumpFn({p.x, p.y, p.z - h});
    Point3D<T> grad{fx, fy, fz};
    auto perturbed = normal - grad * (strength / (T(2) * h));
    T len = perturbed.norm();
    return (len > T(1e-12)) ? perturbed * (T(1) / len) : normal;
}

// ========================================================================
// 便利なファクトリ関数
// ========================================================================

/// Perlin noise ベースのパターンテクスチャを生成
template<typename T>
std::function<RGB<T>(const Point3D<T>&)> makeCheckerTexture(
    const RGB<T>& c0, const RGB<T>& c1,
    T scale = T(1)) {
    return [c0, c1, scale](const Point3D<T>& p) -> RGB<T> {
        T v = pattern::checker(Point3D<T>{p.x * scale, p.y * scale, p.z * scale});
        return c0 * (T(1) - v) + c1 * v;
    };
}

template<typename T>
std::function<RGB<T>(const Point3D<T>&)> makeMarbleTexture(
    const RGB<T>& c0, const RGB<T>& c1,
    T scale = T(1), unsigned seed = 0) {
    auto pn = std::make_shared<PerlinNoise<T>>(seed);
    return [pn, c0, c1, scale](const Point3D<T>& p) -> RGB<T> {
        Point3D<T> sp{p.x * scale, p.y * scale, p.z * scale};
        T v = pattern::marble(sp, *pn);
        return c0 * (T(1) - v) + c1 * v;
    };
}

template<typename T>
std::function<RGB<T>(const Point3D<T>&)> makeWoodTexture(
    const RGB<T>& c0, const RGB<T>& c1,
    T scale = T(1), unsigned seed = 0) {
    auto pn = std::make_shared<PerlinNoise<T>>(seed);
    return [pn, c0, c1, scale](const Point3D<T>& p) -> RGB<T> {
        Point3D<T> sp{p.x * scale, p.y * scale, p.z * scale};
        T v = pattern::wood(sp, *pn);
        return c0 * (T(1) - v) + c1 * v;
    };
}

template<typename T>
std::function<RGB<T>(const Point3D<T>&)> makeGraniteTexture(
    const RGB<T>& c0, const RGB<T>& c1,
    T scale = T(1), unsigned seed = 0) {
    auto pn = std::make_shared<PerlinNoise<T>>(seed);
    return [pn, c0, c1, scale](const Point3D<T>& p) -> RGB<T> {
        Point3D<T> sp{p.x * scale, p.y * scale, p.z * scale};
        T v = pattern::granite(sp, *pn);
        return c0 * (T(1) - v) + c1 * v;
    };
}

template<typename T>
std::function<RGB<T>(const Point3D<T>&)> makeBozoTexture(
    const RGB<T>& c0, const RGB<T>& c1,
    T scale = T(1), unsigned seed = 0) {
    auto pn = std::make_shared<PerlinNoise<T>>(seed);
    return [pn, c0, c1, scale](const Point3D<T>& p) -> RGB<T> {
        Point3D<T> sp{p.x * scale, p.y * scale, p.z * scale};
        T v = pattern::bozo(sp, *pn);
        return c0 * (T(1) - v) + c1 * v;
    };
}

/// Perlin noise ベースのバンプマップを生成
template<typename T>
std::function<Point3D<T>(const Point3D<T>&, const Point3D<T>&)> makeNoiseBump(
    T strength = T(1), T scale = T(1), unsigned seed = 0) {
    auto pn = std::make_shared<PerlinNoise<T>>(seed);
    return [pn, strength, scale](const Point3D<T>& p, const Point3D<T>& n) -> Point3D<T> {
        auto fn = [&](const Point3D<T>& q) -> T {
            return pn->noise(q.x * scale, q.y * scale, q.z * scale);
        };
        return bumpNormal(p, n, fn, strength);
    };
}

} // namespace calx::image

#endif // CALX_IMAGE_RENDER_TEXTURE_HPP
