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

/**
 * @file font.hpp
 * @brief TrueType/OpenType フォントからのテキスト描画
 *
 * 使用例:
 *   Font font("C:/Windows/Fonts/arial.ttf");
 *   font.drawText(img, 10, 50, "Hello World!", 24, {1.0, 1.0, 1.0});
 *   font.drawText(img, 10, 80, u8"こんにちは", 24, {1.0, 0.5, 0.0});
 */

#ifndef CALX_IMAGE_TEXT_FONT_HPP
#define CALX_IMAGE_TEXT_FONT_HPP

#include <image/text/font_parser.hpp>
#include <image/Image.hpp>
#include <image/Image_impl.hpp>  // fillPolygon 等が必要
#include <string>
#include <vector>
#include <cmath>

namespace calx::image::text {

/// TrueType/OpenType フォント
class Font {
public:
    Font() = default;

    /// ファイルからフォントをロード
    explicit Font(const std::string& path, int faceIndex = 0) {
        load(path, faceIndex);
    }

    bool load(const std::string& path, int faceIndex = 0) {
        return data_.load(path, faceIndex);
    }

    bool isLoaded() const { return data_.isLoaded(); }

    /// Image<T> にテキストを描画
    /// UTF-8 文字列を受け取り、各グリフのアウトラインを fillPolygon で描画
    /// angleDeg: 回転角度 (度, 反時計回り, 省略で 0 = 水平)
    template<typename T>
    void drawText(Image<T>& img, int x, int y,
                  const std::string& text, float fontSize,
                  const std::vector<T>& color,
                  double angleDeg = 0) const {
        if (!isLoaded()) return;
        constexpr double pi = 3.14159265358979323846;
        float scale = fontSize / float(data_.unitsPerEm());
        double cosA = std::cos(angleDeg * pi / 180.0);
        double sinA = std::sin(angleDeg * pi / 180.0);

        // テキスト進行方向 (回転に沿って進む)
        float advX = 0;  // テキスト座標系での累積 X 位置

        // UTF-8 デコード + 描画
        std::size_t i = 0;
        while (i < text.size()) {
            uint32_t cp = decodeUTF8(text, i);
            if (cp == 0) break;

            int gid = data_.getGlyphIndex(cp);
            auto outline = data_.getOutline(cp, fontSize);

            // テキスト座標系での現在位置
            float localX = advX + outline.leftBearing;

            // 全輪郭を1つのポリゴンに統合 (偶奇規則で穴を表現)
            std::vector<std::pair<int,int>> allPts;
            for (const auto& contour : outline.contours) {
                // ローカル座標でポリゴン化
                // contourToPolygon は baseline=0 で呼ぶと (offsetX + p.x, -p.y) を返す
                // つまり py は既に Y 反転済み → そのまま使う
                auto poly = contourToPolygon(contour, localX, float(y));
                if (poly.size() < 3) continue;

                if (std::abs(angleDeg) > 1e-6) {
                    // 回転: (x,y) を原点として回転
                    for (auto& [px, py] : poly) {
                        double lx = double(px) - x;
                        double ly = double(py) - y;
                        px = int(x + cosA * lx - sinA * ly + 0.5);
                        py = int(y + sinA * lx + cosA * ly + 0.5);
                    }
                }

                if (!allPts.empty()) {
                    allPts.push_back(allPts.back());
                    allPts.push_back(poly[0]);
                }
                allPts.insert(allPts.end(), poly.begin(), poly.end());
            }
            if (allPts.size() >= 3)
                img.fillPolygon(allPts, color);

            advX += outline.advanceWidth;

            // カーニング
            prevGlyphId_ = gid;
            std::size_t nextI = i;
            if (nextI < text.size()) {
                uint32_t nextCp = decodeUTF8(text, nextI);
                if (nextCp != 0) {
                    int nextGid = data_.getGlyphIndex(nextCp);
                    int16_t kern = data_.getKerning(gid, nextGid);
                    advX += kern * scale;
                }
            }
        }
    }
    mutable int prevGlyphId_ = 0;

