#     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.
#
""" Optimize calls to built-in references to specific built-in calls.

For built-in name references, we check if it's one of the supported built-in
types, and then specialize for the ones, where it makes sense.
"""

from logging import warning

from nuitka.Builtins import calledWithBuiltinArgumentNamesDecorator
from nuitka.nodes.AssignNodes import (
    ExpressionTargetTempVariableRef,
    ExpressionTempVariableRef,
    StatementAssignmentVariable,
    StatementDelVariable
)
from nuitka.nodes.AttributeNodes import (
    ExpressionAttributeLookup,
    ExpressionBuiltinGetattr,
    ExpressionBuiltinHasattr,
    ExpressionBuiltinSetattr
)
from nuitka.nodes.BuiltinDecodingNodes import (
    ExpressionBuiltinChr,
    ExpressionBuiltinOrd
)
from nuitka.nodes.BuiltinDictNodes import ExpressionBuiltinDict
from nuitka.nodes.BuiltinFormatNodes import (
    ExpressionBuiltinAscii,
    ExpressionBuiltinBin,
    ExpressionBuiltinFormat,
    ExpressionBuiltinHex,
    ExpressionBuiltinId,
    ExpressionBuiltinOct
)
from nuitka.nodes.BuiltinHashNodes import ExpressionBuiltinHash
from nuitka.nodes.BuiltinIteratorNodes import (
    ExpressionBuiltinIter1,
    ExpressionBuiltinIter2
)
from nuitka.nodes.BuiltinLenNodes import ExpressionBuiltinLen
from nuitka.nodes.BuiltinNextNodes import (
    ExpressionBuiltinNext1,
    ExpressionBuiltinNext2
)
from nuitka.nodes.BuiltinOpenNodes import ExpressionBuiltinOpen
from nuitka.nodes.BuiltinRangeNodes import (
    ExpressionBuiltinRange1,
    ExpressionBuiltinRange2,
    ExpressionBuiltinRange3,
    ExpressionBuiltinXrange1,
    ExpressionBuiltinXrange2,
    ExpressionBuiltinXrange3
)
from nuitka.nodes.BuiltinRefNodes import (
    ExpressionBuiltinAnonymousRef,
    ExpressionBuiltinOriginalRef,
    ExpressionBuiltinRef
)
from nuitka.nodes.BuiltinTypeNodes import (
    ExpressionBuiltinBool,
    ExpressionBuiltinBytearray,
    ExpressionBuiltinComplex,
    ExpressionBuiltinFloat,
    ExpressionBuiltinInt,
    ExpressionBuiltinList,
    ExpressionBuiltinSet,
    ExpressionBuiltinStr,
    ExpressionBuiltinTuple
)
from nuitka.nodes.BuiltinVarsNodes import ExpressionBuiltinVars
from nuitka.nodes.CallNodes import (
    ExpressionCallEmpty,
    ExpressionCallNoKeywords
)
from nuitka.nodes.ClassNodes import ExpressionBuiltinType3
from nuitka.nodes.ComparisonNodes import ExpressionComparisonIs
from nuitka.nodes.ConditionalNodes import (
    ExpressionConditional,
    StatementConditional
)
from nuitka.nodes.ConstantRefNodes import makeConstantRefNode
from nuitka.nodes.ExecEvalNodes import (
    ExpressionBuiltinCompile,
    ExpressionBuiltinEval
)
from nuitka.nodes.GlobalsLocalsNodes import (
    ExpressionBuiltinDir1,
    ExpressionBuiltinGlobals,
    ExpressionBuiltinLocals
)
from nuitka.nodes.ImportNodes import ExpressionBuiltinImport
from nuitka.nodes.NodeMakingHelpers import (
    makeRaiseExceptionReplacementExpression,
    makeRaiseExceptionReplacementExpressionFromInstance,
    wrapExpressionWithSideEffects
)
from nuitka.nodes.OperatorNodes import (
    ExpressionOperationNOT,
    ExpressionOperationUnary
)
from nuitka.nodes.OutlineNodes import ExpressionOutlineBody
from nuitka.nodes.ReturnNodes import StatementReturn
from nuitka.nodes.SliceNodes import ExpressionBuiltinSlice
from nuitka.nodes.StatementNodes import StatementsSequence
from nuitka.nodes.TypeNodes import (
    ExpressionBuiltinIsinstance,
    ExpressionBuiltinSuper,
    ExpressionBuiltinType1
)
from nuitka.nodes.VariableRefNodes import ExpressionVariableRef
from nuitka.Options import isDebug, shallMakeModule
from nuitka.PythonVersions import python_version
from nuitka.tree.Helpers import (
    makeSequenceCreationOrConstant,
    makeStatementsSequence,
    makeStatementsSequenceFromStatement
)
from nuitka.tree.ReformulationExecStatements import wrapEvalGlobalsAndLocals
from nuitka.tree.ReformulationTryFinallyStatements import \
    makeTryFinallyStatement

