from dataclasses import dataclass

class EndOfInput(Exception):
    pass

def next_row(ix, rows, advance):
    if ix >= len(rows):
        raise EndOfInput

    if advance:
        while rows[ix][0] == "":
            ix += 1
            if ix >= len(rows):
                raise EndOfInput
    row = rows[ix]
    ix += 1
    return ix, row

@dataclass
class FieldDeclaration:
    offset: int
    name: str
    default: int
    array_length: str

@dataclass
class StructDeclaration:
    name: str
    fields: list[FieldDeclaration]
    size: int

def parse_type_declaration(ix, rows, expected_offset, expected_sizes):
    ix, row = next_row(ix, rows, advance=True)
    assert len(row) in {2, 3}, row
    struct_name, *empty = row
    assert all(e == "" for e in empty)
    fields = []
    last_offset = 0 - expected_offset
    res_ix = 0

    def terminate():
        size = last_offset + expected_offset
        assert size in expected_sizes, size
        return ix, StructDeclaration(
            struct_name,
            fields,
            size
        )

    seen_names = set()

    while True:
        try:
            ix, row = next_row(ix, rows, advance=False)
        except EndOfInput:
            return terminate()
        if row[0] == "":
            return terminate()
        else:
            default = None
            if len(row) == 2:
                _offset, name = row
            elif len(row) == 3:
                _offset, name, _default = row
                if _default.strip() != "":
                    default = int(_default, 16)
            else:
                assert False, row
            offset = int(_offset, 16)
            assert offset == last_offset + expected_offset, (hex(offset), hex(last_offset))
            last_offset = offset
            if name == "":
                name = f"_res{res_ix}"
                res_ix += 1

            if fields and fields[-1].name == name:
                assert offset == fields[-1].offset + (fields[-1].array_length * expected_offset)
                fields[-1].array_length += 1
            else:
                assert name not in seen_names, row
                seen_names.add(name)
                fields.append(FieldDeclaration(offset, name, default, 1))

def parse(rows, expected_offset, expected_sizes):
    ix = 0
    declarations = []
    while True:
        try:
            ix, declaration = parse_type_declaration(ix, rows, expected_offset, expected_sizes)
        except EndOfInput:
            break
        declarations.append(declaration)

    return declarations

def render_initializer(declaration, get_type):
    initializer = f"{declaration.name}("
    padding = " " * len(initializer)
    def start(i):
        if i == 0:
            return initializer
        else:
            return padding

    constructor_fields = [f for f in declaration.fields
                          if (not f.name.startswith('_res')
                              and not f.array_length > 1
                              and f.default is None
                              )]
    for i, field in enumerate(constructor_fields):
        s = start(i)
        type = get_type(field.name)
        comma = ',' if i + 1 < len(constructor_fields) else ''
        yield s + f"const {type} {field.name}" + comma

    if constructor_fields:
        yield padding + ')'
    else:
        yield initializer + ')'

    for i, field in enumerate(declaration.fields):
        value = field.name if not field.name.startswith('_res') else '0'
        value = hex(field.default) if field.default is not None else value
        value = '' if field.array_length > 1 else value
        s = ':' if i == 0 else ','
        yield "  " + s + f" {field.name}({value})"
    yield "{ }"

def render_static_assertions(declaration):
    yield f"static_assert((sizeof ({declaration.name})) == {declaration.size});"
    for field in declaration.fields:
        yield f"static_assert((offsetof (struct {declaration.name}, {field.name})) == 0x{field.offset:02x});"

def render_declaration(declaration, get_type):
    yield f"struct {declaration.name} {{"
    for field in declaration.fields:
        type = get_type(field.name)
        if field.array_length == 1:
            yield f"{type} {field.name};"
        else:
            yield f"{type} {field.name}[{field.array_length}];"
    yield ""
    yield from render_initializer(declaration, get_type)
    yield "};"
    yield from render_static_assertions(declaration)

def render_declarations(namespace, declarations, get_type):
    yield f"namespace {namespace} {{"
    for declaration in declarations:
        yield from render_declaration(declaration, get_type)
        yield ""
    yield "}"

def headers():
    yield "#pragma once"
    yield ""
    yield "#include <cstdint>"
    yield "#include <cstddef>"
    yield ""
