"""Reader for ASCII FBX 6100 — an old text format some model packs still use (the origin game's ship packs, for one).

Blender dropped ASCII FBX support, and all three ship packs were exported from Blender 2.71 in
FBX 6.1.0 text form, so `import_scene.fbx` refuses them outright. The format is plain text and the
meshes are simple (one geometry, one material, no rig), so it is cheaper to read the handful of
arrays that matter than to bring in a converter.

Returns meshes in FBX axes and units; the caller converts. `blender_build.py` maps them the same
way Blender's own importer maps a binary FBX, which measurement showed to be (x, z, y) scaled by
the file's UnitScaleFactor/100 — no sign flips.
"""
import re

# A value block runs from `Key:` to the next key or the closing brace of its parent.
_UNTIL_NEXT_KEY = r'(?=\n\s*[A-Za-z_][\w ]*:|\n\s*\})'


def _array(text, key):
    m = re.search(r'\n\s*' + re.escape(key) + r':[ \t]*(.*?)' + _UNTIL_NEXT_KEY, text, re.S)
    if not m:
        return []
    out = []
    for tok in m.group(1).split(','):
        tok = tok.strip()
        if not tok:
            continue
        try:
            out.append(float(tok))
        except ValueError:
            pass
    return out


def _string(text, key):
    m = re.search(r'\n\s*' + re.escape(key) + r':[ \t]*"([^"]*)"', text)
    return m.group(1) if m else None


def _blocks(text, header_re):
    """Every `Name ... {` block matching header_re, sliced by brace depth."""
    for m in re.finditer(header_re, text):
        start = text.find('{', m.end() - 1)
        if start < 0:
            continue
        depth = 0
        for i in range(start, len(text)):
            c = text[i]
            if c == '{':
                depth += 1
            elif c == '}':
                depth -= 1
                if depth == 0:
                    yield m, text[start + 1:i]
                    break


def _prop(text, name, count):
    """A Properties60 entry: Property: "Lcl Translation", "...", "A+",x,y,z"""
    m = re.search(r'Property:\s*"' + re.escape(name) + r'"\s*,[^,]*,[^,]*,(.*)', text)
    if not m:
        return None
    vals = []
    for tok in m.group(1).split(','):
        tok = tok.strip()
        try:
            vals.append(float(tok))
        except ValueError:
            pass
    return vals[:count] if len(vals) >= count else None


def unit_scale(text):
    v = _prop(text, 'UnitScaleFactor', 1)
    return v[0] if v else 1.0


def read_ascii_fbx(path):
    """Parse the mesh models out of an ASCII FBX. Returns (meshes, unit_scale_factor)."""
    with open(path, encoding='utf-8', errors='ignore') as fh:
        text = fh.read()
    if 'Kaydara' in text[:64]:
        raise ValueError('binary FBX passed to the ASCII reader')

    meshes = []
    seen = set()
    for m, body in _blocks(text, r'\n\s*Model:\s*"Model::([^"]+)"\s*,\s*"Mesh"\s*'):
        name = m.group(1)
        verts = _array(body, 'Vertices')
        idx = _array(body, 'PolygonVertexIndex')
        # The Relations section repeats every Model header with an empty body.
        if not verts or not idx or name in seen:
            continue
        seen.add(name)

        polys = []
        cur = []
        for raw in idx:
            i = int(raw)
            if i < 0:
                cur.append(-i - 1)
                if len(cur) >= 3:
                    polys.append(cur)
                cur = []
            else:
                cur.append(i)

        normals = []
        nmap = nref = None
        for _nm, nbody in _blocks(body, r'\n\s*LayerElementNormal:\s*\d+\s*'):
            normals = _array(nbody, 'Normals')
            nmap = _string(nbody, 'MappingInformationType')
            nref = _string(nbody, 'ReferenceInformationType')
            break

        uvs = []
        uv_index = []
        umap = uref = None
        for _um, ubody in _blocks(body, r'\n\s*LayerElementUV:\s*\d+\s*'):
            uvs = _array(ubody, 'UV')
            uv_index = [int(v) for v in _array(ubody, 'UVIndex')]
            umap = _string(ubody, 'MappingInformationType')
            uref = _string(ubody, 'ReferenceInformationType')
            break

        meshes.append({
            'name': name,
            'verts': [(verts[i], verts[i + 1], verts[i + 2]) for i in range(0, len(verts) - 2, 3)],
            'polys': polys,
            'normals': normals,
            'normalMapping': nmap,
            'normalReference': nref,
            'uvs': uvs,
            'uvIndex': uv_index,
            'uvMapping': umap,
            'uvReference': uref,
            'translation': _prop(body, 'Lcl Translation', 3) or [0.0, 0.0, 0.0],
            'rotation': _prop(body, 'Lcl Rotation', 3) or [0.0, 0.0, 0.0],
            'scaling': _prop(body, 'Lcl Scaling', 3) or [1.0, 1.0, 1.0],
        })

    return meshes, unit_scale(text)


if __name__ == '__main__':
    import sys
    ms, us = read_ascii_fbx(sys.argv[1])
    print(f'unit scale {us}, {len(ms)} meshes')
    for mesh in ms:
        vs = mesh['verts']
        if not vs:
            continue
        mn = [min(v[i] for v in vs) for i in range(3)]
        mx = [max(v[i] for v in vs) for i in range(3)]
        print(f"  {mesh['name']}: {len(vs)} verts, {len(mesh['polys'])} polys, "
              f"uv={len(mesh['uvs'])//2} ({mesh['uvMapping']}/{mesh['uvReference']}), "
              f"bounds {[round(a,2) for a in mn]}..{[round(a,2) for a in mx]}")
