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

/**
 * @file material_presets.hpp
 * @brief 素材別マテリアルプリセット + プロシージャルテクスチャ生成
 *
 * 紙、布、革、ニス塗り木材、車の塗装などのプリセットを提供する。
 * 各プリセットは LayeredMaterial<T> を返し、テクスチャとバンプマップも設定する。
 */

#ifndef CALX_IMAGE_RENDER_MATERIAL_PRESETS_HPP
#define CALX_IMAGE_RENDER_MATERIAL_PRESETS_HPP

#include <image/render/pbr.hpp>
#include <image/render/texture.hpp>
#include <cmath>
#include <memory>

namespace calx::image {

// ========================================================================
// プロシージャルテクスチャ生成 — 素材固有パターン
// ========================================================================

namespace material_pattern {

    // ────────────────────────────────────────────────────────────
    // 紙の繊維バンプ — 異方的 Perlin noise + LPF
    //
    // 繊維方向に引き伸ばした Perlin noise を高さ関数とし、
    // 数値微分の間隔 h を大きく取ることで暗黙的 LPF を適用する。
    // h が大きいほど滑らかな（低周波の）バンプになる。
    // ────────────────────────────────────────────────────────────

    /// 紙の繊維バンプ
    /// strength: バンプの強さ [0,1]
    /// scale: ノイズ周波数 (大きい = 細かい)
    /// anisotropy: 繊維方向 (Z) の引き伸ばし比 (>1 で繊維が長くなる)
    /// filterWidth: LPF 幅 = 数値微分の h (大きいほど滑らか)
    template<typename T>
    std::function<Point3D<T>(const Point3D<T>&, const Point3D<T>&)>
    paperFiberBump(T strength = T(0.3), T scale = T(20), T anisotropy = T(8),
                   T filterWidth = T(0.005), unsigned seed = 42) {
        auto pn = std::make_shared<PerlinNoise<T>>(seed);
        // strength を scale で正規化: Perlin 勾配 ≈ scale なので打ち消す
        T normStr = strength / (scale + T(1));
        return [pn, normStr, scale, anisotropy, filterWidth](
                   const Point3D<T>& p, const Point3D<T>& n) -> Point3D<T> {
            // 異方的 Perlin noise: Z 方向に引き伸ばして繊維感を出す
            auto fiberHeight = [&](const Point3D<T>& q) -> T {
                return pn->noise(q.x * scale, q.y * scale,
                                 q.z * scale / anisotropy);
            };

            // 数値微分 — h = filterWidth が LPF として機能
            T h = filterWidth;
            T fx = fiberHeight({p.x+h, p.y, p.z}) - fiberHeight({p.x-h, p.y, p.z});
            T fz = fiberHeight({p.x, p.y, p.z+h}) - fiberHeight({p.x, p.y, p.z-h});
            Point3D<T> grad{fx, T(0), fz};
            auto perturbed = n - grad * (normStr / (T(2) * h));
            T len = perturbed.norm();
            return (len > T(1e-12)) ? perturbed * (T(1) / len) : n;
        };
    }

    /// 紙の色ムラテクスチャ — 異方的 Perlin noise
    template<typename T>
    std::function<RGB<T>(const Point3D<T>&)>
    paperColorVariation(const RGB<T>& baseColor, T variationStrength = T(0.015),
                        T scale = T(20), T anisotropy = T(8), unsigned seed = 123) {
        auto pn = std::make_shared<PerlinNoise<T>>(seed);
        return [pn, baseColor, variationStrength, scale, anisotropy](
                   const Point3D<T>& p) -> RGB<T> {
            T noise = pn->noise(p.x * scale, p.y * scale,
                                p.z * scale / anisotropy);
            T factor = T(1) + noise * variationStrength;
            return {baseColor.r * factor, baseColor.g * factor, baseColor.b * factor};
        };
    }

    /// 織りパターンの種類
    enum class WeaveType {
        Plain,   // 平織り (1/1) — 最も基本的、綿・リネン
        Twill,   // 綾織り (2/1) — 斜めの畝、デニム・ツイード
        Satin,   // 朱子織り (4/1) — 光沢面が広い、サテン・シルク
    };