from . import BuiltinOptimization


def dir_extractor(node):
    def buildDirEmptyCase(source_ref):
        if node.getParentVariableProvider().isCompiledPythonModule():
            source = ExpressionBuiltinGlobals(
                source_ref = source_ref
            )
        else:
            source = ExpressionBuiltinLocals(
                source_ref = source_ref
            )

        result = ExpressionCallEmpty(
            called     = ExpressionAttributeLookup(
                source         = source,
                attribute_name = "keys",
                source_ref     = source_ref
            ),
            source_ref = source_ref
        )

        # For Python3, keys doesn't really return values, but instead a handle
        # only.
        if python_version >= 300:
            result = ExpressionBuiltinList(
                value      = result,
                source_ref = source_ref
            )

        return result


    return BuiltinOptimization.extractBuiltinArgs(
        node                = node,
        builtin_class       = ExpressionBuiltinDir1,
        builtin_spec        = BuiltinOptimization.builtin_dir_spec,
        empty_special_class = buildDirEmptyCase
    )


def vars_extractor(node):
    def selectVarsEmptyClass(source_ref):
        if node.getParentVariableProvider().isCompiledPythonModule():
            return ExpressionBuiltinGlobals(
                source_ref = source_ref
            )
        else:
            return ExpressionBuiltinLocals(
                source_ref = source_ref
            )

    return BuiltinOptimization.extractBuiltinArgs(
        node                = node,
        builtin_class       = ExpressionBuiltinVars,
        builtin_spec        = BuiltinOptimization.builtin_vars_spec,
        empty_special_class = selectVarsEmptyClass
    )


def import_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinImport,
        builtin_spec  = BuiltinOptimization.builtin_import_spec
    )


def type_extractor(node):
    args = node.getCallArgs()

    if args is None:
        iter_length = 0
    else:
        iter_length = args.getIterationLength()

    if iter_length == 1:
        return BuiltinOptimization.extractBuiltinArgs(
            node          = node,
            builtin_class = ExpressionBuiltinType1,
            builtin_spec  = BuiltinOptimization.builtin_type1_spec
        )
    elif iter_length == 3:
        return BuiltinOptimization.extractBuiltinArgs(
            node          = node,
            builtin_class = ExpressionBuiltinType3,
            builtin_spec  = BuiltinOptimization.builtin_type3_spec
        )
    else:
        return makeRaiseExceptionReplacementExpressionFromInstance(
            expression = node,
            exception  = TypeError("type() takes 1 or 3 arguments")
        )

def iter_extractor(node):
    @calledWithBuiltinArgumentNamesDecorator
    def wrapIterCreation(callable_arg, sentinel, source_ref):
        if sentinel is None:
            return ExpressionBuiltinIter1(
                value      = callable_arg,
                source_ref = source_ref
            )
        else:
            return ExpressionBuiltinIter2(
                callable_arg = callable_arg,
                sentinel     = sentinel,
                source_ref   = source_ref
            )

    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = wrapIterCreation,
        builtin_spec  = BuiltinOptimization.builtin_iter_spec
    )


def next_extractor(node):
    # Split up next with and without defaults, they are not going to behave
    # really very similar.
    def selectNextBuiltinClass(iterator, default, source_ref):
        if default is None:
            return ExpressionBuiltinNext1(
                value      = iterator,
                source_ref = source_ref
            )
        else:
            return ExpressionBuiltinNext2(
                iterator   = iterator,
                default    = default,
                source_ref = source_ref
            )

    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = selectNextBuiltinClass,
        builtin_spec  = BuiltinOptimization.builtin_iter_spec
    )


