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

/**
 * @file Composite.hpp
 * @brief Porter-Duff 合成 + ブレンドモード (7b-9)
 */

#ifndef CALX_IMAGE_DRAW_COMPOSITE_HPP
#define CALX_IMAGE_DRAW_COMPOSITE_HPP

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

namespace calx::image {

// ========================================================================
// Porter-Duff 合成演算子
// ========================================================================

enum class CompositeOp {
    Clear, Src, Dst, SrcOver, DstOver,
    SrcIn, DstIn, SrcOut, DstOut,
    SrcAtop, DstAtop, Xor
};

/// Porter-Duff 合成 (4ch RGBA 画像同士)
template<typename T>
void composite(Image<T>& dst, const Image<T>& src, CompositeOp op,
               int ox = 0, int oy = 0) {
    int h = std::min(static_cast<int>(src.height()), static_cast<int>(dst.height()) - oy);
    int w = std::min(static_cast<int>(src.width()), static_cast<int>(dst.width()) - ox);
    int ch = std::min(dst.numChannels(), src.numChannels());
    bool hasSrcA = (src.numChannels() >= 4);
    bool hasDstA = (dst.numChannels() >= 4);

    for (int y = 0; y < h; ++y) {
        int dy = y + oy;
        if (dy < 0 || dy >= static_cast<int>(dst.height())) continue;
        for (int x = 0; x < w; ++x) {
            int dx = x + ox;
            if (dx < 0 || dx >= static_cast<int>(dst.width())) continue;

            T sa = hasSrcA ? src.at(y, x, 3) : T(1);
            T da = hasDstA ? dst.at(dy, dx, 3) : T(1);
            T fa, fb; // src factor, dst factor

            switch (op) {
            case CompositeOp::Clear:   fa=T(0); fb=T(0); break;
            case CompositeOp::Src:     fa=T(1); fb=T(0); break;
            case CompositeOp::Dst:     fa=T(0); fb=T(1); break;
            case CompositeOp::SrcOver: fa=T(1); fb=T(1)-sa; break;
            case CompositeOp::DstOver: fa=T(1)-da; fb=T(1); break;
            case CompositeOp::SrcIn:   fa=da; fb=T(0); break;
            case CompositeOp::DstIn:   fa=T(0); fb=sa; break;
            case CompositeOp::SrcOut:  fa=T(1)-da; fb=T(0); break;
            case CompositeOp::DstOut:  fa=T(0); fb=T(1)-sa; break;
            case CompositeOp::SrcAtop: fa=da; fb=T(1)-sa; break;
            case CompositeOp::DstAtop: fa=T(1)-da; fb=sa; break;
            case CompositeOp::Xor:     fa=T(1)-da; fb=T(1)-sa; break;
            }

            for (int c = 0; c < std::min(ch, 3); ++c)
                dst.at(dy, dx, c) = std::clamp(
                    src.at(y, x, c) * sa * fa + dst.at(dy, dx, c) * da * fb,
                    T(0), T(1));
            if (hasDstA)
                dst.at(dy, dx, 3) = std::clamp(sa * fa + da * fb, T(0), T(1));
        }
    }
}

// ========================================================================
// ブレンドモード (PDF/SVG/Photoshop)
// ========================================================================

enum class BlendMode {
    Normal, Multiply, Screen, Overlay,
    Darken, Lighten, ColorDodge, ColorBurn,
    HardLight, SoftLight, Difference, Exclusion
};

namespace detail_blend {
    template<typename T>
    T blendChannel(T s, T d, BlendMode mode) {
        switch (mode) {
        case BlendMode::Normal:     return s;
        case BlendMode::Multiply:   return s * d;
        case BlendMode::Screen:     return s + d - s * d;
        case BlendMode::Overlay:    return (d < T(0.5)) ? T(2)*s*d : T(1)-T(2)*(T(1)-s)*(T(1)-d);
        case BlendMode::Darken:     return std::min(s, d);
        case BlendMode::Lighten:    return std::max(s, d);
        case BlendMode::ColorDodge: return (s >= T(1)) ? T(1) : std::min(T(1), d / (T(1)-s));
        case BlendMode::ColorBurn:  return (s <= T(0)) ? T(0) : std::max(T(0), T(1)-(T(1)-d)/s);
        case BlendMode::HardLight:  return (s < T(0.5)) ? T(2)*s*d : T(1)-T(2)*(T(1)-s)*(T(1)-d);
        case BlendMode::SoftLight: {
            if (s < T(0.5)) return d - (T(1)-T(2)*s)*d*(T(1)-d);
            T dSqrt = std::sqrt(double(d));
            return T(d + (T(2)*s-T(1))*(T(dSqrt)-d));
        }
        case BlendMode::Difference: return std::abs(s - d);
        case BlendMode::Exclusion:  return s + d - T(2)*s*d;
        }
        return s;
    }
}

/// ブレンドモード付き合成
template<typename T>
void blend(Image<T>& dst, const Image<T>& src, BlendMode mode,
            T opacity = T(1), int ox = 0, int oy = 0) {
    int h = std::min(static_cast<int>(src.height()), static_cast<int>(dst.height()) - oy);
    int w = std::min(static_cast<int>(src.width()), static_cast<int>(dst.width()) - ox);
    int ch = std::min(dst.numChannels(), src.numChannels());

    for (int y = 0; y < h; ++y) {
        int dy = y + oy;
        if (dy < 0 || dy >= static_cast<int>(dst.height())) continue;
        for (int x = 0; x < w; ++x) {
            int dx = x + ox;
            if (dx < 0 || dx >= static_cast<int>(dst.width())) continue;
            for (int c = 0; c < std::min(ch, 3); ++c) {
                T blended = detail_blend::blendChannel(src.at(y,x,c), dst.at(dy,dx,c), mode);
                dst.at(dy,dx,c) = dst.at(dy,dx,c) * (T(1)-opacity) + blended * opacity;
            }
        }
    }
}

} // namespace calx::image

#endif
