import os
from typing import List
from ctestgen.generator import Var, Enum, Include, Define, ControlStructure, Call
from ctestgen.generator.style_parametres import indent_char, indent_size


class Program:
    def __init__(self, name):
        self.name = name
        self.includes = []
        self.defines = []
        self.enums = []
        self.global_variables = []
        self.functions = []

    def __str__(self):
        program_code = '//Generated by ctestgen generator.\n'
        for include in self.includes:
            program_code += include.get_declaration()
        if self.includes:
            program_code += '\n'

        for define in self.defines:
            program_code += define.get_declaration()
        if self.defines:
            program_code += '\n'

        for enum in self.enums:
            program_code += enum.get_declaration()
        if self.enums:
            program_code += '\n'

        for global_var in self.global_variables:
            program_code += global_var.get_declaration() + ';\n'
        if self.global_variables:
            program_code += '\n'

        for func in self.functions:
            program_code += func.get_declaration()
        return program_code

    def set_name(self, name):
        self.name = name

    def add_include(self, include):
        if isinstance(include, Include):
            self.includes.append(include)

    def add_define(self, define):
        if isinstance(define, Define):
            self.defines.append(define)

    def add_enum(self, enum):
        if isinstance(enum, Enum):
            self.enums.append(enum)

    def add_global_variable(self, global_var):
        if isinstance(global_var, Var):
            self.global_variables.append(global_var)

    def add_function(self, func):
        if isinstance(func, Function):
            self.functions.append(func)

    def write_to_file(self, output_dir):
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)
        with open(output_dir + '/' + self.name + '.c', 'wt') as output_file:
            output_file.write(str(self))


class CodeBlock:
    def __init__(self, *body):
        if body and isinstance(body[0], List):
            body = body[0]
        self.body = body

    def __str__(self):
        block_code = ''
        for expression in self.body:
            if isinstance(expression, ControlStructure):
                block_code += str(expression)
            else:
                block_code += str(expression) + ';\n'
        # Transformations for auto indentation
        block_strings = block_code.split('\n')
        if not block_strings[len(block_strings) - 1]:
            block_strings = block_strings[:len(block_strings) - 1]
        block_code = '\n'.join(map(lambda s: indent_size*indent_char + s, block_strings))
        block_code = '{\n' + block_code + '\n}\n'
        return block_code


class Function:
    def __init__(self, name, return_type, *args):
        if args and isinstance(args[0], List):
            args = args[0]
        self.name = name
        self.return_type = return_type
        self.arguments = args
        self.body = CodeBlock()

    def set_body(self, code_block: CodeBlock):
        assert isinstance(code_block, CodeBlock), 'Types mismatch'
        self.body = code_block

    def get_declaration(self):
        function_code = str(self.return_type) \
                        + ' ' + self.name + '('
        add_comma = False
        for argument in self.arguments:
            if isinstance(argument, Var):
                if add_comma:
                    function_code += ', '
                else:
                    add_comma = True
                function_code += argument.get_declaration()

        function_code += ') ' + str(self.body) + '\n'
        return function_code

    def __str__(self):
        return self.name

    def __call__(self, *args):
        if args and isinstance(args[0], List):
            args = args[0]
        return Call(self, *args)