def dict_extractor(node):
    # The "dict" built-in is a bit strange in that it accepts a position
    # parameter, or not, but won't have a default value.
    def wrapExpressionBuiltinDictCreation(positional_args, dict_star_arg,
                                          source_ref):
        if len(positional_args) > 1:

            result = makeRaiseExceptionReplacementExpressionFromInstance(
                expression = node,
                exception  = TypeError(
                    "dict expected at most 1 arguments, got %d" % (
                        len(positional_args)
                    )
                )
            )

            result = wrapExpressionWithSideEffects(
                side_effects = positional_args,
                old_node     = node,
                new_node     = result
            )

            if dict_star_arg:
                result = wrapExpressionWithSideEffects(
                    side_effects = dict_star_arg,
                    old_node     = node,
                    new_node     = result
                )

            return result

        return ExpressionBuiltinDict(
            pos_arg    = positional_args[0] if positional_args else None,
            pairs      = dict_star_arg,
            source_ref = source_ref
        )

    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = wrapExpressionBuiltinDictCreation,
        builtin_spec  = BuiltinOptimization.builtin_dict_spec
    )

def chr_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinChr,
        builtin_spec  = BuiltinOptimization.builtin_chr_spec
    )

def ord_extractor(node):
    def makeOrd0(source_ref):
        # pylint: disable=W0613

        return makeRaiseExceptionReplacementExpressionFromInstance(
            expression = node,
            exception  = TypeError("ord() takes exactly one argument (0 given)")
        )

    return BuiltinOptimization.extractBuiltinArgs(
        node                = node,
        builtin_class       = ExpressionBuiltinOrd,
        builtin_spec        = BuiltinOptimization.builtin_ord_spec,
        empty_special_class = makeOrd0
    )

def bin_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinBin,
        builtin_spec  = BuiltinOptimization.builtin_bin_spec
    )

def oct_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinOct,
        builtin_spec  = BuiltinOptimization.builtin_oct_spec
    )

def hex_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinHex,
        builtin_spec  = BuiltinOptimization.builtin_hex_spec
    )

def id_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinId,
        builtin_spec  = BuiltinOptimization.builtin_id_spec
    )

def repr_extractor(node):
    def makeReprOperator(operand, source_ref):
        return ExpressionOperationUnary(
            operator   = "Repr",
            operand    = operand,
            source_ref = source_ref
        )

    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = makeReprOperator,
        builtin_spec  = BuiltinOptimization.builtin_repr_spec
    )


if python_version >= 300:
    def ascii_extractor(node):
        return BuiltinOptimization.extractBuiltinArgs(
            node          = node,
            builtin_class = ExpressionBuiltinAscii,
            builtin_spec  = BuiltinOptimization.builtin_repr_spec
        )


def range_extractor(node):
    def selectRangeBuiltin(low, high, step, source_ref):
        if high is None:
            return ExpressionBuiltinRange1(
                low        = low,
                source_ref = source_ref
            )
        elif step is None:
            return ExpressionBuiltinRange2(
                low        = low,
                high       = high,
                source_ref = source_ref
            )
        else:
            return ExpressionBuiltinRange3(
                low        = low,
                high       = high,
                step       = step,
                source_ref = source_ref
            )

    def makeRange0(source_ref):
        # pylint: disable=W0613

        return makeRaiseExceptionReplacementExpressionFromInstance(
            expression = node,
            exception  = TypeError("range expected at least 1 arguments, got 0")
        )

    return BuiltinOptimization.extractBuiltinArgs(
        node                = node,
        builtin_class       = selectRangeBuiltin,
        builtin_spec        = BuiltinOptimization.builtin_range_spec,
        empty_special_class = makeRange0
    )



def xrange_extractor(node):
    def selectXrangeBuiltin(low, high, step, source_ref):
        if high is None:
            return ExpressionBuiltinXrange1(
                low        = low,
                source_ref = source_ref
            )
        elif step is None:
            return ExpressionBuiltinXrange2(
                low        = low,
                high       = high,
                source_ref = source_ref
            )
        else:
            return ExpressionBuiltinXrange3(
                low        = low,
                high       = high,
                step       = step,
                source_ref = source_ref
            )

    def makeXrange0(source_ref):
        # pylint: disable=W0613

        return makeRaiseExceptionReplacementExpressionFromInstance(
            expression = node,
            exception  = TypeError(
                "xrange requires 1-3 int arguments"
                    if python_version < 300 else
                "range expected 1 arguments, got 0"
            )
        )

    return BuiltinOptimization.extractBuiltinArgs(
        node                = node,
        builtin_class       = selectXrangeBuiltin,
        builtin_spec        = BuiltinOptimization.builtin_xrange_spec,
        empty_special_class = makeXrange0
    )


