# Copyright 2014-2014 the openage authors. See copying.md for legal info.

from collections import OrderedDict
import re

from .members import IncludeMembers, ContinueReadMember, MultisubtypeMember, GroupMember, StringMember, CharArrayMember, NumberMember, DataMember, RefMember
from .member_access import READ_EXPORT, NOREAD_EXPORT
from .struct_snippet import StructSnippet
from .util import determine_header
from openage.log import dbg
from openage.util import gen_dict_key2lists



# regex for matching type array definitions like int[1337]
# group 1: type name, group 2: length
vararray_match = re.compile("([{0}]+) *\\[([{0}]+)\\] *;?".format("a-zA-Z0-9_"))

# match a simple number
integer_match = re.compile("\\d+")


class StructDefinition:
    """
    input data read from the data files.

    one data set roughly represents one struct in the gamedata dat file.
    it consists of multiple DataMembers, they define the struct members.
    """

    def __init__(self, target):
        """
        create a struct definition from an Exportable
        """

        dbg("generating struct definition from %s" % (repr(target)), lvl=3)

        self.name_struct_file   = target.name_struct_file    #!< name of file where generated struct will be placed
        self.name_struct        = target.name_struct         #!< name of generated C struct
        self.struct_description = target.struct_description  #!< comment placed above generated C struct
        self.prefix             = None
        self.target             = target                     #!< target Exportable class that defines the data format

        #create ordered dict of member type objects from structure definition
        self.members = OrderedDict()
        self.inherited_members = list()
        self.parent_classes = list()

        target_members = target.get_data_format(allowed_modes=(True, READ_EXPORT, NOREAD_EXPORT), flatten_includes=True)
        for is_parent, export, member_name, member_type in target_members:

            if isinstance(member_type, IncludeMembers):
                raise Exception("something went very wrong, inheritance should be flattened at this point.")

            if type(member_name) is not str:
                raise Exception("member name has to be a string, currently: %s<%s>" % (str(member_name), type(member_name)))

            #create member type class according to the defined member type
            if type(member_type) == str:
                array_match = vararray_match.match(member_type)
                if array_match:
                    array_type   = array_match.group(1)
                    array_length = array_match.group(2)

                    if array_type == "char":
                        member = CharArrayMember(array_length)
                    elif array_type in NumberMember.type_scan_lookup:
                        #member = ArrayMember(ref_type=NumberMember, length=array_length, ref_type_params=[array_type])
                        #BIG BIG TODO
                        raise Exception("TODO: implement exporting arrays!")
                    else:
                        raise Exception("member %s has unknown array type %s" % (member_name, member_type))

                elif member_type == "std::string":
                    member = StringMember()
                else:
                    member = NumberMember(member_type)

            elif isinstance(member_type, DataMember):
                member = member_type

            else:
                raise Exception("unknown member type specification!")

            if member is None:
                raise Exception("member %s of struct %s is None" % (member_name, self.name_struct))

            self.members[member_name] = member

            if is_parent:
                self.inherited_members.append(member_name)

        members = target.get_data_format(flatten_includes=False)
        for _, _, _, member_type in members:
            if isinstance(member_type, IncludeMembers):
                self.parent_classes.append(member_type.cls)

    def dynamic_ref_update(self, lookup_ref_data):
        """
        update ourself the with the given reference data.

        data members can be cross references to definitions somewhere else.
        this symbol resolution is done here by replacing the references.
        """

        for member_name, member_type in self.members.items():
            if not isinstance(member_type, RefMember):
                continue

            #this member of self is already resolved
            if member_type.resolved:
                continue

            type_name = member_type.get_effective_type()

            #replace the xref with the real definition
            self.members[type_name] = lookup_ref_data[type_name]

    def generate_struct(self, genfile):
        """
        generate C struct snippet (that should be placed in a header).
        it represents the struct definition in C-code.
        """

        parents = [parent_class.get_effective_type() for parent_class in self.parent_classes]
        snippet = StructSnippet(self.name_struct_file, self.name_struct, self.struct_description, parents)

        snippet.typedefs.add(self.name_struct)

        #add struct members and inheritance parents
        for member_name, member_type in self.members.items():
            if member_name in self.inherited_members:
                #inherited members don't need to be added as they're stored in the superclass
                continue

            snippet.includes |= member_type.get_headers("struct")
            snippet.typerefs |= member_type.get_typerefs()

            snippet.add_members(member_type.get_struct_entries(member_name))

        #append member count variable
        snippet.add_member("static constexpr size_t member_count = %d;" % len(self.members))
        snippet.includes |= determine_header("size_t")

        #add filling function prototypes
        for memname, m in sorted(genfile.member_methods.items()):
            snippet.add_member("%s;" % m.get_signature())
            snippet.includes |= m.get_headers()

        return [ snippet ]

    def generate_struct_implementation(self, genfile):
        """
        create C code for the implementation of the struct functions.
        it is used to fill a struct instance with data of a csv data line.
        """

        #returned snippets
        ret = list()

        #variables to be replaced in the function template
        template_args = {
            "member_count":  self.name_struct + "::member_count",
            "delimiter":     genfile.DELIMITER,
            "struct_name":   self.name_struct,
        }

        #create a list of lines for each parser
        #a parser converts one csv line to struct entries
        parsers = gen_dict_key2lists(genfile.member_methods.keys())

        #place all needed parsers into their requested member function destination
        for idx, (member_name, member_type) in enumerate(self.members.items()):
            for parser in member_type.get_parsers(idx, member_name):
                parsers[parser.destination].append(parser)

        #create parser snippets and return them
        for parser_type, parser_list in parsers.items():
            ret.append(
                genfile.member_methods[parser_type].get_snippet(
                    parser_list,
                    file_name  = self.name_struct_file,
                    class_name = self.name_struct,
                    data       = template_args,
                )
            )

        return ret


    def __str__(self):
        ret = [
            repr(self),
            "\n\tstruct file name: ", self.name_struct_file,
            "\n\tstruct name: ", self.name_struct,
            "\n\tstruct description: ", self.struct_description,
        ]
        return "".join(ret)

    def __repr__(self):
        return "StructDefinition<%s>" % self.name_struct
