using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

// Extracts the cockpit label cells from the GIMP source art (riojoy.xcf) into a
// regions.json in OverlayTemplate shape: each named text layer becomes a region
// (Name = layer name, geometry = layer box, font/size from the gimp-text-layer
// parasite). Replaces eyeballing coordinates out of GIMP by hand.
//
//   dotnet run --project tools/XcfRegionExtract [-- <in.xcf> <out.json>]
//
// Defaults assume it is run from the repo root. XCF is big-endian; pointers are
// 32-bit for versions < 11. Only layer metadata is read (no tile/pixel data), so
// tile compression does not matter.

string inPath = args.Length > 0 ? args[0] : "docs/reference/customBackground/riojoy.xcf";
string outPath = args.Length > 1 ? args[1] : "docs/reference/customBackground/regions.json";
// Base cockpit image (the wallpaper background), a sibling of regions.json.
string baseImage = args.Length > 2 ? args[2] : "riojoy.png";

byte[] d = File.ReadAllBytes(inPath);
int pos = 0;

uint U32() { uint v = (uint)((d[pos] << 24) | (d[pos + 1] << 16) | (d[pos + 2] << 8) | d[pos + 3]); pos += 4; return v; }
int I32() => (int)U32();
ulong U64() { ulong v = 0; for (int i = 0; i < 8; i++) v = (v << 8) | d[pos++]; return v; }

string magic = Encoding.ASCII.GetString(d, 0, 9);   // "gimp xcf "
string ver = Encoding.ASCII.GetString(d, 9, 4);     // "file" (=v0) or "vNNN"
if (magic != "gimp xcf ") { Console.Error.WriteLine($"Not an XCF file: '{magic}'"); return 1; }
bool ptr64 = ver[0] == 'v' && int.TryParse(ver.Substring(1, 3), out int vn) && vn >= 11;
long Ptr() => ptr64 ? (long)U64() : U32();

pos = 14;
int width = (int)U32(), height = (int)U32();
_ = U32(); // base type
Console.WriteLine($"xcf ver='{ver}' canvas={width}x{height}");

string XcfString()
{
    uint len = U32();
    if (len == 0) return "";
    string s = Encoding.UTF8.GetString(d, pos, (int)len - 1); // length includes trailing NUL
    pos += (int)len;
    return s;
}

void SkipProps()
{
    while (true) { uint t = U32(); uint l = U32(); if (t == 0) break; pos += (int)l; }
}

pos = 26;        // header(14) + width(4) + height(4) + base(4)
SkipProps();     // image property list
var layerPtrs = new List<long>();
while (true) { long p = Ptr(); if (p == 0) break; layerPtrs.Add(p); }

var fontRx = new Regex("\\(font \"([^\"]*)\"\\)");
var sizeRx = new Regex("\\(font-size ([0-9.]+)");
var colorRx = new Regex("\\(color \\(color-rgb ([0-9.]+) ([0-9.]+) ([0-9.]+)\\)\\)");

static string? ToHex(Match m)
{
    if (!m.Success) return null;
    int Byte(int g) => (int)Math.Round(double.Parse(m.Groups[g].Value, CultureInfo.InvariantCulture) * 255);
    return $"#{Byte(1):X2}{Byte(2):X2}{Byte(3):X2}";
}

// Cockpit-specific orientation: these cells' labels read 90° counter-clockwise
// (the vertical button column b-10..b-1F and the center title a-00). GIMP doesn't
// record a simple text-layer rotation, so it is applied here by name.
var rotateCcw90 = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "a-00" };
for (int a = 0x10; a <= 0x1F; a++) rotateCcw90.Add($"b-{a:X2}");

var regions = new List<object>();
foreach (long lp in layerPtrs)
{
    pos = (int)lp;
    uint lw = U32(), lh = U32(); _ = U32(); // width, height, type
    string name = XcfString();
    int dx = 0, dy = 0; string? parasite = null;

    while (true)
    {
        uint t = U32(); uint l = U32();
        if (t == 0) break;
        int next = pos + (int)l;
        if (t == 15) { dx = I32(); dy = I32(); }     // PROP_OFFSETS
        else if (t == 21)                             // PROP_PARASITES
        {
            while (pos < next)
            {
                string pname = XcfString();
                _ = U32();                            // flags
                uint psize = U32();
                string pdata = Encoding.UTF8.GetString(d, pos, (int)psize); pos += (int)psize;
                if (pname == "gimp-text-layer") parasite = pdata;
            }
        }
        pos = next;
    }

    if (parasite is null) continue;                   // only text layers are label cells
    string font = fontRx.Match(parasite) is { Success: true } fm ? fm.Groups[1].Value : "Sans";
    string sizeStr = sizeRx.Match(parasite) is { Success: true } sm ? sm.Groups[1].Value : "12";
    double sizePx = double.TryParse(sizeStr, NumberStyles.Float, CultureInfo.InvariantCulture, out double sp) ? sp : 12;
    string? colorHex = ToHex(colorRx.Match(parasite));

    regions.Add(new
    {
        Name = name,
        X = dx,
        Y = dy,
        Width = (int)lw,
        Height = (int)lh,
        FontFamily = string.IsNullOrEmpty(font) ? "Sans" : font,
        FontSizePx = sizePx,
        ColorHex = colorHex,
        RotationDegrees = rotateCcw90.Contains(name) ? 90.0 : (double?)null,
    });
}

var template = new { BaseImagePath = baseImage, Width = width, Height = height, Regions = regions };
var opts = new JsonSerializerOptions
{
    WriteIndented = true,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
File.WriteAllText(outPath, JsonSerializer.Serialize(template, opts));
Console.WriteLine($"Wrote {regions.Count} regions -> {outPath}");
return 0;
