import logging
import io
from .options import COptions
from .context import CContext
from .parser import CParser
from .semantics import CSemantics
from .preprocessor import CPreProcessor, prepare_for_parsing
from .codegenerator import CCodeGenerator
from .utils import print_ast


class CBuilder:
    """ C builder that converts C code into ir-code """

    logger = logging.getLogger("cbuilder")

    def __init__(self, arch_info, coptions):
        self.arch_info = arch_info
        self.coptions = coptions
        self.cgen = None

    def build(self, src: io.TextIOBase, filename: str, reporter=None):
        if reporter:
            reporter.heading(2, "C builder")
            reporter.message(
                "Welcome to the C building report for {}".format(filename)
            )
        cdialect = self.coptions["std"]
        self.logger.info("Starting C compilation (%s)", cdialect)

        context = CContext(self.coptions, self.arch_info)
        compile_unit = _parse(src, filename, context)

        if reporter:
            f = io.StringIO()
            print_ast(compile_unit, file=f)
            reporter.heading(2, "C-ast")
            reporter.message("Behold the abstract syntax tree of your C-code")
            reporter.dump_raw_text(f.getvalue())
        cgen = CCodeGenerator(context)
        return cgen.gen_code(compile_unit)

    def _create_ast(self, src, filename):
        return create_ast(
            src, self.arch_info, filename=filename, coptions=self.coptions
        )


def parse_text(text, arch="x86_64"):
    """ Parse given C sourcecode into an AST """
    from ...api import get_arch

    f = io.StringIO(text)
    arch_info = get_arch(arch).info
    coptions = COptions()
    context = CContext(coptions, arch_info)
    return _parse(f, "?", context)


def create_ast(src, arch_info, filename="<snippet>", coptions=None):
    """ Create a C ast from the given source """
    if coptions is None:
        coptions = COptions()
    context = CContext(coptions, arch_info)
    return _parse(src, filename, context)


def _parse(src, filename, context):
    preprocessor = CPreProcessor(context.coptions)
    tokens = preprocessor.process_file(src, filename)
    semantics = CSemantics(context)
    parser = CParser(context.coptions, semantics)
    tokens = prepare_for_parsing(tokens, parser.keywords)
    ast = parser.parse(tokens)
    return ast


def parse_type(text, context, filename="foo.c"):
    """ Parse given C-type AST.

    For example:

    >>> from ppci.api import get_arch
    >>> from ppci.lang.c import parse_type, CContext, COptions
    >>> msp430_arch = get_arch('msp430')
    >>> coptions = COptions()
    >>> context = CContext(coptions, msp430_arch.info)
    >>> ast = parse_type('int[2]', context)
    >>> context.eval_expr(ast.size)
    2
    >>> context.sizeof(ast)
    4
    """
    # TODO: fix + ; hack below:
    src = io.StringIO(text + ";")
    preprocessor = CPreProcessor(context.coptions)
    tokens = preprocessor.process_file(src, filename)
    semantics = CSemantics(context)
    parser = CParser(context.coptions, semantics)
    tokens = prepare_for_parsing(tokens, parser.keywords)
    parser.init_lexer(tokens)
    parser.typedefs = set()
    return parser.parse_typename()
