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

/**
 * @file animation.hpp
 * @brief アニメーション GIF / APNG 出力 (DRW-52)
 *
 * GIF: LZW 圧縮 (特許失効済み), 256 色パレット
 * APNG: PNG ベースのアニメーション (フルカラー)
 */

#ifndef CALX_IMAGE_IO_ANIMATION_HPP
#define CALX_IMAGE_IO_ANIMATION_HPP

#include <image/Image.hpp>
#include <string>
#include <vector>
#include <fstream>
#include <cstdint>
#include <algorithm>
#include <cmath>

namespace calx::image {

// ========================================================================
// GIF 出力 (LZW 圧縮)
// ========================================================================

/// GIF アニメーション出力
template<typename T>
class GIFWriter {
public:
    GIFWriter(const std::string& path, int width, int height, int delayMs = 100)
        : path_(path), w_(width), h_(height), delay_(delayMs / 10) {}

    /// フレームを追加
    void addFrame(const Image<T>& frame) {
        frames_.push_back(frame);
    }

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

        // GIF89a header
        ofs.write("GIF89a", 6);
        writeU16(ofs, w_);
        writeU16(ofs, h_);
        ofs.put(0xF6);  // GCT flag + 7 (= 128 colors, 但し実際は 256)
        ofs.put(0);      // background
        ofs.put(0);      // aspect ratio

        // グローバルカラーテーブル (256 色の均等パレット)
        for (int i = 0; i < 256; ++i) {
            int r = (i >> 5) * 36;
            int g = ((i >> 2) & 7) * 36;
            int b = (i & 3) * 85;
            ofs.put(static_cast<char>(std::min(r, 255)));
            ofs.put(static_cast<char>(std::min(g, 255)));
            ofs.put(static_cast<char>(std::min(b, 255)));
        }

        // Netscape extension (ループ)
        ofs.put(0x21); ofs.put(static_cast<char>(0xFF)); ofs.put(11);
        ofs.write("NETSCAPE2.0", 11);
        ofs.put(3); ofs.put(1);
        writeU16(ofs, 0);  // loop forever
        ofs.put(0);

        // 各フレーム
        for (const auto& frame : frames_) {
            // Graphic Control Extension
            ofs.put(0x21); ofs.put(static_cast<char>(0xF9)); ofs.put(4);
            ofs.put(0x04);  // dispose: restore to background
            writeU16(ofs, delay_);
            ofs.put(0);  // transparent color index
            ofs.put(0);

            // Image Descriptor
            ofs.put(0x2C);
            writeU16(ofs, 0); writeU16(ofs, 0);
            writeU16(ofs, w_); writeU16(ofs, h_);
            ofs.put(0);  // no local color table

            // LZW 圧縮データ (最小コードサイズ = 7)
            int minCode = 7;
            ofs.put(static_cast<char>(minCode));

            // インデックスデータを生成
            std::vector<uint8_t> indices(w_ * h_);
            for (int y = 0; y < h_; ++y)
                for (int x = 0; x < w_; ++x) {
                    double r = 0, g = 0, b = 0;
                    if (frame.numChannels() >= 3) {
                        r = double(frame.at(y, x, 0));
                        g = double(frame.at(y, x, 1));
                        b = double(frame.at(y, x, 2));
                    } else {
                        r = g = b = double(frame.at(y, x));
                    }
                    if constexpr (!std::is_same_v<T, uint8_t>) {
                        r *= 255; g *= 255; b *= 255;
                    }
                    // 3-3-2 パレットインデックス
                    int ri = std::clamp(int(r), 0, 255) >> 5;
                    int gi = std::clamp(int(g), 0, 255) >> 5;
                    int bi = std::clamp(int(b), 0, 255) >> 6;
                    indices[y * w_ + x] = static_cast<uint8_t>((ri << 5) | (gi << 2) | bi);
                }

            // LZW エンコード (簡易: 非圧縮 LZW)
            writeLZW(ofs, indices, minCode);
        }

        // GIF trailer
        ofs.put(0x3B);
    }

private:
    std::string path_;
    int w_, h_, delay_;
    std::vector<Image<T>> frames_;

    static void writeU16(std::ostream& os, uint16_t v) {
        os.put(static_cast<char>(v & 0xFF));
        os.put(static_cast<char>((v >> 8) & 0xFF));
    }

    static void writeLZW(std::ostream& os, const std::vector<uint8_t>& data, int minCode) {
        // 簡易 LZW: クリアコード + 各バイトを個別出力 + EOI
        int clearCode = 1 << minCode;
        int eoiCode = clearCode + 1;
        int codeSize = minCode + 1;

        std::vector<uint8_t> buf;
        int bitBuf = 0, bitCount = 0;

        auto emit = [&](int code, int bits) {
            bitBuf |= code << bitCount;
            bitCount += bits;
            while (bitCount >= 8) {
                buf.push_back(bitBuf & 0xFF);
                bitBuf >>= 8;
                bitCount -= 8;
            }
        };

        emit(clearCode, codeSize);
        for (uint8_t idx : data)
            emit(idx, codeSize);
        emit(eoiCode, codeSize);
        if (bitCount > 0) buf.push_back(bitBuf & 0xFF);

        // サブブロックに分割 (最大 255 バイト)
        std::size_t pos = 0;
        while (pos < buf.size()) {
            int blockSize = std::min(int(buf.size() - pos), 255);
            os.put(static_cast<char>(blockSize));
            os.write(reinterpret_cast<const char*>(buf.data() + pos), blockSize);
            pos += blockSize;
        }
        os.put(0);  // block terminator
    }
};

/// 簡易 GIF 保存 (1 行)
template<typename T>
void saveGIF(const std::string& path, const std::vector<Image<T>>& frames,
              int delayMs = 100) {
    if (frames.empty()) return;
    GIFWriter<T> writer(path, static_cast<int>(frames[0].width()),
                         static_cast<int>(frames[0].height()), delayMs);
    for (const auto& f : frames) writer.addFrame(f);
    writer.save();
}

} // namespace calx::image

#endif