def len_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinLen,
        builtin_spec  = BuiltinOptimization.builtin_len_spec
    )

def tuple_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinTuple,
        builtin_spec  = BuiltinOptimization.builtin_tuple_spec
    )

def list_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinList,
        builtin_spec  = BuiltinOptimization.builtin_list_spec
    )

def set_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinSet,
        builtin_spec  = BuiltinOptimization.builtin_set_spec
    )

def float_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinFloat,
        builtin_spec  = BuiltinOptimization.builtin_float_spec
    )

def complex_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinComplex,
        builtin_spec  = BuiltinOptimization.builtin_complex_spec
    )

def str_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinStr,
        builtin_spec  = BuiltinOptimization.builtin_str_spec
    )

if python_version < 300:
    from nuitka.nodes.BuiltinTypeNodes import ExpressionBuiltinUnicode

    def unicode_extractor(node):
        return BuiltinOptimization.extractBuiltinArgs(
            node          = node,
            builtin_class = ExpressionBuiltinUnicode,
            builtin_spec  = BuiltinOptimization.builtin_unicode_spec
        )


def bool_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinBool,
        builtin_spec  = BuiltinOptimization.builtin_bool_spec
    )

def int_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinInt,
        builtin_spec  = BuiltinOptimization.builtin_int_spec
    )

if python_version < 300:
    from nuitka.nodes.BuiltinTypeNodes import ExpressionBuiltinLong

    def long_extractor(node):
        return BuiltinOptimization.extractBuiltinArgs(
            node          = node,
            builtin_class = ExpressionBuiltinLong,
            builtin_spec  = BuiltinOptimization.builtin_long_spec
        )

def globals_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinGlobals,
        builtin_spec  = BuiltinOptimization.builtin_globals_spec
    )

def locals_extractor(node):
    # Note: Locals on the module level is really globals.
    provider = node.getParentVariableProvider()

    if provider.isCompiledPythonModule():
        return BuiltinOptimization.extractBuiltinArgs(
            node          = node,
            builtin_class = ExpressionBuiltinGlobals,
            builtin_spec  = BuiltinOptimization.builtin_globals_spec
        )
    else:
        return BuiltinOptimization.extractBuiltinArgs(
            node          = node,
            builtin_class = ExpressionBuiltinLocals,
            builtin_spec  = BuiltinOptimization.builtin_locals_spec
        )

if python_version < 300:
    from nuitka.nodes.ExecEvalNodes import ExpressionBuiltinExecfile

    def execfile_extractor(node):
        @calledWithBuiltinArgumentNamesDecorator
        def wrapExpressionBuiltinExecfileCreation(filename, globals_arg,
                                                  locals_arg, source_ref):
            outline_body = ExpressionOutlineBody(
                provider   = node.getParentVariableProvider(),
                name       = "execfile_call",
                source_ref = source_ref
            )

            globals_ref, locals_ref, tried, final = wrapEvalGlobalsAndLocals(
                provider     = node.getParentVariableProvider(),
                globals_node = globals_arg,
                locals_node  = locals_arg,
                temp_scope   = outline_body.getOutlineTempScope(),
                source_ref   = source_ref
            )

            tried = makeStatementsSequence(
                statements = (
                    tried,
                    StatementReturn(
                        expression = ExpressionBuiltinExecfile(
                            source_code = ExpressionCallEmpty(
                                called     = ExpressionAttributeLookup(
                                    source         = ExpressionBuiltinOpen(
                                        filename   = filename,
                                        mode       = makeConstantRefNode(
                                            constant   = "rU",
                                            source_ref = source_ref
                                        ),
                                        buffering  = None,
                                        source_ref = source_ref
                                    ),
                                    attribute_name = "read",
                                    source_ref     = source_ref
                                ),
                                source_ref = source_ref
                            ),
                            globals_arg = globals_ref,
                            locals_arg  = locals_ref,
                            source_ref  = source_ref
                        ),
                        source_ref = source_ref
                    )
                ),
                allow_none = False,
                source_ref = source_ref
            )

            outline_body.setBody(
                makeStatementsSequenceFromStatement(
                    statement = makeTryFinallyStatement(
                        provider   = outline_body,
                        tried      = tried,
                        final      = final,
                        source_ref = source_ref
                    )
                )
            )

            return outline_body

        return BuiltinOptimization.extractBuiltinArgs(
            node          = node,
            builtin_class = wrapExpressionBuiltinExecfileCreation,
            builtin_spec  = BuiltinOptimization.builtin_execfile_spec
        )

