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

/**
 * @file ColorSpace.hpp
 * @brief 色空間変換 (DRW-46〜49)
 *
 * DRW-46: sRGB / リニア RGB 変換 (ガンマ補正)
 * DRW-47: HSL / HSV カラーモデル
 * DRW-48: CMYK カラーモデル
 * DRW-49: 16bit / HDR (Image<T> はテンプレートなので型は自由)
 */

#ifndef CALX_IMAGE_TYPES_COLORSPACE_HPP
#define CALX_IMAGE_TYPES_COLORSPACE_HPP

#include <cmath>
#include <algorithm>
#include <array>

namespace calx::image {

// ========================================================================
// DRW-46: sRGB ↔ リニア RGB
// ========================================================================

/// sRGB → リニア RGB (ガンマ展開)
inline double srgbToLinear(double c) {
    return (c <= 0.04045) ? c / 12.92 : std::pow((c + 0.055) / 1.055, 2.4);
}

/// リニア RGB → sRGB (ガンマ圧縮)
inline double linearToSrgb(double c) {
    return (c <= 0.0031308) ? c * 12.92 : 1.055 * std::pow(c, 1.0 / 2.4) - 0.055;
}

/// 画像全体を sRGB → リニア変換
template<typename T>
void imageToLinear(Image<T>& img) {
    for (std::size_t y = 0; y < img.height(); ++y)
        for (std::size_t x = 0; x < img.width(); ++x)
            for (int c = 0; c < std::min(img.numChannels(), 3); ++c)
                img.at(y, x, c) = T(srgbToLinear(double(img.at(y, x, c))));
}

/// 画像全体を リニア → sRGB 変換
template<typename T>
void imageToSrgb(Image<T>& img) {
    for (std::size_t y = 0; y < img.height(); ++y)
        for (std::size_t x = 0; x < img.width(); ++x)
            for (int c = 0; c < std::min(img.numChannels(), 3); ++c)
                img.at(y, x, c) = T(linearToSrgb(double(img.at(y, x, c))));
}

// ========================================================================
// DRW-47: HSL / HSV カラーモデル
// ========================================================================

struct HSV { double h, s, v; };  // H: 0-360, S: 0-1, V: 0-1
struct HSL { double h, s, l; };  // H: 0-360, S: 0-1, L: 0-1

/// RGB [0,1] → HSV
inline HSV rgbToHsv(double r, double g, double b) {
    double mx = std::max({r, g, b}), mn = std::min({r, g, b});
    double d = mx - mn;
    double h = 0, s = (mx > 1e-6) ? d / mx : 0, v = mx;
    if (d > 1e-6) {
        if (mx == r)      h = 60.0 * std::fmod((g - b) / d + 6.0, 6.0);
        else if (mx == g) h = 60.0 * ((b - r) / d + 2.0);
        else              h = 60.0 * ((r - g) / d + 4.0);
    }
    return {h, s, v};
}

/// HSV → RGB [0,1]
inline std::array<double, 3> hsvToRgb(double h, double s, double v) {
    double c = v * s, hp = h / 60.0, x = c * (1.0 - std::abs(std::fmod(hp, 2.0) - 1.0));
    double m = v - c, r, g, b;
    if      (hp < 1) { r=c; g=x; b=0; }
    else if (hp < 2) { r=x; g=c; b=0; }
    else if (hp < 3) { r=0; g=c; b=x; }
    else if (hp < 4) { r=0; g=x; b=c; }
    else if (hp < 5) { r=x; g=0; b=c; }
    else             { r=c; g=0; b=x; }
    return {r+m, g+m, b+m};
}

/// RGB [0,1] → HSL
inline HSL rgbToHsl(double r, double g, double b) {
    double mx = std::max({r, g, b}), mn = std::min({r, g, b});
    double d = mx - mn;
    double l = (mx + mn) / 2.0;
    double h = 0, s = 0;
    if (d > 1e-6) {
        s = (l > 0.5) ? d / (2.0 - mx - mn) : d / (mx + mn);
        if (mx == r)      h = 60.0 * std::fmod((g - b) / d + 6.0, 6.0);
        else if (mx == g) h = 60.0 * ((b - r) / d + 2.0);
        else              h = 60.0 * ((r - g) / d + 4.0);
    }
    return {h, s, l};
}

/// HSL → RGB [0,1]
inline std::array<double, 3> hslToRgb(double h, double s, double l) {
    auto hue2rgb = [](double p, double q, double t) -> double {
        if (t < 0) t += 1; if (t > 1) t -= 1;
        if (t < 1.0/6) return p + (q-p) * 6 * t;
        if (t < 1.0/2) return q;
        if (t < 2.0/3) return p + (q-p) * (2.0/3 - t) * 6;
        return p;
    };
    if (s < 1e-6) return {l, l, l};
    double q = (l < 0.5) ? l * (1 + s) : l + s - l * s;
    double p = 2 * l - q;
    double hk = h / 360.0;
    return {hue2rgb(p, q, hk + 1.0/3), hue2rgb(p, q, hk), hue2rgb(p, q, hk - 1.0/3)};
}

// ========================================================================
// DRW-48: CMYK カラーモデル
// ========================================================================

struct CMYK { double c, m, y, k; };  // 0-1

/// RGB [0,1] → CMYK
inline CMYK rgbToCmyk(double r, double g, double b) {
    double k = 1.0 - std::max({r, g, b});
    if (k >= 1.0) return {0, 0, 0, 1};
    double inv = 1.0 / (1.0 - k);
    return {(1.0 - r - k) * inv, (1.0 - g - k) * inv, (1.0 - b - k) * inv, k};
}

/// CMYK → RGB [0,1]
inline std::array<double, 3> cmykToRgb(double c, double m, double y, double k) {
    double inv = 1.0 - k;
    return {(1.0 - c) * inv, (1.0 - m) * inv, (1.0 - y) * inv};
}

// ========================================================================
// DRW-49: 16bit / HDR
// ========================================================================
// Image<T> はテンプレートなので Image<uint16_t>, Image<float>, Image<double> で
// 自然に 16bit / HDR に対応。追加の型エイリアスのみ提供。

using Image16 = Image<uint16_t>;  // 16bit per channel
using ImageHDR = Image<float>;    // HDR (32bit float per channel)

} // namespace calx::image

#endif
