#include "core/Palette.h"

#include <cstdio>
#include <fstream>
#include <stdexcept>

namespace bmp2tile
{

namespace
{
uint8_t Quantize2Bit(uint8_t v)
{
    // Round 0-255 to the nearest of {0, 85, 170, 255}, expressed as a 0-3 index.
    return static_cast<uint8_t>((v * 3 + 127) / 255);
}

uint8_t Quantize4Bit(uint8_t v)
{
    // GG channels are just the top 4 bits (see ToGgColorWord's comment
    // in Palette.h) -- no rounding, matching the original tool exactly.
    return static_cast<uint8_t>(v >> 4);
}
}  // namespace

uint8_t ToSmsColorByte(const Color& c)
{
    const uint8_t r = Quantize2Bit(c.R);
    const uint8_t g = Quantize2Bit(c.G);
    const uint8_t b = Quantize2Bit(c.B);
    return static_cast<uint8_t>(r | (g << 2) | (b << 4));
}

uint16_t ToGgColorWord(const Color& c)
{
    const uint16_t r = Quantize4Bit(c.R);
    const uint16_t g = Quantize4Bit(c.G);
    const uint16_t b = Quantize4Bit(c.B);
    return static_cast<uint16_t>(r | (g << 4) | (b << 8));
}

std::vector<Color> PadPaletteTo16(const std::vector<Color>& palette)
{
    if (palette.size() >= 16)
    {
        return palette;
    }
    std::vector<Color> padded = palette;
    padded.resize(16, Color{});  // Color's R/G/B default to 0 -- black
    return padded;
}

std::string FormatPaletteAsSmsHex(const std::vector<Color>& palette)
{
    std::string out;
    char buf[8];
    for (size_t i = 0; i < palette.size(); ++i)
    {
        std::snprintf(buf, sizeof(buf), "$%02X", ToSmsColorByte(palette[i]));
        out += buf;
        if (i + 1 < palette.size()) out += ',';
    }
    return out;
}

std::string FormatPaletteAsGgHex(const std::vector<Color>& palette)
{
    std::string out;
    char buf[8];
    for (size_t i = 0; i < palette.size(); ++i)
    {
        const uint16_t word = ToGgColorWord(palette[i]);
        // Little-endian: low byte ("ggggrrrr") first, high byte ("0000bbbb") second.
        std::snprintf(buf, sizeof(buf), "$%02X,$%02X", word & 0xFF, (word >> 8) & 0xFF);
        out += buf;
        if (i + 1 < palette.size()) out += ',';
    }
    return out;
}

std::string FormatPaletteAsCl123Constants(const std::vector<Color>& palette)
{
    std::string out;
    char buf[32];
    for (size_t i = 0; i < palette.size(); ++i)
    {
        const Color& c = palette[i];
        std::snprintf(buf, sizeof(buf), "cl%d%d%d", Quantize2Bit(c.R), Quantize2Bit(c.G), Quantize2Bit(c.B));
        out += buf;
        if (i + 1 < palette.size()) out += ',';
    }
    return out;
}

void SavePaletteBin(const std::string& path, const std::vector<Color>& palette, PaletteFormat format)
{
    if (format == PaletteFormat::Cl123)
    {
        throw std::runtime_error("cl123 is a symbolic-constant text format, not a binary one -- there's no file to save.");
    }

    std::ofstream f(path, std::ios::binary | std::ios::trunc);
    if (!f)
    {
        throw std::runtime_error("Couldn't open file for writing: " + path);
    }

    for (const Color& c : palette)
    {
        if (format == PaletteFormat::Gg)
        {
            const uint16_t word = ToGgColorWord(c);
            f.put(static_cast<char>(word & 0xFF));
            f.put(static_cast<char>((word >> 8) & 0xFF));
        }
        else
        {
            f.put(static_cast<char>(ToSmsColorByte(c)));
        }
    }

    if (!f)
    {
        throw std::runtime_error("Couldn't write file: " + path);
    }
}

}  // namespace bmp2tile