def eval_extractor(node):
    @calledWithBuiltinArgumentNamesDecorator
    def wrapEvalBuiltin(source, globals_arg, locals_arg, source_ref):
        provider = node.getParentVariableProvider()

        outline_body = ExpressionOutlineBody(
            provider   = node.getParentVariableProvider(),
            name       = "eval_call",
            source_ref = source_ref
        )

        globals_ref, locals_ref, tried, final = wrapEvalGlobalsAndLocals(
            provider     = provider,
            globals_node = globals_arg,
            locals_node  = locals_arg,
            temp_scope   = outline_body.getOutlineTempScope(),
            source_ref   = source_ref
        )

        # The wrapping should not relocate to the "source_ref".
        assert globals_arg is None or \
               globals_ref.getSourceReference() == \
               globals_arg.getSourceReference()
        assert locals_arg is None or \
               locals_ref.getSourceReference() == \
               locals_arg.getSourceReference()

        source_variable = outline_body.allocateTempVariable(
            temp_scope = None,
            name       = "source"
        )

        final.setStatements(
            final.getStatements() + (
                StatementDelVariable(
                    variable_ref = ExpressionTargetTempVariableRef(
                        variable   = source_variable,
                        source_ref = source_ref
                    ),
                    tolerant     = True,
                    source_ref   = source_ref
                ),
            )
        )

        strip_choice =  makeConstantRefNode(
            constant   = (" \t",),
            source_ref = source_ref
        )

        if python_version >= 300:
            strip_choice = ExpressionConditional(
                condition      = ExpressionComparisonIs(
                    left       = ExpressionBuiltinType1(
                        value      = ExpressionTempVariableRef(
                            variable   = source_variable,
                            source_ref = source_ref
                        ),
                        source_ref = source_ref
                    ),
                    right      = ExpressionBuiltinRef(
                        builtin_name = "bytes",
                        source_ref   = source_ref
                    ),
                    source_ref = source_ref
                ),
                expression_yes = makeConstantRefNode(
                    constant   = (b" \t",),
                    source_ref = source_ref
                ),
                expression_no  = strip_choice,
                source_ref     = source_ref
            )


        # Source needs some special treatment for eval, if it's a string, it
        # must be stripped.
        string_fixup = [
            StatementAssignmentVariable(
                variable_ref = ExpressionTargetTempVariableRef(
                    variable   = source_variable,
                    source_ref = source_ref
                ),
                source       = ExpressionCallNoKeywords(
                    called     = ExpressionAttributeLookup(
                        source         = ExpressionTempVariableRef(
                            variable   = source_variable,
                            source_ref = source_ref
                        ),
                        attribute_name = "strip",
                        source_ref     = source_ref
                    ),
                    args       = strip_choice,
                    source_ref = source_ref
                ),
                source_ref   = source_ref
            )
        ]

        acceptable_builtin_types = [
            ExpressionBuiltinAnonymousRef(
                builtin_name = "code",
                source_ref   = source_ref,
            )
        ]

        if python_version >= 270:
            acceptable_builtin_types.append(
                ExpressionBuiltinRef(
                    builtin_name = "memoryview",
                    source_ref   = source_ref,
                )
            )

        statements = (
            StatementAssignmentVariable(
                variable_ref = ExpressionTargetTempVariableRef(
                    variable   = source_variable,
                    source_ref = source_ref
                ),
                source       = source,
                source_ref   = source_ref,
            ),
            StatementConditional(
                condition  = ExpressionOperationNOT(
                    operand    = ExpressionBuiltinIsinstance(
                        instance   = ExpressionTempVariableRef(
                            variable   = source_variable,
                            source_ref = source_ref
                        ),
                        classes    = makeSequenceCreationOrConstant(
                            sequence_kind = "tuple",
                            elements      = acceptable_builtin_types,
                            source_ref    = source_ref
                        ),
                        source_ref = source_ref
                    ),
                    source_ref = source_ref
                ),
                yes_branch = StatementsSequence(
                    statements = string_fixup,
                    source_ref = source_ref
                ),
                no_branch  = None,
                source_ref = source_ref
            ),
            StatementReturn(
                expression = ExpressionBuiltinEval(
                    source_code = ExpressionTempVariableRef(
                        variable   = source_variable,
                        source_ref = source_ref
                    ),
                    globals_arg = globals_ref,
                    locals_arg  = locals_ref,
                    source_ref  = source_ref
                ),
                source_ref = source_ref
            )
        )

        tried = makeStatementsSequence(
            statements = (
                tried,
            ) + statements,
            allow_none = False,
            source_ref = source_ref
        )

        outline_body.setBody(
            makeStatementsSequenceFromStatement(
                statement = makeTryFinallyStatement(
                    provider   = outline_body,
                    tried      = tried,
                    final      = final,
                    source_ref = source_ref
                )
            )
        )

        return outline_body

    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = wrapEvalBuiltin,
        builtin_spec  = BuiltinOptimization.builtin_eval_spec
    )

