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

/**
 * @file Transform2D.hpp
 * @brief 2D アフィン変換 + 変換スタック + クリッピング + マスク (7b-10)
 */

#ifndef CALX_IMAGE_DRAW_TRANSFORM2D_HPP
#define CALX_IMAGE_DRAW_TRANSFORM2D_HPP

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

namespace calx::image {

// ========================================================================
// 2×3 アフィン変換行列
// ========================================================================

/// アフィン変換 [a b tx; c d ty] → (x', y') = (a*x + b*y + tx, c*x + d*y + ty)
struct AffineTransform {
    double a = 1, b = 0, tx = 0;
    double c = 0, d = 1, ty = 0;

    static AffineTransform identity() { return {}; }

    static AffineTransform translate(double x, double y) {
        return {1, 0, x, 0, 1, y};
    }

    static AffineTransform rotate(double angleDeg) {
        constexpr double pi = 3.14159265358979323846;
        double r = angleDeg * pi / 180.0;
        double cs = std::cos(r), sn = std::sin(r);
        return {cs, -sn, 0, sn, cs, 0};
    }

    static AffineTransform scale(double sx, double sy) {
        return {sx, 0, 0, 0, sy, 0};
    }

    static AffineTransform skewX(double angleDeg) {
        return {1, std::tan(angleDeg * 3.14159265358979323846 / 180.0), 0, 0, 1, 0};
    }

    static AffineTransform skewY(double angleDeg) {
        return {1, 0, 0, std::tan(angleDeg * 3.14159265358979323846 / 180.0), 1, 0};
    }

    /// 合成: this * other (this を先に適用)
    AffineTransform operator*(const AffineTransform& o) const {
        return {
            a*o.a + b*o.c,  a*o.b + b*o.d,  a*o.tx + b*o.ty + tx,
            c*o.a + d*o.c,  c*o.b + d*o.d,  c*o.tx + d*o.ty + ty
        };
    }

    /// 点を変換
    std::pair<double, double> apply(double x, double y) const {
        return {a*x + b*y + tx, c*x + d*y + ty};
    }

    /// 逆変換
    AffineTransform inverse() const {
        double det = a*d - b*c;
        if (std::abs(det) < 1e-12) return identity();
        double inv = 1.0 / det;
        return {
             d*inv, -b*inv, (b*ty - d*tx)*inv,
            -c*inv,  a*inv, (c*tx - a*ty)*inv
        };
    }
};

// ========================================================================
// 描画コンテキスト (変換スタック + クリッピング)
// ========================================================================

/// 描画コンテキスト: 変換スタック + クリップ + マスク
template<typename T>
class DrawContext {
public:
    explicit DrawContext(Image<T>& target) : target_(target) {
        transformStack_.push_back(AffineTransform::identity());
    }

    /// 現在の変換行列
    const AffineTransform& transform() const { return transformStack_.back(); }

    /// 変換を追加 (現在の変換に合成)
    void translate(double x, double y) {
        transformStack_.back() = transformStack_.back() * AffineTransform::translate(x, y);
    }
    void rotate(double angleDeg) {
        transformStack_.back() = transformStack_.back() * AffineTransform::rotate(angleDeg);
    }
    void scale(double sx, double sy) {
        transformStack_.back() = transformStack_.back() * AffineTransform::scale(sx, sy);
    }

    /// 変換スタック保存
    void save() {
        transformStack_.push_back(transformStack_.back());
        clipStack_.push_back(clipMask_);
    }

    /// 変換スタック復元
    void restore() {
        if (transformStack_.size() > 1) transformStack_.pop_back();
        if (!clipStack_.empty()) { clipMask_ = clipStack_.back(); clipStack_.pop_back(); }
    }

    /// 矩形クリップ設定
    void clipRect(int x, int y, int w, int h) {
        clipMask_ = std::make_shared<Image<T>>(target_.height(), target_.width(), 1, T(0));
        for (int cy = y; cy < y + h && cy < static_cast<int>(target_.height()); ++cy)
            for (int cx = x; cx < x + w && cx < static_cast<int>(target_.width()); ++cx)
                if (cy >= 0 && cx >= 0) clipMask_->at(cy, cx) = T(1);
    }

    /// α マスク設定 (グレースケール画像)
    void setMask(const Image<T>& mask) {
        clipMask_ = std::make_shared<Image<T>>(mask);
    }

    /// 変換済み座標で線分描画
    void drawLine(double x0, double y0, double x1, double y1,
                  const std::vector<T>& color, int thickness = 1,
                  const LineAAParams& aa = LineAAParams::none()) {
        auto [tx0, ty0] = transform().apply(x0, y0);
        auto [tx1, ty1] = transform().apply(x1, y1);
        if (clipMask_) {
            // クリップ付き: blendPixel をオーバーライドして描画
            // 簡易: 変換後座標で描画し、マスクでマスキング
            target_.drawLine(int(tx0+.5), int(ty0+.5), int(tx1+.5), int(ty1+.5),
                             color, thickness, aa);
            applyClipMask();
        } else {
            target_.drawLine(int(tx0+.5), int(ty0+.5), int(tx1+.5), int(ty1+.5),
                             color, thickness, aa);
        }
    }

    /// 変換済み座標で円描画
    void drawCircle(double cx, double cy, double radius,
                    const std::vector<T>& color, int thickness = 1,
                    const LineAAParams& aa = LineAAParams::none()) {
        auto [tcx, tcy] = transform().apply(cx, cy);
        // スケールを考慮した半径 (簡易: x 方向のスケール)
        double sx = std::sqrt(transform().a * transform().a + transform().c * transform().c);
        target_.drawCircle(int(tcx+.5), int(tcy+.5), int(radius*sx+.5),
                           color, thickness, aa);
    }

    /// 画像にアフィン変換を適用
    static Image<T> warpAffine(const Image<T>& src, const AffineTransform& M,
                                int dstW, int dstH, T padValue = T(0)) {
        Image<T> dst(dstH, dstW, src.numChannels(), padValue);
        auto invM = M.inverse();
        for (int y = 0; y < dstH; ++y)
            for (int x = 0; x < dstW; ++x) {
                auto [sx, sy] = invM.apply(x, y);
                int ix = static_cast<int>(sx), iy = static_cast<int>(sy);
                if (ix >= 0 && ix < static_cast<int>(src.width()) &&
                    iy >= 0 && iy < static_cast<int>(src.height()))
                    for (int c = 0; c < src.numChannels(); ++c)
                        dst.at(y, x, c) = src.at(iy, ix, c);
            }
        return dst;
    }

private:
    Image<T>& target_;
    std::vector<AffineTransform> transformStack_;
    std::shared_ptr<Image<T>> clipMask_;
    std::vector<std::shared_ptr<Image<T>>> clipStack_;

    void applyClipMask() {
        if (!clipMask_) return;
        for (std::size_t y = 0; y < target_.height(); ++y)
            for (std::size_t x = 0; x < target_.width(); ++x) {
                T mask = clipMask_->at(y, x);
                if (mask < T(1))
                    for (int c = 0; c < target_.numChannels(); ++c)
                        target_.at(y, x, c) *= mask;
            }
    }
};

} // namespace calx::image

#endif