    /// 布の織りパターンバンプ (LPF 対応)
    /// strength: バンプ強度 [0,1]
    /// scale: 糸の密度 (大きい = 細かい織り)
    /// weave: 織り方の種類
    /// filterWidth: LPF 幅 (大きいほど滑らか)
    template<typename T>
    std::function<Point3D<T>(const Point3D<T>&, const Point3D<T>&)>
    weaveBump(T strength = T(0.2), T scale = T(30), WeaveType weave = WeaveType::Plain,
              T filterWidth = T(0.003), unsigned seed = 77) {
        auto pn = std::make_shared<PerlinNoise<T>>(seed);
        constexpr T twoPi = T(6.28318530717958647);
        // strength を scale で正規化
        T normStr = strength / (scale * T(0.5) + T(1));
        return [pn, normStr, scale, weave, filterWidth, twoPi](
                   const Point3D<T>& p, const Point3D<T>& n) -> Point3D<T> {
            auto weaveHeight = [&](const Point3D<T>& q) -> T {
                T sx = q.x * scale, sz = q.z * scale;
                // 経糸 (X方向) と緯糸 (Z方向) の周期波
                T warp = std::sin(sx * twoPi);
                T weft = std::sin(sz * twoPi);

                T h = T(0);
                switch (weave) {
                case WeaveType::Plain: {
                    // 平織り: 1 over / 1 under のチェッカー
                    T cell = std::sin(sx * T(3.14159265)) * std::sin(sz * T(3.14159265));
                    h = (cell > T(0)) ? warp : weft;
                    break;
                }
                case WeaveType::Twill: {
                    // 綾織り: 斜めにシフトするパターン (2/1 twill)
                    // 斜めの位相シフトで斜線の畝を作る
                    T phase = std::fmod(sx + sz, T(3));
                    h = (phase < T(2)) ? warp : weft;
                    break;
                }
                case WeaveType::Satin: {
                    // 朱子織り: 経糸が長く浮く (4/1)
                    // 交差点が規則的に分散 → 大部分が経糸面
                    T ix = std::floor(sx), iz = std::floor(sz);
                    int cross = (static_cast<int>(ix) + static_cast<int>(iz) * 3) % 5;
                    h = (cross == 0) ? weft : warp * T(0.3);  // ほとんど経糸面
                    break;
                }
                }
                // Perlin ノイズで糸の太さ・位置のばらつきを追加
                h += pn->noise(sx * T(1.5), sz * T(1.5), T(0)) * T(0.2);
                return h;
            };

            // 数値微分 (LPF として機能)
            T h = filterWidth;
            T fx = weaveHeight({p.x+h, p.y, p.z}) - weaveHeight({p.x-h, p.y, p.z});
            T fz = weaveHeight({p.x, p.y, p.z+h}) - weaveHeight({p.x, p.y, p.z-h});
            Point3D<T> grad{fx, T(0), fz};
            auto perturbed = n - grad * (normStr / (T(2) * h));
            T len = perturbed.norm();
            return (len > T(1e-12)) ? perturbed * (T(1) / len) : n;
        };
    }

    /// 布の色ムラテクスチャ (織りに沿った微妙な明暗差)
    template<typename T>
    std::function<RGB<T>(const Point3D<T>&)>
    weaveColorVariation(const RGB<T>& baseColor, T strength = T(0.02),
                        T scale = T(30), WeaveType weave = WeaveType::Plain,
                        unsigned seed = 88) {
        auto pn = std::make_shared<PerlinNoise<T>>(seed);
        constexpr T twoPi = T(6.28318530717958647);
        return [pn, baseColor, strength, scale, weave, twoPi](
                   const Point3D<T>& p) -> RGB<T> {
            T sx = p.x * scale, sz = p.z * scale;
            T warp = std::sin(sx * twoPi) * T(0.5) + T(0.5);
            T weft = std::sin(sz * twoPi) * T(0.5) + T(0.5);

            T pattern = T(0);
            switch (weave) {
            case WeaveType::Plain: {
                T cell = std::sin(sx * T(3.14159265)) * std::sin(sz * T(3.14159265));
                pattern = (cell > T(0)) ? warp : weft;
                break;
            }
            case WeaveType::Twill: {
                T phase = std::fmod(std::abs(sx + sz), T(3));
                pattern = (phase < T(2)) ? warp : weft;
                break;
            }
            case WeaveType::Satin:
                pattern = warp * T(0.8) + weft * T(0.2);
                break;
            }
            // ノイズで染めムラ
            T noise = pn->noise(p.x * T(10), p.z * T(10), T(5));
            T factor = T(1) + (pattern - T(0.5) + noise * T(0.3)) * strength;
            return {baseColor.r * factor, baseColor.g * factor, baseColor.b * factor};
        };
    }

