import sys
from dataclasses import dataclass
import enum

from generate import renderer

def calculate_line_columns(line):
    columns = []
    span_start = 0
    span_end = 0
    last_c = None
    for i, c in enumerate(line):
        if c == ' ':
            if last_c != ' ':
                span_start = i
            span_end = i
        elif last_c == ' ':
            assert span_end >= span_start
            if span_end > span_start:
                columns.append(span_end)

        last_c = c


    print(columns)

def debug_columns(lines):
    for line in lines:
        calculate_line_columns(line)

def columnize(line):
    _columns = [18, 45, 51, 65, 70, 82]
    ix = 0
    for col in _columns:
        if ix > len(line):
            yield ""
        else:
            yield line[ix:col+1]
        ix = col+1
    if ix > len(line):
        yield ""
    else:
        yield line[ix:]

def columnize_lines(lines):
    for line in lines:
        if not line.strip():
            continue
        columns = list(columnize(line))
        yield columns

@dataclass
class RegisterLine:
    name: str
    start_address: int
    end_address: int
    bits: tuple[int, int]
    chips: set[str]
    r_w: set[str]
    pipelined: bool
    fifo: bool
    description: str

def parse_address(s):
    assert s.startswith("0x")
    s = s.removeprefix("0x")
    assert s.endswith(")")
    s = s.removesuffix(")")
    addr16s, addr10s = s.split("(")
    addr16 = int(addr16s, 16)
    addr10 = int(addr10s, 10)
    assert addr16 == addr10
    return addr16

def parse_address_range(s):
    if " to " in s:
        start, end = s.split(" to ")
        return parse_address(start), parse_address(end)
    else:
        address = parse_address(s)
        return address, address

def parse_bits(s):
    if s == "n/a":
        return 31, 0
    elif ":" in s:
        high, low = s.split(":")
        return int(high, 10), int(low, 10)
    else:
        v = int(s, 10)
        return v, v

def parse_chips(s):
    valid_chips = {"Chuck", "Bruce*", "Bruce%", "Bruce-1"}
    for chip in s.split("+"):
        assert chip in valid_chips, s
        if chip == "Bruce-1":
            chip = "Bruce1"
        yield chip

def parse_r_w(s):
    valid_r_w = {"R", "R/W", "W"}
    assert s in valid_r_w, s
    return set(map(str.lower, s.split("/")))

def parse_pipelined_fifo(s):
    if   s == "(n/a) / No":
        return False, False
    elif s ==  "No / Yes":
        return False, True
    elif s == "Yes / No":
        return True, False
    elif s == "Yes / Yes":
        return True, True
    elif s == "Yes / n/a":
        return True, False
    elif s == "n/a":
        return False, False
    else:
        assert False, s

def parse(lines):
    next_address = 0
    for columns in columnize_lines(lines):
        name, address_s, *rest = map(str.strip, columns)
        start_address, end_address = parse_address_range(address_s)
        assert next_address == start_address
        next_address = end_address + 4
        if name == 'reserved':
            continue

        #print(columns)
        bits_s, chips_s, r_w_s, pipelined_fifo_s, description = rest
        bits = parse_bits(bits_s)
        chips = set(parse_chips(chips_s))
        r_w = parse_r_w(r_w_s)
        pipelined, fifo = parse_pipelined_fifo(pipelined_fifo_s)

        yield RegisterLine(
            name=name,
            start_address=start_address,
            end_address=end_address,
            bits=bits,
            chips=chips,
            r_w=r_w,
            pipelined=pipelined,
            fifo=fifo,
            description=description,
        )

def format_rw(r_w):
    if r_w == {"r", "w"}:
        return "rw"
    if r_w == {"w"}:
        return " w"
    if r_w == {"r"}:
        return "r "
    return ','.join(sorted(r_w))

def sanitize(name):
    return name.replace("/", "_")

def format_register_line(rl, max_length):
    if rl.start_address == rl.end_address:
        space = ' ' * (max_length - len(rl.name))
        return f"reg32 {sanitize(rl.name)};{space} // ({format_rw(rl.r_w)}) {rl.description}"
    else:
        length = (rl.end_address - rl.start_address) // 4 + 1
        length_s = f"[{length}]"
        space = ' ' * (max_length - (len(rl.name) + len(length_s)))
        return f"reg32 {sanitize(rl.name)}{length_s};{space} // ({format_rw(rl.r_w)}) {rl.description}"

def render_register_struct(rls, max_length):
    last_address = -4
    reserved_ix = 0

    yield "struct voodoo2_reg {"
    for rl in rls:
        assert rl.start_address >= last_address + 4
        if rl.start_address != last_address + 4:
            padding = rl.start_address - (last_address + 4)
            yield f"reg32 _reserved{reserved_ix}[{padding // 4}];"
            reserved_ix += 1
        yield format_register_line(rl, max_length)
        last_address = rl.end_address
    yield "};"

def render_static_assert(rls, max_length):
    for rl in rls:
        if rl.start_address == rl.end_address:
            space = ' ' * (max_length - len(rl.name))
            yield f"static_assert((offsetof (struct voodoo2_reg, {sanitize(rl.name)})){space} == 0x{rl.start_address:03x});"
        else:
            space = ' ' * (max_length - (len(rl.name) + 3))
            yield f"static_assert((offsetof (struct voodoo2_reg, {sanitize(rl.name)}[0])){space} == 0x{rl.start_address:03x});"

def render_all(rls):
    max_length = max(len(rl.name) for rl in rls)
    yield "#pragma once"
    yield ""
    yield "#include <stddef.h>"
    yield "#include <stdint.h>"
    yield ""
    yield "typedef volatile uint32_t reg32;"
    yield ""
    yield from render_register_struct(rls, max_length)
    yield from render_static_assert(rls, max_length)

with open(sys.argv[1], 'r') as f:
    buf = f.read()
lines = buf.split('\n')
rls = list(parse(lines))
render, out = renderer()
render(render_all(rls))
sys.stdout.write(out.getvalue())
