#     Copyright 2016, Kay Hayen, mailto:kay.hayen@gmail.com
#
#     Part of "Nuitka", an optimizing Python compiler that is compatible and
#     integrates with CPython, but also works on its own.
#
#     Licensed under the Apache License, Version 2.0 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.
#
""" This module is providing helper functions for complex call re-formulations.

One for each type of call. """

from nuitka.nodes.AssignNodes import (
    ExpressionTargetTempVariableRef,
    ExpressionTargetVariableRef,
    StatementAssignmentVariable,
    StatementReleaseVariable
)
from nuitka.nodes.AttributeNodes import (
    ExpressionAttributeLookup,
    ExpressionBuiltinHasattr
)
from nuitka.nodes.BuiltinDictNodes import ExpressionBuiltinDict
from nuitka.nodes.BuiltinIteratorNodes import ExpressionBuiltinIter1
from nuitka.nodes.BuiltinNextNodes import ExpressionBuiltinNext1
from nuitka.nodes.BuiltinRefNodes import (
    ExpressionBuiltinAnonymousRef,
    ExpressionBuiltinRef
)
from nuitka.nodes.BuiltinTypeNodes import ExpressionBuiltinTuple
from nuitka.nodes.CallNodes import (
    ExpressionCall,
    ExpressionCallEmpty,
    ExpressionCallKeywordsOnly,
    ExpressionCallNoKeywords
)
from nuitka.nodes.ComparisonNodes import (
    ExpressionComparisonIn,
    ExpressionComparisonIsNOT
)
from nuitka.nodes.ConditionalNodes import (
    ExpressionConditionalOR,
    StatementConditional
)
from nuitka.nodes.ConstantRefNodes import (
    ExpressionConstantNoneRef,
    makeConstantRefNode
)
from nuitka.nodes.ContainerMakingNodes import ExpressionMakeTuple
from nuitka.nodes.DictionaryNodes import StatementDictOperationSet
from nuitka.nodes.ExceptionNodes import (
    ExpressionBuiltinMakeException,
    StatementRaiseException
)
from nuitka.nodes.FunctionNodes import (
    ExpressionFunctionBody,
    ExpressionFunctionCall,
    ExpressionFunctionCreation,
    ExpressionFunctionRef
)
from nuitka.nodes.LoopNodes import StatementLoop, StatementLoopBreak
from nuitka.nodes.OperatorNodes import makeBinaryOperationNode
from nuitka.nodes.ParameterSpecs import ParameterSpec
from nuitka.nodes.ReturnNodes import StatementReturn
from nuitka.nodes.SubscriptNodes import (
    ExpressionSubscriptLookup,
    StatementAssignmentSubscript
)
from nuitka.nodes.TypeNodes import (
    ExpressionBuiltinIsinstance,
    ExpressionBuiltinType1
)
from nuitka.nodes.VariableRefNodes import (
    ExpressionTempVariableRef,
    ExpressionVariableRef
)
from nuitka.PythonVersions import (
    getComplexCallSequenceErrorTemplate,
    python_version
)

from .Helpers import (
    makeConditionalStatement,
    makeStatementsSequenceFromStatement,
    makeStatementsSequenceFromStatements
)
from .InternalMod
ule import (
    getInternalModule,
    internal_source_ref,
    once_decorator
)
from .ReformulationTryExceptStatements 
import makeTryExceptSingleHandlerNode
from .ReformulationTryFinallyStatements import makeTryFinallyStatement


def orderArgs(*args):
    if python_version >= 350:
        def weight(arg):
            result = args.index(arg)

            if arg == "kw":
                result += 1.5
            elif arg == "star_arg_list":
                result -= 1.5

            return result

        return tuple(
            sorted(args, key = weight)
        )

    return args

@once_decorator
def getCallableNameDescBody():
    helper_name = "get_callable_name_desc"

    # Equivalent of:
    #
    # Note: The "called_type" is a temporary variable.
    #
    # called_type = type( BuiltinFunctionType )
    #
    # if ininstance( called, (FunctionType, MethodType, BuiltinFunctionType) ):
    #     return called.__name__
    # elif python_version < 3 and isinstance( called, ClassType ):
    #     return called_type.__name__ + " constructor"
    # elif python_version < 3 and isinstance( called, InstanceType ):
    #     return called_type.__name__ + " instance"
    # else:
    #     return called_type.__name__ + " object"

    result = ExpressionFunctionBody(
        provider   = getInternalModule(),
        name       = helper_name,
        doc        = None,
        parameters = ParameterSpec(
            ps_name          = helper_name,
            ps_normal_args   = (
                "called",
            ),
            ps_list_star_arg = None,
            ps_dict_star_arg = None,
            ps_default_count = 0,
            ps_kw_only_args  = ()
        ),
        flags      = set(),
        source_ref = internal_source_ref
    )

    called_variable = result.getVariableForAssignment(
        variable_name = "called"
    )

    def makeNameAttributeLookup(node, attribute_name = "__name__"):
        return ExpressionAttributeLookup(
            source         = node,
            attribute_name = attribute_name,
            source_ref     = internal_source_ref
        )

    functions_case = makeStatementsSequenceFromStatement(
        statement = (
            StatementReturn(
                expression = makeBinaryOperationNode(
                    operator   = "Add",
                    right      = makeConstantRefNode(
                        constant      = "()",
                        source_ref    = internal_source_ref,
                        user_provided = True
                    ),
                    left       = makeNameAttributeLookup(
                        ExpressionVariableRef(
                            variable_name = "called",
                            variable      = called_variable,
                            source_ref    = internal_source_ref
                        )
                    ),
                    source_ref = internal_source_ref

                ),
                source_ref = internal_source_ref
            )
        )
    )

    no_branch = StatementReturn(
        expression = makeBinaryOperationNode(
            operator   = "Add",
            right      = makeConstantRefNode(
                constant      = " object",
                source_ref    = internal_source_ref,
                user_provided = True
            ),
            left       = makeNameAttributeLookup(
                ExpressionBuiltinType1(
                    value      = ExpressionVariableRef(
                        variable_name = "called",
                        variable      = called_variable,
                        source_ref    = internal_source_ref
                    ),
                    source_ref = internal_source_ref
                )
            ),
            source_ref = internal_source_ref
        ),
        source_ref = internal_source_ref
    )

    if python_version < 300:
        instance_case = StatementReturn(
            expression = makeBinaryOperationNode(
                operator   = "Add",
                right      = makeConstantRefNode(
                    constant      = " instance",
                    source_ref    = internal_source_ref,
                    user_provided = True
                ),
                left       = makeNameAttributeLookup(
                    makeNameAttributeLookup(
                        ExpressionVariableRef(
                            variable_name = "called",
                            variable      = called_variable,
                            source_ref    = internal_source_ref
                        ),
                        attribute_name = "__class__",
                    )
                ),
                source_ref = internal_source_ref
            ),
            source_ref = internal_source_ref
        )

        no_branch = makeConditionalStatement(
            condition  = ExpressionBuiltinIsinstance(
                instance   = ExpressionVariableRef(
                    variable_name = "ca