    /// 後方互換: plainWeaveBump
    template<typename T>
    std::function<Point3D<T>(const Point3D<T>&, const Point3D<T>&)>
    plainWeaveBump(T strength = T(0.2), T scale = T(30), unsigned seed = 77) {
        return weaveBump(strength, scale, WeaveType::Plain, T(0.003), seed);
    }

    /// 革のシボ (粒状凹凸) バンプ
    template<typename T>
    std::function<Point3D<T>(const Point3D<T>&, const Point3D<T>&)>
    leatherGrainBump(T strength = T(0.4), T scale = T(15), unsigned seed = 99) {
        auto pn = std::make_shared<PerlinNoise<T>>(seed);
        return [pn, strength, scale](
                   const Point3D<T>& p, const Point3D<T>& n) -> Point3D<T> {
            std::function<T(const Point3D<T>&)> fn = [&](const Point3D<T>& q) -> T {
                T sx = q.x * scale, sy = q.y * scale, sz = q.z * scale;
                // 大きなシボ (Voronoi 的) + 細かいシワ
                T grain = pn->turbulence(sx, sy, sz, 5);
                T wrinkle = pn->fbm(sx * T(4), sy * T(4), sz * T(4), 3, T(2.3), T(0.4));
                return grain * T(0.6) + wrinkle * T(0.4);
            };
            return bumpNormal(p, n, fn, strength);
        };
    }

} // namespace material_pattern

// ========================================================================
// 素材プリセット
// ========================================================================

namespace material_preset {

    /// 紙の種類
    enum class PaperType {
        CopyPaper,   // コピー用紙 (薄い、白い、平滑)
        Cardboard,   // 厚紙 (厚い、やや黄色い、粗い)
        Washi,       // 和紙 (薄い、繊維が見える、半透明)
        Parchment,   // 羊皮紙風 (やや黄色い、光沢あり)
    };

    /// 紙マテリアルを生成
    template<typename T>
    LayeredMaterial<T> paper(RGB<T> color = {T(0.95), T(0.93), T(0.90)},
                             PaperType type = PaperType::CopyPaper,
                             unsigned seed = 42) {
        LayeredMaterial<T> mat;

        // ── ベース PBR ──
        mat.base.albedo = color;
        mat.base.metallic = T(0);
        mat.base.ior = T(1.5);

        // ── Sheen (繊維による縁光り) ──
        mat.sheenColor = {T(1), T(1), T(1)};

        switch (type) {
        case PaperType::CopyPaper:
            mat.base.roughness = T(0.88);
            mat.base.subsurface = T(0.2);
            mat.sheenWeight = T(0.1);
            mat.sheenRoughness = T(0.8);
            // コピー用紙: 非常に細かい繊維, 浅い凹凸
            mat.base.normalMap = material_pattern::paperFiberBump<T>(
                T(0.06), T(250), T(10), T(0.002), seed);
            mat.texture = material_pattern::paperColorVariation<T>(
                color, T(0.005), T(250), T(10), seed + 1);
            break;

        case PaperType::Cardboard:
            mat.base.roughness = T(0.95);
            mat.base.subsurface = T(0.05);
            mat.base.albedo = {
                color.r * T(0.95),
                color.g * T(0.90),
                color.b * T(0.80)
            };
            mat.sheenWeight = T(0.05);
            mat.sheenRoughness = T(0.9);
            // 厚紙: コピー用紙よりやや粗い
            mat.base.normalMap = material_pattern::paperFiberBump<T>(
                T(0.10), T(150), T(6), T(0.002), seed);
            mat.texture = material_pattern::paperColorVariation<T>(
                mat.base.albedo, T(0.01), T(150), T(6), seed + 1);
            break;

        case PaperType::Washi:
            mat.base.roughness = T(0.90);
            mat.base.subsurface = T(0.45);
            mat.sheenWeight = T(0.3);
            mat.sheenRoughness = T(0.7);
            // 和紙: 繊維が見える → 他より粗めだが浅い
            mat.base.normalMap = material_pattern::paperFiberBump<T>(
                T(0.15), T(100), T(12), T(0.002), seed);
            mat.texture = material_pattern::paperColorVariation<T>(
                color, T(0.015), T(100), T(12), seed + 1);
            break;

        case PaperType::Parchment:
            mat.base.roughness = T(0.6);
            mat.base.subsurface = T(0.15);
            mat.base.albedo = {
                color.r * T(0.92),
                color.g * T(0.88),
                color.b * T(0.75)
            };
            // 軽いクリアコート (ワックス処理)
            mat.coatWeight = T(0.2);
            mat.coatRoughness = T(0.3);
            mat.sheenWeight = T(0.15);
            mat.sheenRoughness = T(0.6);
            // 羊皮紙: 細かく浅い
            mat.base.normalMap = material_pattern::paperFiberBump<T>(
                T(0.08), T(180), T(3), T(0.002), seed);
            mat.texture = material_pattern::paperColorVariation<T>(
                mat.base.albedo, T(0.008), T(180), T(3), seed + 1);
            break;
        }

        return mat;
    }