if python_version >= 300:
    from nuitka.nodes.ExecEvalNodes import ExpressionBuiltinExec

    def exec_extractor(node):
        @calledWithBuiltinArgumentNamesDecorator
        def wrapExpressionBuiltinExecCreation(source, globals_arg, locals_arg,
                                              source_ref):
            provider = node.getParentVariableProvider()

            outline_body = ExpressionOutlineBody(
                provider   = provider,
                name       = "exec_call",
                source_ref = source_ref
            )

            globals_ref, locals_ref, tried, final = wrapEvalGlobalsAndLocals(
                provider     = provider,
                globals_node = globals_arg,
                locals_node  = locals_arg,
                temp_scope   = outline_body.getOutlineTempScope(),
                source_ref   = source_ref
            )

            tried = makeStatementsSequence(
                statements = (
                    tried,
                    StatementReturn(
                        expression = ExpressionBuiltinExec(
                            source_code = source,
                            globals_arg = globals_ref,
                            locals_arg  = locals_ref,
                            source_ref  = source_ref
                        ),
                        source_ref = source_ref
                    ),
                ),
                allow_none = False,
                source_ref = source_ref
            )

            # Hack: Allow some APIs to work already
            tried.parent = outline_body

            outline_body.setBody(
                makeStatementsSequenceFromStatement(
                    statement = makeTryFinallyStatement(
                        provider   = provider,
                        tried      = tried,
                        final      = final,
                        source_ref = source_ref
                    )
                )
            )

            return outline_body

        return BuiltinOptimization.extractBuiltinArgs(
            node          = node,
            builtin_class = wrapExpressionBuiltinExecCreation,
            builtin_spec  = BuiltinOptimization.builtin_eval_spec
        )

def compile_extractor(node):
    def wrapExpressionBuiltinCompileCreation(source_code, filename, mode, flags,
                                             dont_inherit, optimize = None,
                                             source_ref = None):
        return ExpressionBuiltinCompile(
            source_code  = source_code,
            filename     = filename,
            mode         = mode,
            flags        = flags,
            dont_inherit = dont_inherit,
            optimize     = optimize,
            source_ref   = source_ref
        )

    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = wrapExpressionBuiltinCompileCreation,
        builtin_spec  = BuiltinOptimization.builtin_compile_spec
    )


def open_extractor(node):
    def makeOpen0(source_ref):
        # pylint: disable=W0613

        return makeRaiseExceptionReplacementExpressionFromInstance(
            expression = node,
            exception  = TypeError(
                "Required argument 'name' (pos 1) not found"
                  if python_version < 300 else
                "Required argument 'file' (pos 1) not found"
            )
        )

    return BuiltinOptimization.extractBuiltinArgs(
        node                = node,
        builtin_class       = ExpressionBuiltinOpen,
        builtin_spec        = BuiltinOptimization.builtin_open_spec,
        empty_special_class = makeOpen0
    )