    /// テキストの幅を計算 (ピクセル単位)
    float measureWidth(const std::string& text, float fontSize) const {
        if (!isLoaded()) return 0;
        float width = 0;
        std::size_t i = 0;
        while (i < text.size()) {
            uint32_t cp = decodeUTF8(text, i);
            if (cp == 0) break;
            auto outline = data_.getOutline(cp, fontSize);
            width += outline.advanceWidth;
        }
        return width;
    }

private:
    FontData data_;

    /// UTF-8 → Unicode コードポイント (1文字デコード、i を進める)
    static uint32_t decodeUTF8(const std::string& s, std::size_t& i) {
        if (i >= s.size()) return 0;
        uint8_t b = static_cast<uint8_t>(s[i]);
        uint32_t cp;
        int len;

        if (b < 0x80) { cp = b; len = 1; }
        else if ((b & 0xE0) == 0xC0) { cp = b & 0x1F; len = 2; }
        else if ((b & 0xF0) == 0xE0) { cp = b & 0x0F; len = 3; }
        else if ((b & 0xF8) == 0xF0) { cp = b & 0x07; len = 4; }
        else { ++i; return 0xFFFD; }  // invalid

        for (int j = 1; j < len; ++j) {
            if (i + j >= s.size()) { i += len; return 0xFFFD; }
            cp = (cp << 6) | (static_cast<uint8_t>(s[i + j]) & 0x3F);
        }
        i += len;
        return cp;
    }

    /// グリフ輪郭 → 多角形 (2次ベジェをフラット化)
    /// y 座標を反転 (TTF は上が正、Image は下が正)
    static std::vector<std::pair<int,int>> contourToPolygon(
        const GlyphContour& contour, float offsetX, float baseline,
        int segmentsPerCurve = 4)
    {
        std::vector<std::pair<int,int>> poly;
        const auto& pts = contour.points;
        if (pts.empty()) return poly;

        std::size_t n = pts.size();
        for (std::size_t i = 0; i < n; ++i) {
            const auto& p0 = pts[i];
            const auto& p1 = pts[(i + 1) % n];

            if (p0.onCurve) {
                poly.push_back({int(p0.x + offsetX + 0.5f),
                                int(baseline - p0.y + 0.5f)});
            }

            if (!p0.onCurve && !p1.onCurve) {
                // 暗黙の on-curve 点 (TTF の仕様)
                float mx = (p0.x + p1.x) * 0.5f;
                float my = (p0.y + p1.y) * 0.5f;
                poly.push_back({int(mx + offsetX + 0.5f),
                                int(baseline - my + 0.5f)});
            }

            if (p0.onCurve && !p1.onCurve) {
                // 2次ベジェ: p0 → p1 (制御点) → p2 (次の on-curve or 暗黙)
                const auto& p2raw = pts[(i + 2) % n];
                float p2x, p2y;
                if (p2raw.onCurve) {
                    p2x = p2raw.x; p2y = p2raw.y;
                } else {
                    p2x = (p1.x + p2raw.x) * 0.5f;
                    p2y = (p1.y + p2raw.y) * 0.5f;
                }

                for (int s = 1; s <= segmentsPerCurve; ++s) {
                    float t = float(s) / segmentsPerCurve;
                    float u = 1.0f - t;
                    float x = u*u*p0.x + 2*u*t*p1.x + t*t*p2x;
                    float y = u*u*p0.y + 2*u*t*p1.y + t*t*p2y;
                    poly.push_back({int(x + offsetX + 0.5f),
                                    int(baseline - y + 0.5f)});
                }
            }
        }
        return poly;
    }
};

} // namespace calx::image::text

#endif // CALX_IMAGE_TEXT_FONT_HPP