    /// 布の種類
    enum class ClothType {
        Cotton,   // 綿 (平織り、マットな質感)
        Denim,    // デニム (綾織り、斜めの畝)
        Silk,     // 絹 (朱子織り、異方的光沢)
        Velvet,   // ベルベット (逆フレネル + sheen)
        Linen,    // リネン (平織り、やや粗い)
        Tweed,    // ツイード (綾織り、粗い)
    };

    /// 布マテリアルを生成
    template<typename T>
    LayeredMaterial<T> cloth(RGB<T> color = {T(0.3), T(0.3), T(0.6)},
                             ClothType type = ClothType::Cotton,
                             unsigned seed = 77) {
        LayeredMaterial<T> mat;
        mat.base.albedo = color;
        mat.base.metallic = T(0);
        mat.base.ior = T(1.46);  // 繊維の屈折率

        switch (type) {
        case ClothType::Cotton:
            mat.base.roughness = T(0.9);
            mat.base.subsurface = T(0.15);
            mat.sheenWeight = T(0.3);
            mat.sheenColor = {T(1), T(1), T(1)};
            mat.sheenRoughness = T(0.7);
            mat.base.normalMap = material_pattern::weaveBump<T>(
                T(0.2), T(50), material_pattern::WeaveType::Plain, T(0.003), seed);
            mat.texture = material_pattern::weaveColorVariation<T>(
                color, T(0.015), T(50), material_pattern::WeaveType::Plain, seed + 1);
            break;

        case ClothType::Denim:
            mat.base.roughness = T(0.85);
            mat.base.subsurface = T(0.1);
            mat.sheenWeight = T(0.2);
            mat.sheenColor = {T(1), T(1), T(1)};
            mat.sheenRoughness = T(0.6);
            // 綾織り: 斜めの畝パターン
            mat.base.normalMap = material_pattern::weaveBump<T>(
                T(0.25), T(40), material_pattern::WeaveType::Twill, T(0.003), seed);
            mat.texture = material_pattern::weaveColorVariation<T>(
                color, T(0.02), T(40), material_pattern::WeaveType::Twill, seed + 1);
            break;

        case ClothType::Silk:
            mat.base.roughness = T(0.45);
            mat.base.anisotropy = T(0.6);  // 異方的光沢
            mat.base.anisotropyAngle = T(0);
            mat.sheenWeight = T(0.5);
            mat.sheenColor = color * T(0.3) + RGB<T>{T(0.7), T(0.7), T(0.7)};
            mat.sheenRoughness = T(0.4);
            // 朱子織り: 滑らかで光沢のある面
            mat.base.normalMap = material_pattern::weaveBump<T>(
                T(0.08), T(80), material_pattern::WeaveType::Satin, T(0.004), seed);
            mat.texture = material_pattern::weaveColorVariation<T>(
                color, T(0.01), T(80), material_pattern::WeaveType::Satin, seed + 1);
            break;

        case ClothType::Velvet:
            mat.base.roughness = T(0.95);
            // Velvet BRDF: 逆フレネルで縁が明るく光る
            mat.velvetWeight = T(0.7);
            mat.velvetColor = color * T(0.5) + RGB<T>{T(0.5), T(0.5), T(0.5)};
            mat.velvetRoughness = T(0.4);
            // Sheen も併用
            mat.sheenWeight = T(0.3);
            mat.sheenColor = color * T(0.3) + RGB<T>{T(0.7), T(0.7), T(0.7)};
            mat.sheenRoughness = T(0.3);
            mat.base.normalMap = material_pattern::weaveBump<T>(
                T(0.05), T(100), material_pattern::WeaveType::Plain, T(0.004), seed);
            break;

        case ClothType::Linen:
            mat.base.roughness = T(0.92);
            mat.base.subsurface = T(0.1);
            mat.sheenWeight = T(0.25);
            mat.sheenColor = {T(1), T(1), T(1)};
            mat.sheenRoughness = T(0.75);
            // 平織りだが糸が太め
            mat.base.normalMap = material_pattern::weaveBump<T>(
                T(0.3), T(25), material_pattern::WeaveType::Plain, T(0.003), seed);
            mat.texture = material_pattern::weaveColorVariation<T>(
                color, T(0.025), T(25), material_pattern::WeaveType::Plain, seed + 1);
            break;

        case ClothType::Tweed:
            mat.base.roughness = T(0.93);
            mat.base.subsurface = T(0.08);
            mat.sheenWeight = T(0.15);
            mat.sheenColor = {T(1), T(1), T(1)};
            mat.sheenRoughness = T(0.8);
            // 綾織りで太い糸
            mat.base.normalMap = material_pattern::weaveBump<T>(
                T(0.35), T(20), material_pattern::WeaveType::Twill, T(0.003), seed);
            mat.texture = material_pattern::weaveColorVariation<T>(
                color, T(0.03), T(20), material_pattern::WeaveType::Twill, seed + 1);
            break;
        }

        return mat;
    }

