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

/**
 * @file Effects.hpp
 * @brief 描画エフェクト (7b-11: ガウシアンぼかし, ドロップシャドウ, カラーマトリクス)
 */

#ifndef CALX_IMAGE_DRAW_EFFECTS_HPP
#define CALX_IMAGE_DRAW_EFFECTS_HPP

#include <image/Image.hpp>
#include <cmath>
#include <algorithm>
#include <vector>
#include <array>

namespace calx::image {

/// ガウシアンぼかし (分離可能カーネル)
template<typename T>
Image<T> gaussianBlur(const Image<T>& img, double sigma) {
    int r = static_cast<int>(std::ceil(sigma * 3));
    if (r < 1) return img;

    // 1D カーネル
    std::vector<double> kernel(2*r+1);
    double sum = 0;
    for (int i = -r; i <= r; ++i) {
        kernel[i+r] = std::exp(-0.5 * i * i / (sigma * sigma));
        sum += kernel[i+r];
    }
    for (auto& k : kernel) k /= sum;

    int h = static_cast<int>(img.height());
    int w = static_cast<int>(img.width());
    int ch = img.numChannels();

    // 水平パス
    Image<T> tmp(h, w, ch, T(0));
    for (int y = 0; y < h; ++y)
        for (int x = 0; x < w; ++x)
            for (int c = 0; c < ch; ++c) {
                double v = 0;
                for (int k = -r; k <= r; ++k) {
                    int nx = std::clamp(x + k, 0, w - 1);
                    v += double(img.at(y, nx, c)) * kernel[k + r];
                }
                tmp.at(y, x, c) = T(v);
            }

    // 垂直パス
    Image<T> result(h, w, ch, T(0));
    for (int y = 0; y < h; ++y)
        for (int x = 0; x < w; ++x)
            for (int c = 0; c < ch; ++c) {
                double v = 0;
                for (int k = -r; k <= r; ++k) {
                    int ny = std::clamp(y + k, 0, h - 1);
                    v += double(tmp.at(ny, x, c)) * kernel[k + r];
                }
                result.at(y, x, c) = T(v);
            }
    return result;
}

/// ドロップシャドウを追加
/// offset: シャドウのずれ, blurRadius: ぼかし, shadowColor: 影の色, opacity: 不透明度
template<typename T>
Image<T> dropShadow(const Image<T>& img, int offsetX, int offsetY,
                     double blurRadius, const std::vector<T>& shadowColor,
                     T opacity = T(0.5)) {
    int h = static_cast<int>(img.height());
    int w = static_cast<int>(img.width());
    int ch = img.numChannels();

    // シャドウマスク: 元画像の非透明部分をオフセット
    Image<T> shadow(h, w, ch, T(0));
    for (int y = 0; y < h; ++y)
        for (int x = 0; x < w; ++x) {
            int sy = y - offsetY, sx = x - offsetX;
            if (sy < 0 || sy >= h || sx < 0 || sx >= w) continue;
            // 元画像の輝度が閾値以上ならシャドウ
            double lum = 0;
            for (int c = 0; c < ch; ++c) lum += double(img.at(sy, sx, c));
            if (lum / ch > 0.01) {
                for (int c = 0; c < ch && c < static_cast<int>(shadowColor.size()); ++c)
                    shadow.at(y, x, c) = shadowColor[c];
            }
        }

    // ぼかし
    if (blurRadius > 0) shadow = gaussianBlur(shadow, blurRadius);

    // 合成: shadow * opacity + img
    Image<T> result(h, w, ch, T(0));
    for (int y = 0; y < h; ++y)
        for (int x = 0; x < w; ++x)
            for (int c = 0; c < ch; ++c) {
                T sv = shadow.at(y, x, c) * opacity;
                T iv = img.at(y, x, c);
                // 元画像が前面
                result.at(y, x, c) = std::clamp(iv + sv * (T(1) - iv), T(0), T(1));
            }
    return result;
}

/// カラーマトリクス (4×5 RGBA → RGBA 変換)
/// matrix[0..3] は出力 R,G,B,A の各行、各行は [Ri,Gi,Bi,Ai,offset]
template<typename T>
Image<T> colorMatrix(const Image<T>& img,
                      const std::array<std::array<double, 5>, 4>& matrix) {
    int h = static_cast<int>(img.height());
    int w = static_cast<int>(img.width());
    int ch = img.numChannels();
    Image<T> result(h, w, ch, T(0));

    for (int y = 0; y < h; ++y)
        for (int x = 0; x < w; ++x) {
            double rgba[4] = {0, 0, 0, 1};
            for (int c = 0; c < std::min(ch, 4); ++c)
                rgba[c] = double(img.at(y, x, c));
            for (int out = 0; out < std::min(ch, 4); ++out) {
                double v = matrix[out][4]; // offset
                for (int in = 0; in < 4; ++in)
                    v += matrix[out][in] * rgba[in];
                result.at(y, x, out) = T(std::clamp(v, 0.0, 1.0));
            }
        }
    return result;
}

/// セピア変換 (カラーマトリクスの便利関数)
template<typename T>
Image<T> sepia(const Image<T>& img) {
    return colorMatrix(img, {{
        {{0.393, 0.769, 0.189, 0, 0}},
        {{0.349, 0.686, 0.168, 0, 0}},
        {{0.272, 0.534, 0.131, 0, 0}},
        {{0,     0,     0,     1, 0}}
    }});
}

} // namespace calx::image

#endif