def super_extractor(node):
    @calledWithBuiltinArgumentNamesDecorator
    def wrapSuperBuiltin(type_arg, object_arg, source_ref):
        if type_arg is None and python_version >= 300:

            provider = node.getParentVariableProvider()

            type_arg = ExpressionVariableRef(
                variable_name = "__class__",
                source_ref    = source_ref
            )

            # Ought to be already closure taken.
            type_arg.setVariable(
                provider.getVariableForReference(
                    variable_name = "__class__"
                )
            )

            # If we already have this as a local variable, then use that
            # instead.
            type_arg_owner = type_arg.getVariable().getOwner()
            if type_arg_owner is provider or \
            not (type_arg_owner.isExpressionFunctionBody() or \
                 type_arg_owner.isExpressionClassBody()):
                type_arg = None

            if type_arg is None:
                return makeRaiseExceptionReplacementExpression(
                    expression      = node,
                    exception_type  = "SystemError"
                                        if python_version < 331 else
                                      "RuntimeError",
                    exception_value = "super(): __class__ cell not found",
                )

            if object_arg is None:
                if provider.isExpressionGeneratorObjectBody():
                    parameter_provider = provider.getParentVariableProvider()
                else:
                    parameter_provider = provider

                if parameter_provider.getParameters().getArgumentCount() > 0:
                    par1_name = parameter_provider.getParameters().getArgumentNames()[0]

                    object_arg = ExpressionVariableRef(
                        variable_name = par1_name,
                        source_ref    = source_ref
                    )

                    object_arg.setVariable(
                        provider.getVariableForReference(
                            variable_name = par1_name
                        )
                    )

                    if not object_arg.getVariable().isParameterVariable():
                        return makeRaiseExceptionReplacementExpression(
                            expression      = node,
                            exception_type  = "SystemError"
                                                if python_version < 330 else
                                              "RuntimeError",
                            exception_value = "super(): __class__ cell not found",
                        )
                else:
                    return makeRaiseExceptionReplacementExpression(
                        expression      = node,
                        exception_type  = "RuntimeError",
                        exception_value = "super(): no arguments"
                    )

        return ExpressionBuiltinSuper(
            super_type   = type_arg,
            super_object = object_arg,
            source_ref   = source_ref
        )

    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = wrapSuperBuiltin,
        builtin_spec  = BuiltinOptimization.builtin_super_spec
    )

def hasattr_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinHasattr,
        builtin_spec  = BuiltinOptimization.builtin_hasattr_spec
    )

def getattr_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinGetattr,
        builtin_spec  = BuiltinOptimization.builtin_getattr_spec
    )

def setattr_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinSetattr,
        builtin_spec  = BuiltinOptimization.builtin_setattr_spec
    )

def isinstance_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinIsinstance,
        builtin_spec  = BuiltinOptimization.builtin_isinstance_spec
    )

def bytearray_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinBytearray,
        builtin_spec  = BuiltinOptimization.builtin_bytearray_spec
    )


def slice_extractor(node):
    def wrapSlice(start, stop, step, source_ref):
        if start is not None and stop is None:
            # Default rules are strange. If one argument is given, it's the
            # second one then.
            stop = start
            start = None

        return ExpressionBuiltinSlice(
            start      = start,
            stop       = stop,
            step       = step,
            source_ref = source_ref
        )

    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = wrapSlice,
        builtin_spec  = BuiltinOptimization.builtin_slice_spec
    )

def hash_extractor(node):
    return BuiltinOptimization.extractBuiltinArgs(
        node          = node,
        builtin_class = ExpressionBuiltinHash,
        builtin_spec  = BuiltinOptimization.builtin_hash_spec
    )

def format_extractor(node):
    def makeFormat0(source_ref):
        # pylint: disable=W0613

        return makeRaiseExceptionReplacementExpressionFromInstance(
            expression = node,
            exception  = TypeError("format() takes at least 1 argument (0 given)")
        )

    return BuiltinOptimization.extractBuiltinArgs(
        node                = node,
        builtin_class       = ExpressionBuiltinFormat,
        builtin_spec        = BuiltinOptimization.builtin_format_spec,
        empty_special_class = makeFormat0
    )


_dispatch_dict = {
    "compile"    : compile_extractor,
    "globals"    : globals_extractor,
    "locals"     : locals_extractor,
    "eval"       : eval_extractor,
    "dir"        : dir_extractor,
    "vars"       : vars_extractor,
    "__import__" : import_extractor,
    "chr"        : chr_extractor,
    "ord"        : ord_extractor,
    "bin"        : bin_extractor,
    "oct"        : oct_extractor,
    "hex"        : hex_extractor,
    "id"         : id_extractor,
    "type"       : type_extractor,
    "iter"       : iter_extractor,
    "next"       : next_extractor,
    "tuple"      : tuple_extractor,
    "list"       : list_extractor,
    "dict"       : dict_extractor,
    "set"        : set_extractor,
    "float"      : float_extractor,
    "complex"    : complex_extractor,
    "str"        : str_extractor,
    "bool"       : bool_extractor,
    "int"        : int_extractor,
    "repr"       : repr_extractor,
    "len"        : len_extractor,
    "super"      : super_extractor,
    "hasattr"    : hasattr_extractor,
    "getattr"    : getattr_extractor,
    "setattr"    : setattr_extractor,
    "isinstance" : isinstance_extractor,
    # TODO: Disabled for now, not handling all cases.
    # "bytearray"  : bytearray_extractor,
    "slice"      : slice_extractor,
    "hash"       : hash_extractor,
    "format"     : format_extractor,
}