    /// 革マテリアルを生成
    /// worn: 使用感 [0=新品, 1=使い込み] — 粗さとコートの劣化に反映
    template<typename T>
    LayeredMaterial<T> leather(RGB<T> color = {T(0.35), T(0.2), T(0.1)},
                               T worn = T(0.3),
                               unsigned seed = 99) {
        LayeredMaterial<T> mat;
        mat.base.albedo = color;
        mat.base.metallic = T(0);
        mat.base.roughness = T(0.4) + worn * T(0.3);
        mat.base.ior = T(1.5);
        mat.base.subsurface = T(0.1);

        // コート (新品ほど光沢あり)
        mat.coatWeight = std::max(T(0.6) - worn * T(0.5), T(0));
        mat.coatRoughness = T(0.1) + worn * T(0.2);
        mat.coatIOR = T(1.5);

        mat.sheenWeight = T(0.05) + worn * T(0.1);
        mat.sheenRoughness = T(0.8);

        mat.base.normalMap = material_pattern::leatherGrainBump<T>(
            T(0.35), T(15), seed);

        return mat;
    }

    /// ニス塗り木材マテリアルを生成
    template<typename T>
    LayeredMaterial<T> lacqueredWood(RGB<T> color = {T(0.45), T(0.28), T(0.12)},
                                     T glossiness = T(0.8),
                                     unsigned seed = 55) {
        LayeredMaterial<T> mat;
        mat.base.albedo = color;
        mat.base.metallic = T(0);
        mat.base.roughness = T(0.7);
        mat.base.ior = T(1.5);

        // ニスのクリアコート
        mat.coatWeight = glossiness;
        mat.coatRoughness = T(0.05) + (T(1) - glossiness) * T(0.2);
        mat.coatIOR = T(1.55);

        // 木目テクスチャ
        auto pn = std::make_shared<PerlinNoise<T>>(seed);
        mat.texture = [pn, color](const Point3D<T>& p) -> RGB<T> {
            T v = pattern::wood(
                Point3D<T>{p.x * T(5), p.y * T(5), p.z * T(5)}, *pn);
            RGB<T> dark{color.r * T(0.5), color.g * T(0.4), color.b * T(0.3)};
            return color * (T(1) - v) + dark * v;
        };

        return mat;
    }

    /// 車の塗装マテリアルを生成
    template<typename T>
    LayeredMaterial<T> carPaint(RGB<T> color = {T(0.7), T(0.05), T(0.05)},
                                T flakeIntensity = T(0.3),
                                unsigned seed = 200) {
        LayeredMaterial<T> mat;
        mat.base.albedo = color;
        mat.base.metallic = T(0.5);
        mat.base.roughness = T(0.35);
        mat.base.ior = T(1.5);

        // 強いクリアコート
        mat.coatWeight = T(1.0);
        mat.coatRoughness = T(0.03);
        mat.coatIOR = T(1.5);

        // メタリックフレーク (微細バンプ)
        if (flakeIntensity > T(0)) {
            auto pn = std::make_shared<PerlinNoise<T>>(seed);
            mat.base.normalMap = [pn, flakeIntensity](
                const Point3D<T>& p, const Point3D<T>& n) -> Point3D<T> {
                std::function<T(const Point3D<T>&)> fn = [&](const Point3D<T>& q) -> T {
                    return pn->noise(q.x * T(200), q.y * T(200), q.z * T(200));
                };
                return bumpNormal(p, n, fn, flakeIntensity * T(0.3));
            };
        }

        return mat;
    }

} // namespace material_preset

} // namespace calx::image

#endif // CALX_IMAGE_RENDER_MATERIAL_PRESETS_HPP
