import re

from ._regexes import (
    GLOBAL as _GLOBAL,
)
from ._common import (
    log_match,
    parse_var_decl,
    set_capture_groups,
)
from ._compound_decl_body import DECL_BODY_PARSERS
from ._func_body import parse_function_statics as parse_function_body


GLOBAL = set_capture_groups(_GLOBAL, (
    'EMPTY',
    'COMPOUND_LEADING',
    'COMPOUND_KIND',
    'COMPOUND_NAME',
    'FORWARD_KIND',
    'FORWARD_NAME',
    'MAYBE_INLINE_ACTUAL',
    'TYPEDEF_DECL',
    'TYPEDEF_FUNC_PARAMS',
    'VAR_STORAGE',
    'FUNC_INLINE',
    'VAR_DECL',
    'FUNC_PARAMS',
    'FUNC_DELIM',
    'FUNC_LEGACY_PARAMS',
    'VAR_INIT',
    'VAR_ENDING',
))
GLOBAL_RE = re.compile(rf'^ \s* {GLOBAL}', re.VERBOSE)


def parse_globals(source, anon_name):
    for srcinfo in source:
        m = GLOBAL_RE.match(srcinfo.text)
        if not m:
            # We need more text.
            continue
        for item in _parse_next(m, srcinfo, anon_name):
            if callable(item):
                parse_body = item
                yield from parse_body(source)
            else:
                yield item
    else:
        # We ran out of lines.
        if srcinfo is not None:
            srcinfo.done()
        return


def _parse_next(m, srcinfo, anon_name):
    (
     empty,
     # compound type decl (maybe inline)
     compound_leading, compound_kind, compound_name,
     forward_kind, forward_name, maybe_inline_actual,
     # typedef
     typedef_decl, typedef_func_params,
     # vars and funcs
     storage, func_inline, decl,
     func_params, func_delim, func_legacy_params,
     var_init, var_ending,
     ) = m.groups()
    remainder = srcinfo.text[m.end():]

    if empty:
        log_match('global empty', m)
        srcinfo.advance(remainder)

    elif maybe_inline_actual:
        log_match('maybe_inline_actual', m)
        # Ignore forward declarations.
        # XXX Maybe return them too (with an "isforward" flag)?
        if not maybe_inline_actual.strip().endswith(';'):
            remainder = maybe_inline_actual + remainder
        yield srcinfo.resolve(forward_kind, None, forward_name)
        if maybe_inline_actual.strip().endswith('='):
            # We use a dummy prefix for a fake typedef.
            # XXX Ideally this case would not be caught by MAYBE_INLINE_ACTUAL.
            _, name, data = parse_var_decl(f'{forward_kind} {forward_name} fake_typedef_{forward_name}')
            yield srcinfo.resolve('typedef', data, name, parent=None)
            remainder = f'{name} {remainder}'
        srcinfo.advance(remainder)

    elif compound_kind:
        kind = compound_kind
        name = compound_name or anon_name('inline-')
        # Immediately emit a forward declaration.
        yield srcinfo.resolve(kind, name=name, data=None)

        # un-inline the decl.  Note that it might not actually be inline.
        # We handle the case in the "maybe_inline_actual" branch.
        srcinfo.nest(
            remainder,
            f'{compound_leading or ""} {compound_kind} {name}',
        )
        def parse_body(source):
            _parse_body = DECL_BODY_PARSERS[compound_kind]

            data = []  # members
            ident = f'{kind} {name}'
            for item in _parse_body(source, anon_name, ident):
                if item.kind == 'field':
                    data.append(item)
                else:
                    yield item
            # XXX Should "parent" really be None for inline type decls?
            yield srcinfo.resolve(kind, data, name, parent=None)

            srcinfo.resume()
        yield parse_body

    elif typedef_decl:
        log_match('typedef', m)
        kind = 'typedef'
        _, name, data = parse_var_decl(typedef_decl)
        if typedef_func_params:
            return_type = data
            # This matches the data for func declarations.
            data = {
                'storage': None,
                'inline': None,
                'params': f'({typedef_func_params})',
                'returntype': return_type,
                'isforward': True,
            }
        yield srcinfo.resolve(kind, data, name, parent=None)
        srcinfo.advance(remainder)

    elif func_delim or func_legacy_params:
        log_match('function', m)
        kind = 'function'
        _, name, return_type = parse_var_decl(decl)
        func_params = func_params or func_legacy_params
        data = {
            'storage': storage,
            'inline': func_inline,
            'params': f'({func_params})',
            'returntype': return_type,
            'isforward': func_delim == ';',
        }

        yield srcinfo.resolve(kind, data, name, parent=None)
        srcinfo.advance(remainder)

        if func_delim == '{' or func_legacy_params:
            def parse_body(source):
                yield from parse_function_body(source, name, anon_name)
            yield parse_body

    elif var_ending:
        log_match('global variable', m)
        kind = 'variable'
        _, name, vartype = parse_var_decl(decl)
        data = {
            'storage': storage,
            'vartype': vartype,
        }
        yield srcinfo.resolve(kind, data, name, parent=None)

        if var_ending == ',':
            # It was a multi-declaration, so queue up the next one.
            _, qual, typespec, _ = vartype.values()
            remainder = f'{storage or ""} {qual or ""} {typespec} {remainder}'
        srcinfo.advance(remainder)

        if var_init:
            _data = f'{name} = {var_init.strip()}'
            yield srcinfo.resolve('statement', _data, name=None)

    else:
        # This should be unreachable.
        raise NotImplementedError