if python_version < 300:
    # These are not in Python3
    _dispatch_dict["long"] = long_extractor
    _dispatch_dict["unicode"] = unicode_extractor
    _dispatch_dict["execfile"] = execfile_extractor

    _dispatch_dict["xrange"] = xrange_extractor
    _dispatch_dict["range"] = range_extractor

    # The handling of 'open' built-in for Python3 is not yet correct.
    _dispatch_dict["open"] = open_extractor
else:
    # The Python3 range is really an xrange, use that.
    _dispatch_dict["range"] = xrange_extractor
    _dispatch_dict["exec"] = exec_extractor
    _dispatch_dict["ascii"] = ascii_extractor

def check():
    from nuitka.Builtins import builtin_names

    for builtin_name in _dispatch_dict:
        assert builtin_name in builtin_names, builtin_name

check()

_builtin_white_list = (
    # Not supporting 'print', because it could be replaced, and is not
    # worth the effort yet.
    "print",

    # TODO: This could, and should be supported, as we could e.g. lower
    # types easily for it.
    "sorted",
)

def computeBuiltinCall(call_node, called):
    # There is some dispatching for how to output various types of changes,
    # with lots of cases, pylint: disable=R0912

    builtin_name = called.getBuiltinName()

    if builtin_name in _dispatch_dict:
        new_node = _dispatch_dict[builtin_name](call_node)

        # Lets just have this contract to return "None" when no change is meant
        # to be done.
        assert new_node is not call_node
        if new_node is None:
            return call_node, None, None

        # For traces, we are going to ignore side effects, and output traces
        # only based on the basis of it.
        inspect_node = new_node
        if inspect_node.isExpressionSideEffects():
            inspect_node = inspect_node.getExpression()

        if inspect_node.isExpressionBuiltinImport():
            tags    = "new_import"
            message = """\
Replaced dynamic __import__ %s with static module import.""" % (
                inspect_node.kind,
            )
        elif inspect_node.isExpressionBuiltin() or \
             inspect_node.isStatementExec():
            tags = "new_builtin"
            message = "Replaced call to built-in '%s' with built-in call '%s'." % (
                builtin_name,
                inspect_node.kind,
            )
        elif inspect_node.isExpressionRaiseException():
            tags = "new_raise"
            message = """\
Replaced call to built-in '%s' with exception raising call.""" % (
                inspect_node.kind,
            )
        elif inspect_node.isExpressionOperationUnary():
            tags = "new_expression"
            message = """\
Replaced call to built-in '%s' with unary operation '%s'.""" % (
                inspect_node.kind,
                inspect_node.getOperator()
            )
        elif inspect_node.isExpressionCall():
            tags = "new_expression"
            message = """\
Replaced call to built-in '%s' with call.""" % (
                inspect_node.kind,
            )
        elif inspect_node.isExpressionOutlineBody():
            tags = "new_expression"
            message = """\
Replaced call to built-in '%s' with outlined call.""" % builtin_name
        else:

            assert False, (builtin_name, "->", inspect_node)

        # TODO: One day, this should be enabled by default and call either the
        # original built-in or the optimized above one. That should be done,
        # once we can eliminate the condition for most cases.
        if False and isDebug() and not shallMakeModule() and builtin_name:
            source_ref = called.getSourceReference()

            new_node = ExpressionConditional(
                condition      = ExpressionComparisonIs(
                    left       = ExpressionBuiltinRef(
                        builtin_name = builtin_name,
                        source_ref   = source_ref
                    ),
                    right      = ExpressionBuiltinOriginalRef(
                        builtin_name = builtin_name,
                        source_ref   = source_ref
                    ),
                    source_ref = source_ref
                ),
                expression_yes = new_node,
                expression_no  = makeRaiseExceptionReplacementExpression(
                    exception_type  = "RuntimeError",
                    exception_value = "Built-in '%s' cannot be replaced." % (
                        builtin_name
                    ),
                    expression      = call_node
                ),
                source_ref     = source_ref
            )

        assert tags != ""

        return new_node, tags, message
    else:
        if False and isDebug() and builtin_name not in _builtin_white_list:
            warning(
                "Not handling built-in '%s', consider support." % builtin_name
            )

        return call_node, None, None
