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

/**
 * @file pdf_writer.hpp
 * @brief 簡易 PDF 出力 (DRW-51)
 *
 * 描画操作を PDF ファイルとして出力。
 * PDF 1.4 ベースの最小実装 (テキスト, 線, 矩形, 円)。
 */

#ifndef CALX_IMAGE_IO_PDF_WRITER_HPP
#define CALX_IMAGE_IO_PDF_WRITER_HPP

#include <string>
#include <sstream>
#include <fstream>
#include <vector>
#include <cmath>

namespace calx::image {

class PDFWriter {
public:
    PDFWriter(double widthPt = 595.28, double heightPt = 841.89)  // A4
        : pageW_(widthPt), pageH_(heightPt) {}

    /// 色設定 (RGB 0-1)
    PDFWriter& setStrokeColor(double r, double g, double b) {
        content_ << r << " " << g << " " << b << " RG\n"; return *this;
    }
    PDFWriter& setFillColor(double r, double g, double b) {
        content_ << r << " " << g << " " << b << " rg\n"; return *this;
    }

    /// 線幅
    PDFWriter& setLineWidth(double w) {
        content_ << w << " w\n"; return *this;
    }

    /// 線分
    PDFWriter& line(double x1, double y1, double x2, double y2) {
        content_ << x1 << " " << y1 << " m " << x2 << " " << y2 << " l S\n";
        return *this;
    }

    /// 矩形 (stroke)
    PDFWriter& rect(double x, double y, double w, double h) {
        content_ << x << " " << y << " " << w << " " << h << " re S\n";
        return *this;
    }

    /// 矩形 (fill)
    PDFWriter& fillRect(double x, double y, double w, double h) {
        content_ << x << " " << y << " " << w << " " << h << " re f\n";
        return *this;
    }

    /// 円 (ベジェ近似)
    PDFWriter& circle(double cx, double cy, double r) {
        double k = 0.5522847498 * r;
        content_ << (cx+r) << " " << cy << " m\n"
                 << (cx+r) << " " << (cy+k) << " " << (cx+k) << " " << (cy+r) << " " << cx << " " << (cy+r) << " c\n"
                 << (cx-k) << " " << (cy+r) << " " << (cx-r) << " " << (cy+k) << " " << (cx-r) << " " << cy << " c\n"
                 << (cx-r) << " " << (cy-k) << " " << (cx-k) << " " << (cy-r) << " " << cx << " " << (cy-r) << " c\n"
                 << (cx+k) << " " << (cy-r) << " " << (cx+r) << " " << (cy-k) << " " << (cx+r) << " " << cy << " c\n"
                 << "S\n";
        return *this;
    }

    /// テキスト (組み込みフォント Helvetica)
    PDFWriter& text(double x, double y, const std::string& str, double fontSize = 12) {
        needFont_ = true;
        content_ << "BT\n/F1 " << fontSize << " Tf\n"
                 << x << " " << y << " Td\n(" << escapePdf(str) << ") Tj\nET\n";
        return *this;
    }

    /// ファイルに保存
    void save(const std::string& path) const {
        std::ofstream ofs(path, std::ios::binary);
        if (!ofs) return;

        std::vector<std::size_t> offsets;

        // Header
        ofs << "%PDF-1.4\n";

        // Catalog (obj 1)
        offsets.push_back(ofs.tellp());
        ofs << "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";

        // Pages (obj 2)
        offsets.push_back(ofs.tellp());
        ofs << "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n";

        // Page (obj 3)
        offsets.push_back(ofs.tellp());
        ofs << "3 0 obj\n<< /Type /Page /Parent 2 0 R"
            << " /MediaBox [0 0 " << pageW_ << " " << pageH_ << "]"
            << " /Contents 4 0 R";
        if (needFont_)
            ofs << " /Resources << /Font << /F1 5 0 R >> >>";
        ofs << " >>\nendobj\n";

        // Content stream (obj 4)
        std::string streamData = content_.str();
        offsets.push_back(ofs.tellp());
        ofs << "4 0 obj\n<< /Length " << streamData.size() << " >>\nstream\n"
            << streamData << "\nendstream\nendobj\n";

        // Font (obj 5)
        if (needFont_) {
            offsets.push_back(ofs.tellp());
            ofs << "5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n";
        }

        // Xref
        std::size_t xrefPos = ofs.tellp();
        int nObj = static_cast<int>(offsets.size()) + 1;
        ofs << "xref\n0 " << nObj << "\n";
        ofs << "0000000000 65535 f \n";
        for (auto off : offsets) {
            char buf[32];
            std::snprintf(buf, sizeof(buf), "%010zu 00000 n \n", off);
            ofs << buf;
        }

        // Trailer
        ofs << "trailer\n<< /Size " << nObj << " /Root 1 0 R >>\n";
        ofs << "startxref\n" << xrefPos << "\n%%EOF\n";
    }

private:
    double pageW_, pageH_;
    std::stringstream content_;
    mutable bool needFont_ = false;

    static std::string escapePdf(const std::string& s) {
        std::string out;
        for (char c : s) {
            if (c == '(' || c == ')' || c == '\\') out += '\\';
            out += c;
        }
        return out;
    }
};

} // namespace calx::image

#endif
