# Copyright 2014 Insight Software Consortium.
# Copyright 2004-2008 Roman Yakovenko.
# Distributed under the Boost Software License, Version 1.0.
# See http://www.boost.org/LICENSE_1_0.txt

from pygccxml import utils
from pygccxml import declarations


class default_argument_patcher_t(object):

    def __init__(self, enums):
        object.__init__(self)
        self.__enums = enums

    def __call__(self, decl):
        for arg in decl.arguments:
            if not arg.default_value:
                continue
            fixer = self.__find_fixer(decl, arg)
            if fixer:
                arg.default_value = fixer(decl, arg)

    def __find_fixer(self, func, arg):
        if not arg.default_value:
            return False
        elif self.__is_unqualified_enum(func, arg):
            return self.__fix_unqualified_enum
        elif self.__is_double_call(func, arg):
            return self.__fix_double_call
        elif self.__is_invalid_integral(func, arg):
            return self.__fix_invalid_integral
        elif self.__is_constructor_call(func, arg):
            return self.__fix_constructor_call
        else:
            return None

    def __join_names(self, prefix, suffix):
        if prefix == '::':
            return '::' + suffix
        else:
            return prefix + '::' + suffix

    def __is_unqualified_enum(self, func, arg):
        type_ = declarations.remove_reference(declarations.remove_cv(arg.type))
        if not declarations.is_enum(type_):
            return False
        enum_type = declarations.enum_declaration(type_)
        return enum_type.has_value_name(arg.default_value)

    def __fix_unqualified_enum(self, func, arg):
        type_ = declarations.remove_reference(declarations.remove_cv(arg.type))
        enum_type = declarations.enum_declaration(type_)
        return self.__join_names(
            enum_type.parent.decl_string,
            arg.default_value)

    def __is_invalid_integral(self, func, arg):
        type_ = declarations.remove_reference(declarations.remove_cv(arg.type))
        if not declarations.is_integral(type_):
            return False
        try:
            int(arg.default_value)
            return False
        except:
            return True

    def __fix_invalid_integral(self, func, arg):
        try:
            int(arg.default_value)
            return arg.default_value
        except:
            pass

        try:
            int(arg.default_value, 16)
            if 64 == utils.get_architecture():
                # on 64 bit architecture, gccxml reports 0fffff, which is
                # valid number the problem is that in this case it is so
                # buggy so pygccxml can not fix it users will have to fix the
                # default value manually
                return arg.default_value
            default_value = arg.default_value.lower()
            found_hex = [ch for ch in default_value if ch in 'abcdef']
            if found_hex and not default_value.startswith('0x'):
                int('0x' + default_value, 16)
                return '0x' + default_value
        except:
            pass

        # may be we deal with enum
        parent = func.parent
        while parent:
            found = self.__find_enum(parent, arg.default_value)
            if found:
                if declarations.is_fundamental(arg.type) and ' ' in \
                        arg.type.decl_string:
                    template = '(%s)(%s)'
                else:
                    template = '%s(%s)'
                return template % (arg.type.decl_string,
                                   self.__join_names(
                                       found.parent.decl_string,
                                       arg.default_value))
            else:
                parent = parent.parent
        return arg.default_value

    def __find_enum(self, scope, default_value):
        # this algorithm could be improved: it could take into account
        # 1. unnamed namespaced
        # 2. location within files

        for enum in self.__enums:
            if enum.parent is scope and enum.has_value_name(default_value):
                return enum
        return None

    def __is_double_call(self, func, arg):
        call_invocation = declarations.call_invocation
        dv = arg.default_value
        found1 = call_invocation.find_args(dv)
        if found1 == call_invocation.NOT_FOUND:
            return False
        found2 = call_invocation.find_args(dv, found1[1] + 1)
        if found2 == call_invocation.NOT_FOUND:
            return False
        args1 = call_invocation.args(dv[found1[0]: found1[1] + 1])
        args2 = call_invocation.args(dv[found2[0]: found2[1] + 1])
        return len(args1) == len(args2)

    def __fix_double_call(self, func, arg):
        call_invocation = declarations.call_invocation
        dv = arg.default_value
        found1 = call_invocation.find_args(dv)
        found2 = call_invocation.find_args(dv, found1[1] + 1)
        # args1 = call_invocation.args(dv[ found1[0] : found1[1] + 1 ])
        args2 = call_invocation.args(dv[found2[0]: found2[1] + 1])
        return call_invocation.join(dv[:found1[0]], args2)

    def __is_constructor_call(self, func, arg):
        # if '0.9' in func.compiler:
        #    return False
        call_invocation = declarations.call_invocation
        dv = arg.default_value
        if not call_invocation.is_call_invocation(dv):
            return False
        name = call_invocation.name(dv)
        base_type = declarations.base_type(arg.type)
        if not isinstance(base_type, declarations.declarated_t):
            return False
        decl = base_type.declaration
        return decl.name == name \
            or (isinstance(decl, declarations.class_t)
                and name in [typedef.name for typedef in decl.aliases])

    def __fix_constructor_call(self, func, arg):
        call_invocation = declarations.call_invocation
        dv = arg.default_value
        if not call_invocation.is_call_invocation(dv):
            return False
        base_type = declarations.base_type(arg.type)
        decl = base_type.declaration
        name, args = call_invocation.split(dv)
        if decl.name != name:
            # we have some alias to the class
            relevant_typedefs = [
                typedef for typedef in decl.aliases if typedef.name == name]
            if 1 == len(relevant_typedefs):
                f_q_name = self.__join_names(
                    declarations.full_name(
                        relevant_typedefs[0].parent),
                    name)
            else:  # in this case we can not say which typedef user uses:
                f_q_name = self.__join_names(
                    declarations.full_name(
                        decl.parent),
                    decl.name)
        else:
            f_q_name = self.__join_names(
                declarations.full_name(
                    decl.parent),
                name)

        return call_invocation.join(f_q_name, args)


class casting_operator_patcher_t(object):

    def __init__(self):
        object.__init__(self)

    def __call__(self, decl):
        decl.name = 'operator ' + decl.return_type.decl_string

_casting_oper_patcher_ = casting_operator_patcher_t()


def fix_calldef_decls(decls, enums):
    default_arg_patcher = default_argument_patcher_t(enums)
    # decls should be flat list of all declarations, you want to apply patch on
    for decl in decls:
        default_arg_patcher(decl)
        if isinstance(decl, declarations.casting_operator_t):
            _casting_oper_patcher_(decl)
