//     Copyright 2015, 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.
//
// Implementations of compiled code helpers.

// The definition of a compiled code helper is that it's being used in
// generated C++ code and provides part of the operations implementation.

// Currently we also have standalone mode related code here, patches to CPython
// runtime that we do, and e.g. the built-in module. TODO: Move these to their
// own files for clarity.

#include "nuitka/prelude.hpp"

extern PyObject *const_str_plain_compile;

static PythonBuiltin _python_builtin_compile( &const_str_plain_compile );

#if PYTHON_VERSION < 300
PyObject *COMPILE_CODE( PyObject *source_code, PyObject *file_name, PyObject *mode, PyObject *flags, PyObject *dont_inherit )
#else
PyObject *COMPILE_CODE( PyObject *source_code, PyObject *file_name, PyObject *mode, PyObject *flags, PyObject *dont_inherit, PyObject *optimize )
#endif
{
    // May be a source, but also could already be a compiled object, in which
    // case this should just return it.
    if ( PyCode_Check( source_code ) )
    {
        return INCREASE_REFCOUNT( source_code );
    }

    PyObject *pos_args = PyTuple_New(3);
    PyTuple_SET_ITEM( pos_args, 0, INCREASE_REFCOUNT( source_code ) );
    PyTuple_SET_ITEM( pos_args, 1, INCREASE_REFCOUNT( file_name ) );
    PyTuple_SET_ITEM( pos_args, 2, INCREASE_REFCOUNT( mode ) );

    PyObject *kw_args = NULL;

    if ( flags != NULL )
    {
        if ( kw_args == NULL ) kw_args = PyDict_New();
        PyDict_SetItemString( kw_args, "flags", flags );
    }

    if ( dont_inherit != NULL )
    {
        if ( kw_args == NULL ) kw_args = PyDict_New();
        PyDict_SetItemString( kw_args, "dont_inherit", dont_inherit );
    }

#if PYTHON_VERSION >= 300
    if ( optimize != NULL )
    {
        if ( kw_args == NULL ) kw_args = PyDict_New();
        PyDict_SetItemString( kw_args, "optimize", optimize );
    }
#endif

    PyObject *result = CALL_FUNCTION(
        _python_builtin_compile.asObject0(),
        pos_args,
        kw_args
    );

    Py_DECREF( pos_args );
    Py_XDECREF( kw_args );

    return result;
}

PyObject *EVAL_CODE( PyObject *code, PyObject *globals, PyObject *locals )
{
    CHECK_OBJECT( code );
    CHECK_OBJECT( globals );
    CHECK_OBJECT( locals );

    if ( PyDict_Check( globals ) == 0 )
    {
        PyErr_Format( PyExc_TypeError, "exec: arg 2 must be a dictionary or None" );
        return NULL;
    }

    // TODO: Our re-formulation prevents this externally, doesn't it.
    if ( locals == Py_None )
    {
        locals = globals;
    }

    if ( PyMapping_Check( locals ) == 0 )
    {
        PyErr_Format( PyExc_TypeError, "exec: arg 3 must be a mapping or None" );
        return NULL;
    }

    // Set the __builtins__ in globals, it is expected to be present.
    if ( PyDict_GetItem( globals, const_str_plain___builtins__ ) == NULL )
    {
        if ( PyDict_SetItem( globals, const_str_plain___builtins__, (PyObject *)builtin_module ) != 0 )
        {
            return NULL;
        }
    }

#if PYTHON_VERSION < 300
    PyObject *result = PyEval_EvalCode( (PyCodeObject *)code, globals, locals );
#else
    PyObject *result = PyEval_EvalCode( code, globals, locals );
#endif

    if (unlikely( result == NULL ))
    {
        return NULL;
    }

    return result;
}

extern PyObject *const_str_plain_open;

static PythonBuiltin _python_builtin_open( &const_str_plain_open );

PyObject *BUILTIN_OPEN( PyObject *file_name, PyObject *mode, PyObject *buffering )
{
    if ( file_name == NULL )
    {
        return CALL_FUNCTION_NO_ARGS(
            _python_builtin_open.asObject0()
        );
    }
    else if ( mode == NULL )
    {
        return CALL_FUNCTION_WITH_ARGS1(
            _python_builtin_open.asObject0(),
            file_name
        );

    }
    else if ( buffering == NULL )
    {
        return CALL_FUNCTION_WITH_ARGS2(
            _python_builtin_open.asObject0(),
            file_name,
            mode
        );
    }
    else
    {
        return CALL_FUNCTION_WITH_ARGS3(
            _python_builtin_open.asObject0(),
            file_name,
            mode,
            buffering
        );
    }
}

PyObject *BUILTIN_CHR( unsigned char c )
{
    // TODO: A switch statement might be faster, because no object needs to be
    // created at all, this here is how CPython does it.
    char s[1];
    s[0] = (char)c;

#if PYTHON_VERSION < 300
    return PyString_FromStringAndSize( s, 1 );
#else
    return PyUnicode_FromStringAndSize( s, 1 );
#endif
}

PyObject *BUILTIN_CHR( PyObject *value )
{
    long x = PyInt_AsLong( value );

#if PYTHON_VERSION < 300
    if ( x < 0 || x >= 256 )
    {
        PyErr_Format( PyExc_ValueError, "chr() arg not in range(256)" );
        return NULL;
    }

    // TODO: A switch statement might be faster, because no object needs to be
    // created at all, this is how CPython does it.
    char s[1];
    s[0] = (char)x;

    return PyString_FromStringAndSize( s, 1 );
#else
    PyObject *result = PyUnicode_FromOrdinal( x );

    if (unlikely( result == NULL ))
    {
        return NULL;
    }

    assert( PyUnicode_Check( result ));

    return result;
#endif
}

PyObject *BUILTIN_ORD( PyObject *value )
{
    long result;

    if (likely( PyBytes_Check( value ) ))
    {
        Py_ssize_t size = PyBytes_GET_SIZE( value );

        if (likely( size == 1 ))
        {
            result = long( ((unsigned char *)PyBytes_AS_STRING( value ))[0] );
        }
        else
        {
            PyErr_Format( PyExc_TypeError, "ord() expected a character, but string of length %zd found", size );
            return NULL;
        }
    }
    else if ( PyByteArray_Check( value ) )
    {
        Py_ssize_t size = PyByteArray_GET_SIZE( value );

        if (likely( size == 1 ))
        {
            result = long( ((unsigned char *)PyByteArray_AS_STRING( value ))[0] );
        }
        else
        {
            PyErr_Format( PyExc_TypeError, "ord() expected a character, but byte array of length %zd found", size );
            return NULL;
        }
    }
    else if ( PyUnicode_Check( value ) )
    {
#if PYTHON_VERSION >= 330
        if (unlikely( PyUnicode_READY( value ) == -1 ))
        {
            return NULL;
        }

        Py_ssize_t size = PyUnicode_GET_LENGTH( value );
#else
        Py_ssize_t size = PyUnicode_GET_SIZE( value );
#endif

        if (likely( size == 1 ))
        {
#if PYTHON_VERSION >= 330
            result = long( PyUnicode_READ_CHAR( value, 0 ) );
#else
            result = long( *PyUnicode_AS_UNICODE( value ) );
#endif
        }
        else
        {
            PyErr_Format( PyExc_TypeError, "ord() expected a character, but unicode string of length %zd found", size );
            return NULL;
        }
    }
    else
    {
        PyErr_Format( PyExc_TypeError, "ord() expected string of length 1, but %s found", Py_TYPE( value )->tp_name );
        return NULL;
    }

    return PyInt_FromLong( result );
}

PyObject *BUILTIN_BIN( PyObject *value )
{
    // Note: I don't really know why ord and hex don't use this as well.
    PyObject *result = PyNumber_ToBase( value, 2 );

    if ( unlikely( result == NULL ))
    {
        return NULL;
    }

    return result;
}

PyObject *BUILTIN_OCT( PyObject *value )
{
#if PYTHON_VERSION >= 300
    PyObject *result = PyNumber_ToBase( value, 8 );

    if ( unlikely( result == NULL ))
    {
        return NULL;
    }

    return result;
#else
    if (unlikely( value == NULL ))
    {
        PyErr_Format( PyExc_TypeError, "oct() argument can't be converted to oct" );
        return NULL;
    }

    PyNumberMethods *nb = Py_TYPE( value )->tp_as_number;

    if (unlikely( nb == NULL || nb->nb_oct == NULL ))
    {
        PyErr_Format( PyExc_TypeError, "oct() argument can't be converted to oct" );
        return NULL;
    }

    PyObject *result = (*nb->nb_oct)( value );

    if ( result )
    {
        if (unlikely( !PyString_Check( result ) ))
        {
            PyErr_Format( PyExc_TypeError, "__oct__ returned non-string (type %s)", Py_TYPE( result )->tp_name );

            Py_DECREF( result );
            return NULL;
        }
    }

    return result;
#endif
}

PyObject *BUILTIN_HEX( PyObject *value )
{
#if PYTHON_VERSION >= 300
    PyObject *result = PyNumber_ToBase( value, 16 );

    if ( unlikely( result == NULL ))
    {
        return NULL;
    }

    return result;
#else
    if (unlikely( value == NULL ))
    {
        PyErr_Format( PyExc_TypeError, "hex() argument can't be converted to hex" );
        return NULL;
    }

    PyNumberMethods *nb = Py_TYPE( value )->tp_as_number;

    if (unlikely( nb == NULL || nb->nb_hex == NULL ))
    {
        PyErr_Format( PyExc_TypeError, "hex() argument can't be converted to hex" );
        return NULL;
    }

    PyObject *result = (*nb->nb_hex)( value );

    if (likely( result ))
    {
        if (unlikely( !PyString_Check( result ) ))
        {
            PyErr_Format( PyExc_TypeError, "__hex__ returned non-string (type %s)", Py_TYPE( result )->tp_name );

            Py_DECREF( result );
            return NULL;
        }
    }

    return result;
#endif
}

PyObject *BUILTIN_BYTEARRAY( PyObject *value )
{
    PyObject *result = PyByteArray_FromObject( value );

    if ( unlikely( result == NULL ))
    {
        return NULL;
    }

    return result;
}


// From CPython:
typedef struct {
    PyObject_HEAD
    PyObject *it_callable;
    PyObject *it_sentinel;
} calliterobject;

PyObject *BUILTIN_ITER2( PyObject *callable, PyObject *sentinel )
{
    calliterobject *result = PyObject_GC_New( calliterobject, &PyCallIter_Type );

    if (unlikely( result == NULL ))
    {
        return NULL;
    }

    // Note: References were taken at call site already.
    result->it_callable = INCREASE_REFCOUNT( callable );
    result->it_sentinel = INCREASE_REFCOUNT( sentinel );

    Nuitka_GC_Track( result );

    return (PyObject *)result;
}

PyObject *BUILTIN_TYPE1( PyObject *arg )
{
    return INCREASE_REFCOUNT( (PyObject *)Py_TYPE( arg ) );
}

extern PyObject *const_str_plain___module__;

PyObject *BUILTIN_TYPE3( PyObject *module_name, PyObject *name, PyObject *bases, PyObject *dict )
{
    PyObject *pos_args = PyTuple_New(3);
    PyTuple_SET_ITEM( pos_args, 0, INCREASE_REFCOUNT( name ) );
    PyTuple_SET_ITEM( pos_args, 1, INCREASE_REFCOUNT( bases ) );
    PyTuple_SET_ITEM( pos_args, 2, INCREASE_REFCOUNT( dict ) );

    PyObject *result = PyType_Type.tp_new(
        &PyType_Type,
        pos_args,
        NULL
    );


    if (unlikely( result == NULL ))
    {
        Py_DECREF( pos_args );
        return NULL;
    }

    PyTypeObject *type = Py_TYPE( result );

    if (likely( PyType_IsSubtype( type, &PyType_Type ) ))
    {
        if (
#if PYTHON_VERSION < 300
            PyType_HasFeature( type, Py_TPFLAGS_HAVE_CLASS ) &&
#endif
            type->tp_init != NULL
           )
        {
            int res = type->tp_init( result, pos_args, NULL );

            if (unlikely( res < 0 ))
            {
                Py_DECREF( pos_args );
                Py_DECREF( result );
                return NULL;
            }
        }
    }

    Py_DECREF( pos_args );

    int res = PyObject_SetAttr( result, const_str_plain___module__, module_name );

    if ( res < 0 )
    {
        return NULL;
    }

    return result;
}

Py_ssize_t ESTIMATE_RANGE( long low, long high, long step )
{
    if ( low >= high )
    {
        return 0;
    }
    else
    {
        return ( high - low - 1 ) / step + 1;
    }
}

#if PYTHON_VERSION < 300
static PyObject *_BUILTIN_RANGE_INT3( long low, long high, long step )
{
    assert( step != 0 );

    Py_ssize_t size;

    if ( step > 0 )
    {
        size = ESTIMATE_RANGE( low, high, step );
    }
    else
    {
        size = ESTIMATE_RANGE( high, low, -step );
    }

    PyObject *result = PyList_New( size );

    long current = low;

    for ( int i = 0; i < size; i++ )
    {
        PyList_SET_ITEM( result, i, PyInt_FromLong( current ) );
        current += step;
    }

    return result;
}

static PyObject *_BUILTIN_RANGE_INT2( long low, long high )
{
    return _BUILTIN_RANGE_INT3( low, high, 1 );
}

static PyObject *_BUILTIN_RANGE_INT( long boundary )
{
    return _BUILTIN_RANGE_INT2( 0, boundary );
}

static PyObject *TO_RANGE_ARG( PyObject *value, char const *name )
{
    if (likely( PyInt_Check( value ) || PyLong_Check( value ) ))
    {
        return INCREASE_REFCOUNT( value );
    }

    PyTypeObject *type = Py_TYPE( value );
    PyNumberMethods *tp_as_number = type->tp_as_number;

    // Everything that casts to int is allowed.
    if (
#if PYTHON_VERSION >= 270
        PyFloat_Check( value ) ||
#endif
        tp_as_number == NULL ||
        tp_as_number->nb_int == NULL
       )
    {
        PyErr_Format( PyExc_TypeError, "range() integer %s argument expected, got %s.", name, type->tp_name );
        return NULL;
    }

    PyObject *result = tp_as_number->nb_int( value );

    if (unlikely( result == NULL ))
    {
        return NULL;
    }

    return result;
}
#endif

extern PyObject *const_str_plain_range;

static PythonBuiltin _python_builtin_range( &const_str_plain_range );

PyObject *BUILTIN_RANGE( PyObject *boundary )
{
#if PYTHON_VERSION < 300
    PyObject *boundary_temp = TO_RANGE_ARG( boundary, "end" );

    if (unlikely( boundary_temp == NULL ))
    {
        return NULL;
    }

    long start = PyInt_AsLong( boundary_temp );

    if ( start == -1 && ERROR_OCCURRED() )
    {
        CLEAR_ERROR_OCCURRED();

        PyObject *result = CALL_FUNCTION_WITH_ARGS1(
            _python_builtin_range.asObject0(),
            boundary_temp
        );

        Py_DECREF( boundary_temp );

        return result;
    }
    Py_DECREF( boundary_temp );

    return _BUILTIN_RANGE_INT( start );
#else
    return CALL_FUNCTION_WITH_ARGS1(
       _python_builtin_range.asObject0(),
       boundary
    );
#endif
}

PyObject *BUILTIN_RANGE2( PyObject *low, PyObject *high )
{
#if PYTHON_VERSION < 300
    PyObject *low_temp = TO_RANGE_ARG( low, "start" );

    if (unlikely( low_temp == NULL ))
    {
        return NULL;
    }

    PyObject *high_temp = TO_RANGE_ARG( high, "end" );

    if (unlikely( high_temp == NULL ))
    {
        Py_DECREF( low_temp );
        return NULL;
    }

    bool fallback = false;

    long start = PyInt_AsLong( low_temp );

    if (unlikely( start == -1 && ERROR_OCCURRED() ))
    {
        CLEAR_ERROR_OCCURRED();
        fallback = true;
    }

    long end = PyInt_AsLong( high_temp );

    if (unlikely( end == -1 && ERROR_OCCURRED() ))
    {
        CLEAR_ERROR_OCCURRED();
        fallback = true;
    }

    if ( fallback )
    {
        PyObject *pos_args = PyTuple_New( 2 );
        PyTuple_SET_ITEM( pos_args, 0, low_temp );
        PyTuple_SET_ITEM( pos_args, 1, high_temp );

        PyObject *result = CALL_FUNCTION_WITH_POSARGS(
            _python_builtin_range.asObject0(),
            pos_args
        );

        Py_DECREF( pos_args );

        return result;
    }
    else
    {
        Py_DECREF( low_temp );
        Py_DECREF( high_temp );

        return _BUILTIN_RANGE_INT2( start, end );
    }
#else
    PyObject *pos_args = PyTuple_New( 2 );
    PyTuple_SET_ITEM( pos_args, 0, INCREASE_REFCOUNT( low ) );
    PyTuple_SET_ITEM( pos_args, 1, INCREASE_REFCOUNT( high ) );

    PyObject *result = CALL_FUNCTION_WITH_POSARGS(
        _python_builtin_range.asObject0(),
        pos_args
    );

     Py_DECREF( pos_args );

     return result;
#endif
}

PyObject *BUILTIN_RANGE3( PyObject *low, PyObject *high, PyObject *step )
{
#if PYTHON_VERSION < 300
    PyObject *low_temp = TO_RANGE_ARG( low, "start" );

    if (unlikely( low_temp == NULL ))
    {
        return NULL;
    }

    PyObject *high_temp = TO_RANGE_ARG( high, "end" );

    if (unlikely( high_temp == NULL ))
    {
        Py_DECREF( low_temp );
        return NULL;
    }

    PyObject *step_temp = TO_RANGE_ARG( step, "step" );

    if (unlikely( high_temp == NULL ))
    {
        Py_DECREF( low_temp );
        Py_DECREF( high_temp );
        return NULL;
    }

    bool fallback = false;

    long start = PyInt_AsLong( low_temp );

    if (unlikely( start == -1 && ERROR_OCCURRED() ))
    {
        CLEAR_ERROR_OCCURRED();
        fallback = true;
    }

    long end = PyInt_AsLong( high_temp );

    if (unlikely( end == -1 && ERROR_OCCURRED() ))
    {
        CLEAR_ERROR_OCCURRED();
        fallback = true;
    }

    long step_long = PyInt_AsLong( step_temp );

    if (unlikely( step_long == -1 && ERROR_OCCURRED() ))
    {
        CLEAR_ERROR_OCCURRED();
        fallback = true;
    }

    if ( fallback )
    {
        PyObject *pos_args = PyTuple_New( 3 );
        PyTuple_SET_ITEM( pos_args, 0, low_temp );
        PyTuple_SET_ITEM( pos_args, 1, high_temp );
        PyTuple_SET_ITEM( pos_args, 2, step_temp );

        PyObject *result = CALL_FUNCTION_WITH_POSARGS(
            _python_builtin_range.asObject0(),
            pos_args
        );

        Py_DECREF( pos_args );

        return result;
    }
    else
    {
        Py_DECREF( low_temp );
        Py_DECREF( high_temp );
        Py_DECREF( step_temp );

        if (unlikely( step_long == 0 ))
        {
            PyErr_Format( PyExc_ValueError, "range() step argument must not be zero" );
            return NULL;
        }

        return _BUILTIN_RANGE_INT3( start, end, step_long );
    }
#else
    PyObject *pos_args = PyTuple_New( 3 );
    PyTuple_SET_ITEM( pos_args, 0, INCREASE_REFCOUNT( low ) );
    PyTuple_SET_ITEM( pos_args, 1, INCREASE_REFCOUNT( high ) );
    PyTuple_SET_ITEM( pos_args, 2, INCREASE_REFCOUNT( step ) );

    PyObject *result = CALL_FUNCTION_WITH_POSARGS(
        _python_builtin_range.asObject0(),
        pos_args
    );

    Py_DECREF( pos_args );

    return result;
#endif
}

#if PYTHON_VERSION < 300
extern PyObject *const_str_plain_xrange;

static PythonBuiltin _python_builtin_xrange( &const_str_plain_xrange );

PyObject *BUILTIN_XRANGE( PyObject *low, PyObject *high, PyObject *step )
{
    if ( step != NULL )
    {
        return CALL_FUNCTION_WITH_ARGS3(
            _python_builtin_xrange.asObject0(),
            low,
            high,
            step
        );
    }
    else if ( high != NULL )
    {
        return CALL_FUNCTION_WITH_ARGS2(
            _python_builtin_xrange.asObject0(),
            low,
            high
        );
    }
    else
    {
        return CALL_FUNCTION_WITH_ARGS1(
            _python_builtin_xrange.asObject0(),
            low
        );
    }
}
#endif

PyObject *BUILTIN_LEN( PyObject *value )
{
    CHECK_OBJECT( value );

    Py_ssize_t res = PyObject_Size( value );

    if (unlikely( res < 0 && ERROR_OCCURRED() ))
    {
        return NULL;
    }

    return PyInt_FromSsize_t( res );
}

extern PyObject *const_str_plain___import__;

static PythonBuiltin _python_builtin_import( &const_str_plain___import__ );

PyObject *IMPORT_MODULE( PyObject *module_name, PyObject *globals, PyObject *locals, PyObject *import_items, PyObject *level )
{
    CHECK_OBJECT( module_name );
    CHECK_OBJECT( globals );
    CHECK_OBJECT( locals );
    CHECK_OBJECT( import_items );
    CHECK_OBJECT( level );

    PyObject *pos_args = PyTuple_New(5);
    PyTuple_SET_ITEM( pos_args, 0, INCREASE_REFCOUNT( module_name ) );
    PyTuple_SET_ITEM( pos_args, 1, INCREASE_REFCOUNT( globals ) );
    PyTuple_SET_ITEM( pos_args, 2, INCREASE_REFCOUNT( locals ) );
    PyTuple_SET_ITEM( pos_args, 3, INCREASE_REFCOUNT( import_items ) );
    PyTuple_SET_ITEM( pos_args, 4, INCREASE_REFCOUNT( level ) );

    PyObject *import_result = CALL_FUNCTION_WITH_POSARGS(
        _python_builtin_import.asObject0(),
        pos_args
    );

    Py_DECREF( pos_args );

    return import_result;
}

extern PyObject *const_str_plain___all__;

bool IMPORT_MODULE_STAR( PyObject *target, bool is_module, PyObject *module )
{
    // Check parameters.
    CHECK_OBJECT( module );
    CHECK_OBJECT( target );

    PyObject *iter;
    bool all_case;

    PyObject *all = PyObject_GetAttr( module, const_str_plain___all__ );
    if ( all != NULL )
    {
        iter = MAKE_ITERATOR( all );
        Py_DECREF( all );

        if (unlikely( iter == NULL ))
        {
            return false;
        }

        all_case = true;
    }
    else if ( EXCEPTION_MATCH_BOOL_SINGLE( GET_ERROR_OCCURRED(), PyExc_AttributeError ) )
    {
        CLEAR_ERROR_OCCURRED();

        iter = MAKE_ITERATOR( PyModule_GetDict( module ) );
        CHECK_OBJECT( iter );

        all_case = false;
    }
    else
    {
        return false;
    }

    while ( PyObject *item = ITERATOR_NEXT( iter ) )
    {
        assert( Nuitka_String_Check( item ) );

        // TODO: Not yet clear, what happens with __all__ and "_" of its
        // contents.
        if ( all_case == false )
        {
            if ( Nuitka_String_AsString_Unchecked( item )[0] == '_' )
            {
                continue;
            }
        }

        // TODO: What if it isn't there, because of e.g. wrong __all__ value.
        PyObject *value = LOOKUP_ATTRIBUTE( module, item );

        // TODO: Check if the reference is handled correctly
        if ( is_module )
        {
            SET_ATTRIBUTE( target, item,  value );
        }
        else
        {
            SET_SUBSCRIPT( target, item, value );
        }

        Py_DECREF( value );
        Py_DECREF( item );
    }

    Py_DECREF( iter );

    return !ERROR_OCCURRED();
}

// Helper functions for print. Need to play nice with Python softspace
// behaviour.

#if PYTHON_VERSION >= 300
extern PyObject *const_str_plain_print;
extern PyObject *const_str_plain_end;
extern PyObject *const_str_plain_file;
extern PyObject *const_str_empty;

static PythonBuiltin _python_builtin_print( &const_str_plain_print );
#endif


bool PRINT_NEW_LINE_TO( PyObject *file )
{
#if PYTHON_VERSION < 300
    if ( file == NULL || file == Py_None )
    {
        file = GET_STDOUT();
    }

    // need to hold a reference to the file or else __getattr__ may release
    // "file" in the mean time.
    Py_INCREF( file );

    if (unlikely( PyFile_WriteString( "\n", file ) == -1))
    {
        Py_DECREF( file );
        return false;
    }

    PyFile_SoftSpace( file, 0 );
    CHECK_OBJECT( file );

    Py_DECREF( file );
    return true;
#else
    if (likely( file == NULL ))
    {
        PyObject *result = CALL_FUNCTION_NO_ARGS(
            _python_builtin_print.asObject0()
        );
        Py_XDECREF( result );
        return result != NULL;
    }
    else
    {
        PyObject *kw_args = PyDict_New();
        PyDict_SetItem( kw_args, const_str_plain_file, GET_STDOUT() );

        PyObject *result = CALL_FUNCTION_WITH_KEYARGS(
            _python_builtin_print.asObject0(),
            kw_args
        );

        Py_DECREF( kw_args );
        Py_XDECREF( result );

        return result != NULL;
    }
#endif
}


bool PRINT_ITEM_TO( PyObject *file, PyObject *object )
{
// The print built-in function cannot replace "softspace" behavior of CPython
// print statement, so this code is really necessary.
#if PYTHON_VERSION < 300
    if ( file == NULL || file == Py_None )
    {
        file = GET_STDOUT();
    }

    CHECK_OBJECT( file );
    CHECK_OBJECT( object );

    // need to hold a reference to the file or else "__getattr__" code may
    // release "file" in the mean time.
    Py_INCREF( file );

    // Check for soft space indicator
    if ( PyFile_SoftSpace( file, 0 ) )
    {
        if (unlikely( PyFile_WriteString( " ", file ) == -1 ))
        {
            Py_DECREF( file );
            return false;
        }
    }

    if ( unlikely( PyFile_WriteObject( object, file, Py_PRINT_RAW ) == -1 ))
    {
        Py_DECREF( file );
        return false;
    }

    if ( PyString_Check( object ) )
    {
        char *buffer;
        Py_ssize_t length;

#ifndef __NUITKA_NO_ASSERT__
        int status =
#endif
            PyString_AsStringAndSize( object, &buffer, &length );
        assert( status != -1 );

        if ( length == 0 || !isspace( Py_CHARMASK(buffer[length-1])) || buffer[ length - 1 ] == ' ' )
        {
            PyFile_SoftSpace( file, 1 );
        }
    }
    else if ( PyUnicode_Check( object ) )
    {
        Py_UNICODE *buffer = PyUnicode_AS_UNICODE( object );
        Py_ssize_t length = PyUnicode_GET_SIZE( object );

        if ( length == 0 || !Py_UNICODE_ISSPACE( buffer[length-1]) || buffer[length-1] == ' ' )
        {
            PyFile_SoftSpace( file, 1 );
        }
    }
    else
    {
        PyFile_SoftSpace( file, 1 );
    }

    CHECK_OBJECT( file );
    Py_DECREF( file );

    return true;
#else
    if (likely( file == NULL ))
    {
        PyObject *result = CALL_FUNCTION_WITH_ARGS1(
            _python_builtin_print.asObject0(),
            object
        );

        Py_XDECREF( result );

        return result != NULL;
    }
    else
    {
        PyObject *print_kw = PyDict_New();
        PyDict_SetItem( print_kw, const_str_plain_end, const_str_empty );
        PyDict_SetItem( print_kw, const_str_plain_file, GET_STDOUT() );

        PyObject *print_args = PyTuple_New( 1 );
        PyTuple_SET_ITEM( print_args, 0, INCREASE_REFCOUNT( object ) );

        PyObject *res = CALL_FUNCTION(
            _python_builtin_print.asObject0(),
            print_args,
            print_kw
        );

        Py_DECREF( print_args );
        Py_DECREF( print_kw );

        Py_XDECREF( res );

        return res != NULL;
    }
#endif
}

void PRINT_REFCOUNT( PyObject *object )
{
    char buffer[ 1024 ];
    sprintf( buffer, " refcnt %" PY_FORMAT_SIZE_T "d ", Py_REFCNT( object ) );

    PRINT_STRING( buffer );
}

bool PRINT_STRING( char const *str )
{
    int res = PyFile_WriteString( str, GET_STDOUT() );

    return res != -1;
}

bool PRINT_REPR( PyObject *object )
{
    bool res;

    if ( object != NULL )
    {
        PyObject *repr = PyObject_Repr( object );
        res = PRINT_ITEM( repr );
        Py_DECREF( repr );
    }
    else
    {
        res = PRINT_NULL();
    }

    return res;
}

bool PRINT_NULL( void )
{
    return PRINT_STRING("<NULL>");
}

void PRINT_EXCEPTION( PyObject *exception_type, PyObject *exception_value, PyObject *exception_tb )
{
    PRINT_REPR( exception_type );
    PRINT_STRING("|");
    PRINT_REPR( exception_value );
#if PYTHON_VERSION >= 300
    if ( exception_value != NULL )
    {
        PRINT_STRING(" <- ");
        PRINT_REPR( PyException_GetContext( exception_value ) );
    }
#endif
    PRINT_STRING("|");
    PRINT_REPR( exception_tb );

    PRINT_NEW_LINE();
}

// TODO: Could be ported, the "printf" stuff would need to be split. On Python3
// the normal C print output gets lost.
#if PYTHON_VERSION < 300
void PRINT_TRACEBACK( PyTracebackObject *traceback )
{
    PRINT_STRING("Dumping traceback:\n");

    if ( traceback == NULL ) PRINT_STRING( "<NULL traceback?!>\n" );

    while( traceback )
    {
        printf( " line %d (frame object chain):\n", traceback->tb_lineno );

        PyFrameObject *frame = traceback->tb_frame;

        while ( frame )
        {
            printf( "  Frame at %s\n", PyString_AsString( PyObject_Str( (PyObject *)frame->f_code )));

            frame = frame->f_back;
        }

        assert( traceback->tb_next != traceback );
        traceback = traceback->tb_next;
    }

    PRINT_STRING("End of Dump.\n");
}
#endif


PyObject *GET_STDOUT()
{
    PyObject *result = PySys_GetObject( (char *)"stdout" );

    if (unlikely( result == NULL ))
    {
        PyErr_Format( PyExc_RuntimeError, "lost sys.stdout" );
        return NULL;
    }

    return result;
}

PyObject *GET_STDERR()
{
    PyObject *result = PySys_GetObject( (char *)"stderr" );

    if (unlikely( result == NULL ))
    {
        PyErr_Format( PyExc_RuntimeError, "lost sys.stderr" );
        return NULL;
    }

    return result;
}

bool PRINT_NEW_LINE( void )
{
    PyObject *target = GET_STDOUT();

    return target != NULL && PRINT_NEW_LINE_TO( target );
}

bool PRINT_ITEM( PyObject *object )
{
    PyObject *target = GET_STDOUT();

    return target != NULL && PRINT_ITEM_TO( target, object );
}



PyObject *UNSTREAM_CONSTANT( unsigned char const *buffer, Py_ssize_t size )
{
    assert( buffer );

    // We unstream difficult constant objects using the "pickle" module, this is
    // aimed at being the exception, e.g. unicode that doesn't fit into UTF-8
    // will be dealt with like this.
    static PyObject *module_pickle = NULL;

    if ( module_pickle == NULL )
    {
#if PYTHON_VERSION < 300
        module_pickle = PyImport_ImportModule( "cPickle" );
#else
        module_pickle = PyImport_ImportModule( "pickle" );
#endif
        if (unlikely( module_pickle == NULL ))
        {
            PyErr_Print();
        }

        assert( module_pickle );
    }

    static PyObject *function_pickle_loads = NULL;

    if ( function_pickle_loads == NULL )
    {
        function_pickle_loads = PyObject_GetAttrString(
            module_pickle,
            "loads"
        );

        if (unlikely( function_pickle_loads == NULL ))
        {
            PyErr_Print();
        }

        assert( function_pickle_loads );
    }

    PyObject *result = PyObject_CallFunction(
        function_pickle_loads,
#if PYTHON_VERSION < 300
        (char *)"(s#)", // TODO: Why the ()
#else
        (char *)"y#",
#endif
        buffer,
        size
    );

    if (unlikely( result == NULL ))
    {
        PyErr_Print();
    }

    CHECK_OBJECT( result );

    return result;
}

#if PYTHON_VERSION < 300
PyObject *UNSTREAM_UNICODE( unsigned char const *buffer, Py_ssize_t size )
{
    PyObject *result = PyUnicode_FromStringAndSize( (char const  *)buffer, size );

    assert( !ERROR_OCCURRED() );
    CHECK_OBJECT( result );

    return result;
}
#endif

PyObject *UNSTREAM_STRING( unsigned char const *buffer, Py_ssize_t size, bool intern )
{
#if PYTHON_VERSION < 300
    PyObject *result = PyString_FromStringAndSize( (char const  *)buffer, size );
#else
    PyObject *result = PyUnicode_FromStringAndSize( (char const  *)buffer, size );
#endif

    assert( !ERROR_OCCURRED() );
    CHECK_OBJECT( result );
    assert( Nuitka_String_Check( result ) );

#if PYTHON_VERSION < 300
    assert( PyString_Size( result ) == size );
#endif

    if ( intern )
    {
        Nuitka_StringIntern( &result );

        CHECK_OBJECT( result );
        assert( Nuitka_String_Check( result ) );

#if PYTHON_VERSION < 300
        assert( PyString_Size( result ) == size );
#else
        assert( PyUnicode_GET_SIZE( result ) == size );
#endif
    }

    return result;
}

PyObject *UNSTREAM_CHAR( unsigned char value, bool intern )
{
#if PYTHON_VERSION < 300
    PyObject *result = PyString_FromStringAndSize( (char const  *)&value, 1 );
#else
    PyObject *result = PyUnicode_FromStringAndSize( (char const  *)&value, 1 );
#endif

    assert( !ERROR_OCCURRED() );
    CHECK_OBJECT( result );
    assert( Nuitka_String_Check( result ) );

#if PYTHON_VERSION < 300
    assert( PyString_Size( result ) == 1 );
#else
    assert( PyUnicode_GET_SIZE( result ) == 1 );
#endif

    if ( intern )
    {
        Nuitka_StringIntern( &result );

        CHECK_OBJECT( result );
        assert( Nuitka_String_Check( result ) );

#if PYTHON_VERSION < 300
        assert( PyString_Size( result ) == 1 );
#else
        assert( PyUnicode_GET_SIZE( result ) == 1 );
#endif
    }

    return result;
}

PyObject *UNSTREAM_FLOAT( unsigned char const *buffer )
{
    double x = _PyFloat_Unpack8( buffer, 1 );
    assert( x != -1.0 || !PyErr_Occurred() );

    PyObject *result = PyFloat_FromDouble(x);
    assert( result != NULL );

    return result;
}

#if PYTHON_VERSION >= 300
PyObject *UNSTREAM_BYTES( unsigned char const *buffer, Py_ssize_t size )
{
    PyObject *result = PyBytes_FromStringAndSize( (char const  *)buffer, size );
    assert( !ERROR_OCCURRED() );
    CHECK_OBJECT( result );

    assert( PyBytes_GET_SIZE( result ) == size );
    return result;
}
#endif


#if PYTHON_VERSION < 300

static void set_slot( PyObject **slot, PyObject *value )
{
    PyObject *temp = *slot;
    Py_XINCREF( value );
    *slot = value;
    Py_XDECREF( temp );
}

extern PyObject *const_str_plain___getattr__;
extern PyObject *const_str_plain___setattr__;
extern PyObject *const_str_plain___delattr__;

static void set_attr_slots( PyClassObject *klass )
{
    set_slot( &klass->cl_getattr, FIND_ATTRIBUTE_IN_CLASS( klass, const_str_plain___getattr__ ) );
    set_slot( &klass->cl_setattr, FIND_ATTRIBUTE_IN_CLASS( klass, const_str_plain___setattr__ ) );
    set_slot( &klass->cl_delattr, FIND_ATTRIBUTE_IN_CLASS( klass, const_str_plain___delattr__ ) );
}

static bool set_dict( PyClassObject *klass, PyObject *value )
{
    if ( value == NULL || !PyDict_Check( value ) )
    {
        PyErr_SetString( PyExc_TypeError, (char *)"__dict__ must be a dictionary object" );
        return false;
    }
    else
    {
        set_slot( &klass->cl_dict, value );
        set_attr_slots( klass );

        return true;
    }
}

static bool set_bases( PyClassObject *klass, PyObject *value )
{
    if ( value == NULL || !PyTuple_Check( value ) )
    {
        PyErr_SetString( PyExc_TypeError, (char *)"__bases__ must be a tuple object" );
        return false;
    }
    else
    {
        Py_ssize_t n = PyTuple_Size( value );

        for ( Py_ssize_t i = 0; i < n; i++ )
        {
            PyObject *base = PyTuple_GET_ITEM( value, i );

            if (unlikely( !PyClass_Check( base ) ))
            {
                PyErr_SetString( PyExc_TypeError, (char *)"__bases__ items must be classes" );
                return false;
            }

            if (unlikely( PyClass_IsSubclass( base, (PyObject *)klass) ))
            {
                PyErr_SetString( PyExc_TypeError, (char *)"a __bases__ item causes an inheritance cycle" );
                return false;
            }
        }

        set_slot( &klass->cl_bases, value );
        set_attr_slots( klass );

        return true;
    }
}

static bool set_name( PyClassObject *klass, PyObject *value )
{
    if ( value == NULL || !PyDict_Check( value ) )
    {
        PyErr_SetString( PyExc_TypeError, (char *)"__name__ must be a string object" );
        return false;
    }

    if ( strlen( PyString_AS_STRING( value )) != (size_t)PyString_GET_SIZE( value ) )
    {
        PyErr_SetString( PyExc_TypeError, (char *)"__name__ must not contain null bytes" );
        return false;
    }

    set_slot( &klass->cl_name, value );
    return true;
}

static int nuitka_class_setattr( PyClassObject *klass, PyObject *attr_name, PyObject *value )
{
    char *sattr_name = PyString_AsString( attr_name );

    if ( sattr_name[0] == '_' && sattr_name[1] == '_' )
    {
        Py_ssize_t n = PyString_Size( attr_name );

        if ( sattr_name[ n-2 ] == '_' && sattr_name[ n-1 ] == '_' )
        {
            if ( strcmp( sattr_name, "__dict__" ) == 0 )
            {
                if ( set_dict( klass, value ) == false )
                {
                    return -1;
                }
                else
                {
                    return 0;
                }
            }
            else if ( strcmp( sattr_name, "__bases__" ) == 0 )
            {
                if ( set_bases( klass, value ) == false )
                {
                    return -1;
                }
                else
                {
                    return 0;
                }
            }
            else if ( strcmp( sattr_name, "__name__" ) == 0 )
            {
                if ( set_name( klass, value ) == false )
                {
                    return -1;
                }
                else
                {
                    return 0;
                }
            }
            else if ( strcmp( sattr_name, "__getattr__" ) == 0 )
            {
                set_slot( &klass->cl_getattr, value );
            }
            else if ( strcmp(sattr_name, "__setattr__" ) == 0 )
            {
                set_slot( &klass->cl_setattr, value );
            }
            else if ( strcmp(sattr_name, "__delattr__" ) == 0 )
            {
                set_slot( &klass->cl_delattr, value );
            }
        }
    }

    if ( value == NULL )
    {
        int status = PyDict_DelItem( klass->cl_dict, attr_name );

        if ( status < 0 )
        {
            PyErr_Format( PyExc_AttributeError, "class %s has no attribute '%s'", PyString_AS_STRING( klass->cl_name ), sattr_name );
        }

        return status;
    }
    else
    {
        return PyDict_SetItem( klass->cl_dict, attr_name, value );
    }
}

static PyObject *nuitka_class_getattr( PyClassObject *klass, PyObject *attr_name )
{
    char *sattr_name = PyString_AsString( attr_name );

    if ( sattr_name[0] == '_' && sattr_name[1] == '_' )
    {
        if ( strcmp( sattr_name, "__dict__" ) == 0 )
        {
            return INCREASE_REFCOUNT( klass->cl_dict );
        }
        else if ( strcmp(sattr_name, "__bases__" ) == 0 )
        {
            return INCREASE_REFCOUNT( klass->cl_bases );
        }
        else if ( strcmp(sattr_name, "__name__" ) == 0 )
        {
            return klass->cl_name ? INCREASE_REFCOUNT( klass->cl_name ) : INCREASE_REFCOUNT( Py_None );
        }
    }

    PyObject *value = FIND_ATTRIBUTE_IN_CLASS( klass, attr_name );

    if (unlikely( value == NULL ))
    {
        PyErr_Format( PyExc_AttributeError, "class %s has no attribute '%s'", PyString_AS_STRING( klass->cl_name ), sattr_name );
        return NULL;
    }

    PyTypeObject *type = Py_TYPE( value );

    descrgetfunc tp_descr_get = PyType_HasFeature( type, Py_TPFLAGS_HAVE_CLASS ) ? type->tp_descr_get : NULL;

    if ( tp_descr_get == NULL )
    {
        return INCREASE_REFCOUNT( value );
    }
    else
    {
        return tp_descr_get( value, (PyObject *)NULL, (PyObject *)klass );
    }
}

#endif

void enhancePythonTypes( void )
{
#if PYTHON_VERSION < 300
    // Our own variant won't call PyEval_GetRestricted, saving quite some cycles
    // not doing that.
    PyClass_Type.tp_setattro = (setattrofunc)nuitka_class_setattr;
    PyClass_Type.tp_getattro = (getattrofunc)nuitka_class_getattr;
#endif
}

#ifdef __APPLE__
extern "C" wchar_t* _Py_DecodeUTF8_surrogateescape(const char *s, Py_ssize_t size);
#endif

#ifdef __FreeBSD__
#include <floatingpoint.h>
#endif

#include <locale.h>

#if PYTHON_VERSION >= 300
static wchar_t **argv_copy = NULL;
#endif

void setCommandLineParameters( int argc, char *argv[], bool initial )
{
#if PYTHON_VERSION < 300
    if ( initial )
    {
        Py_SetProgramName( argv[0] );
    }
    else
    {
        PySys_SetArgv( argc, argv );
    }
#else
    if ( initial )
    {
        // Originally taken from CPython3: There seems to be no sane way to use
        argv_copy = (wchar_t **)PyMem_Malloc(sizeof(wchar_t*)*argc);

#ifdef __FreeBSD__
        // 754 requires that FP exceptions run in "no stop" mode by default, and
        // until C vendors implement C99's ways to control FP exceptions, Python
        // requires non-stop mode.  Alas, some platforms enable FP exceptions by
        // default.  Here we disable them.

        fp_except_t m;

        m = fpgetmask();
        fpsetmask( m & ~FP_X_OFL );
#endif
        char *oldloc = strdup( setlocale( LC_ALL, NULL ) );
        setlocale( LC_ALL, "" );

        for ( int i = 0; i < argc; i++ )
        {
#ifdef __APPLE__
            argv_copy[i] = _Py_DecodeUTF8_surrogateescape( argv[ i ], strlen( argv[ i ] ) );
#else
#if PYTHON_VERSION < 350
            argv_copy[i] = _Py_char2wchar( argv[ i ], NULL );
#else
            argv_copy[i] = Py_DecodeLocale( argv[ i ], NULL );
#endif

#endif
            assert ( argv_copy[ i ] );
        }

        setlocale( LC_ALL, oldloc );
        free( oldloc );
    }


    if ( initial )
    {
        Py_SetProgramName( argv_copy[0] );
    }
    else
    {
        PySys_SetArgv( argc, argv_copy );
    }
#endif
}

typedef struct {
    PyObject_HEAD
    PyTypeObject *type;
    PyObject *obj;
    PyTypeObject *obj_type;
} superobject;

extern PyObject *const_str_plain___class__;

PyObject *BUILTIN_SUPER( PyObject *type, PyObject *object )
{
    CHECK_OBJECT( type );

    superobject *result = PyObject_GC_New( superobject, &PySuper_Type );
    assert( result );

    if ( object == Py_None )
    {
        object = NULL;
    }

    if (unlikely( PyType_Check( type ) == false ))
    {
        PyErr_Format( PyExc_RuntimeError, "super(): __class__ is not a type (%s)", Py_TYPE( type )->tp_name );

        return NULL;
    }

    result->type = (PyTypeObject *)INCREASE_REFCOUNT( type );
    if ( object )
    {
        result->obj = INCREASE_REFCOUNT( object );

        if ( PyType_Check( object ) && PyType_IsSubtype( (PyTypeObject *)object, (PyTypeObject *)type ))
        {
            result->obj_type = (PyTypeObject *)INCREASE_REFCOUNT( object );
        }
        else if ( PyType_IsSubtype( Py_TYPE(object ), (PyTypeObject *)type) )
        {
            result->obj_type = (PyTypeObject *)INCREASE_REFCOUNT( (PyObject *)Py_TYPE( object ) );
        }
        else
        {
            PyObject *class_attr = PyObject_GetAttr( object, const_str_plain___class__);

            if (likely( class_attr != NULL && PyType_Check( class_attr ) && (PyTypeObject *)class_attr != Py_TYPE( object ) ))
            {
                result->obj_type = (PyTypeObject *)class_attr;
            }
            else
            {
                if ( class_attr == NULL )
                {
                    CLEAR_ERROR_OCCURRED();
                }
                else
                {
                    Py_DECREF( class_attr );
                }

                PyErr_Format(
                    PyExc_TypeError,
                    "super(type, obj): obj must be an instance or subtype of type"
                );

                return NULL;
            }
        }
    }
    else
    {
        result->obj = NULL;
        result->obj_type = NULL;
    }

    Nuitka_GC_Track( result );

    CHECK_OBJECT( (PyObject *)result );
    assert( Py_TYPE( result ) == &PySuper_Type );

    return (PyObject *)result;
}

PyObject *BUILTIN_CALLABLE( PyObject *value )
{
    return PyBool_FromLong( (long)PyCallable_Check( value ) );
}

PyObject *original_isinstance = NULL;

// Note: Installed and used by "InspectPatcher".
int Nuitka_IsInstance( PyObject *inst, PyObject *cls )
{
    CHECK_OBJECT( original_isinstance );
    CHECK_OBJECT( inst );
    CHECK_OBJECT( cls );

    // Quick path.
    if ( Py_TYPE( inst ) == (PyTypeObject *)cls )
    {
        return true;
    }

    // Our paths for the types we need to hook.
    if ( cls == (PyObject *)&PyFunction_Type && Nuitka_Function_Check( inst ) )
    {
        return true;
    }

    if ( cls == (PyObject *)&PyGen_Type && Nuitka_Generator_Check( inst ) )
    {
        return true;
    }

    if ( cls == (PyObject *)&PyMethod_Type && Nuitka_Method_Check( inst ) )
    {
        return true;
    }

    if ( cls == (PyObject *)&PyFrame_Type && Nuitka_Frame_Check( inst ) )
    {
        return true;
    }

    // May need to be recursive for tuple arguments.
    if ( PyTuple_Check( cls ) )
    {
        for ( Py_ssize_t i = 0, size = PyTuple_GET_SIZE( cls ); i < size; i++ )
        {
            PyObject *element = PyTuple_GET_ITEM( cls, i );

            if (unlikely( Py_EnterRecursiveCall( (char *)" in __instancecheck__" ) ))
            {
                return -1;
            }

            int res = Nuitka_IsInstance( inst, element );

            Py_LeaveRecursiveCall();

            if ( res != 0 )
            {
                return res;
            }
        }

        return 0;
    }
    else
    {
        PyObject *result = CALL_FUNCTION_WITH_ARGS2( original_isinstance, inst, cls );

        if ( result == NULL )
        {
            return -1;
        }

        int res = PyObject_IsTrue( result );
        Py_DECREF( result );

        return res;
    }
}

PyObject *BUILTIN_ISINSTANCE( PyObject *inst, PyObject *cls )
{
    int res = Nuitka_IsInstance( inst, cls );

    if (unlikely( res < 0 ))
    {
        return NULL;
    }

    return BOOL_FROM( res != 0 );
}

PyObject *BUILTIN_GETATTR( PyObject *object, PyObject *attribute, PyObject *default_value )
{
#if PYTHON_VERSION < 300
    if ( PyUnicode_Check( attribute ) )
    {
        attribute = _PyUnicode_AsDefaultEncodedString( attribute, NULL );

        if (unlikely( attribute == NULL ))
        {
            return NULL;
        }
    }

    if (unlikely( !PyString_Check( attribute ) ))
    {
        PyErr_Format( PyExc_TypeError, "getattr(): attribute name must be string" );
        return NULL;
    }
#else
    if (!PyUnicode_Check( attribute ))
    {
        PyErr_Format( PyExc_TypeError, "getattr(): attribute name must be string" );
        return NULL;
    }
#endif

    PyObject *result = PyObject_GetAttr( object, attribute );

    if ( result == NULL )
    {
        if ( default_value != NULL && EXCEPTION_MATCH_BOOL_SINGLE( GET_ERROR_OCCURRED(), PyExc_AttributeError ) )
        {
            CLEAR_ERROR_OCCURRED();

            return INCREASE_REFCOUNT( default_value );
        }
        else
        {
            return NULL;
        }
    }
    else
    {
        return result;
    }
}

PyObject *BUILTIN_SETATTR( PyObject *object, PyObject *attribute, PyObject *value )
{
    int res = PyObject_SetAttr( object, attribute, value );

    if ( res < 0 )
    {
        return NULL;
    }

    return BOOL_FROM( res == 0 );
}

PyDictObject *dict_builtin = NULL;
PyModuleObject *builtin_module = NULL;

#define ASSIGN_BUILTIN( name ) _python_original_builtin_value_##name = LOOKUP_BUILTIN( const_str_plain_##name );

static PyTypeObject Nuitka_BuiltinModule_Type =
{
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "compiled_module",                           // tp_name
    sizeof(PyModuleObject),                      // tp_size
};

extern PyObject *const_str_plain_open;

int Nuitka_BuiltinModule_SetAttr( PyModuleObject *module, PyObject *name, PyObject *value )
{
    CHECK_OBJECT( (PyObject *)module );
    CHECK_OBJECT( name );

    // This is used for "del" as well.
    assert( value == NULL || Py_REFCNT( value ) > 0 );

    // only checks the builtins that we can refresh at this time, if we have
    // many value to check maybe need create a dict first.
    bool found = false;

    int res = PyObject_RichCompareBool( name, const_str_plain_open, Py_EQ );

    if (unlikely( res == -1 ))
    {
        return -1;
    }
    if ( res == 1 )
    {
        _python_builtin_open.update( value );
        found = true;
    }

    if ( found == false )
    {
        res = PyObject_RichCompareBool( name, const_str_plain___import__, Py_EQ );

        if (unlikely( res == -1 ))
        {
            return -1;
        }

        if ( res == 1 )
        {
            _python_builtin_import.update( value );
            found = true;
        }
    }

#if PYTHON_VERSION >= 300
    if ( found == false )
    {
        res = PyObject_RichCompareBool( name, const_str_plain_print, Py_EQ );

        if (unlikely( res == -1 ))
        {
            return -1;
        }

        if ( res == 1 )
        {
            _python_builtin_print.update( value );
            found = true;
        }
    }
#endif

    return PyObject_GenericSetAttr( (PyObject *)module, name, value );
}


#include <osdefs.h>

#if defined(_WIN32)
#include <Shlwapi.h>
#elif defined(__APPLE__)
#include <mach-o/dyld.h>
#include <dlfcn.h>
#include <libgen.h>
#else
#include <dlfcn.h>
#include <libgen.h>
#endif

#if defined(_WIN32) && !defined(PATH_MAX)
#define PATH_MAX MAXPATHLEN
#endif

#if defined( __FreeBSD__ )
#include <sys/sysctl.h>
#endif

#if defined(_NUITKA_EXE)

char *getBinaryDirectoryUTF8Encoded()
{
    static char binary_directory[ PATH_MAX + 1 ];
    static bool init_done = false;

    if ( init_done )
    {
        return binary_directory;
    }

#if defined(_WIN32)

#if PYTHON_VERSION >= 300
    WCHAR binary_directory2[ PATH_MAX + 1 ];
    binary_directory2[0] = 0;

    DWORD res = GetModuleFileNameW( NULL, binary_directory2, PATH_MAX + 1 );
    assert( res != 0 );

    int res2 = WideCharToMultiByte( CP_UTF8, 0, binary_directory2, -1, binary_directory, PATH_MAX+1, NULL, NULL );
    assert( res2 != 0 );
#else
    DWORD res = GetModuleFileName( NULL, binary_directory, PATH_MAX + 1 );
    assert( res != 0 );
#endif
    PathRemoveFileSpec( binary_directory );
#elif defined(__APPLE__)
    uint32_t bufsize = PATH_MAX + 1;
    int res =_NSGetExecutablePath( binary_directory, &bufsize );

    if (unlikely( res != 0 ))
    {
        abort();
    }

    // On MacOS, the "dirname" call creates a separate internal string, we can
    // safely copy back.
    strncpy(binary_directory, dirname(binary_directory), PATH_MAX + 1);

#elif defined( __FreeBSD__ )
    // Not all of FreeBSD has /proc file system, so use the appropiate
    // "sysctl" instead.
    int mib[4];
    mib[0] = CTL_KERN;
    mib[1] = KERN_PROC;
    mib[2] = KERN_PROC_PATHNAME;
    mib[3] = -1;
    size_t cb = sizeof(binary_directory);
    sysctl(mib, 4, binary_directory, &cb, NULL, 0);
#else
    // The remaining platforms, mostly Linux.
    // The "readlink" does not terminate result, so fill zeros there, then
    // it is a proper C string right away.
    memset( binary_directory, 0, PATH_MAX + 1 );
    ssize_t res = readlink( "/proc/self/exe", binary_directory, PATH_MAX );

    if (unlikely( res == -1 ))
    {
        abort();
    }

    strcpy( binary_directory, dirname( binary_directory ) );
#endif
    init_done = true;
    return binary_directory;
}

char *getBinaryDirectoryHostEncoded()
{
#if defined(_WIN32)
    static char binary_directory[ PATH_MAX + 1 ];
    static bool init_done = false;

    if ( init_done )
    {
        return binary_directory;
    }

#if PYTHON_VERSION >= 300
    WCHAR binary_directory2[ PATH_MAX + 1 ];
    binary_directory2[0] = 0;

    DWORD res = GetModuleFileNameW( NULL, binary_directory2, PATH_MAX + 1 );
    assert( res != 0 );

    int res2 = WideCharToMultiByte( CP_ACP, 0, binary_directory2, -1, binary_directory, PATH_MAX+1, NULL, NULL );
    assert( res2 != 0 );
#else
    DWORD res = GetModuleFileName( NULL, binary_directory, PATH_MAX + 1 );
    assert( res != 0 );
#endif
    PathRemoveFileSpec( binary_directory );

    init_done = true;
    return binary_directory;
#else
    return getBinaryDirectoryUTF8Encoded();
#endif
}

static PyObject *getBinaryDirectoryObject()
{
    static PyObject *binary_directory = NULL;

    if ( binary_directory != NULL )
    {
        return binary_directory;
    }

#if PYTHON_VERSION >= 300
    binary_directory = PyUnicode_FromString( getBinaryDirectoryUTF8Encoded() );
#else
    binary_directory = PyString_FromString( getBinaryDirectoryUTF8Encoded() );
#endif

    if (unlikely( binary_directory == NULL ))
    {
        PyErr_Print();
        abort();
    }

    return binary_directory;
}

#else
static char *getDllDirectory()
{
#if defined(_WIN32)
    static char path[ PATH_MAX ];
    HMODULE hm = NULL;
    path[0] = '\0';

#if PYTHON_VERSION >= 300
    WCHAR path2[ PATH_MAX + 1 ];
    path2[0] = 0;

    int res = GetModuleHandleExA(
        GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
        (LPCSTR) &getDllDirectory,
        &hm
    );
    assert( res != 0 );

    int res2 = WideCharToMultiByte(CP_UTF8, 0, path2, -1, path, PATH_MAX+1, NULL, NULL);
    assert( res2 != 0 );
#else
    int res = GetModuleHandleExA(
        GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
        (LPCSTR) &getDllDirectory,
        &hm
    );
    assert( res != 0 );
#endif

    PathRemoveFileSpec( path );

    return path;

#else
    Dl_info where;
    int res = dladdr( (void *)getDllDirectory, &where );
    assert( res != 0 );

    return dirname( (char *)where.dli_fname );
#endif
}
#endif

void _initBuiltinModule()
{
#if _NUITKA_MODULE
    if ( builtin_module ) return;
#else
    assert( builtin_module == NULL );
#endif

#if PYTHON_VERSION < 300
    builtin_module = (PyModuleObject *)PyImport_ImportModule( "__builtin__" );
#else
    builtin_module = (PyModuleObject *)PyImport_ImportModule( "builtins" );
#endif
    assert( builtin_module );
    dict_builtin = (PyDictObject *)builtin_module->md_dict;
    assert( PyDict_Check( dict_builtin ) );

#ifdef _NUITKA_STANDALONE
    int res = PyDict_SetItemString(
        (PyObject *)dict_builtin,
        "__nuitka_binary_dir",
        getBinaryDirectoryObject()
    );

    assert( res == 0 );
#endif

    // init Nuitka_BuiltinModule_Type, PyType_Ready wont copy all member from
    // base type, so we need copy all members from PyModule_Type manual for
    // safety.  PyType_Ready will change tp_flags, we need define it again. Set
    // tp_setattro to Nuitka_BuiltinModule_SetAttr and we can detect value
    // change. Set tp_base to PyModule_Type and PyModule_Check will pass.
    Nuitka_BuiltinModule_Type.tp_dealloc = PyModule_Type.tp_dealloc;
    Nuitka_BuiltinModule_Type.tp_repr = PyModule_Type.tp_repr;
    Nuitka_BuiltinModule_Type.tp_setattro = (setattrofunc)Nuitka_BuiltinModule_SetAttr;
    Nuitka_BuiltinModule_Type.tp_getattro = PyModule_Type.tp_getattro;
    Nuitka_BuiltinModule_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE;
    Nuitka_BuiltinModule_Type.tp_doc = PyModule_Type.tp_doc;
    Nuitka_BuiltinModule_Type.tp_traverse = PyModule_Type.tp_traverse;
    Nuitka_BuiltinModule_Type.tp_members = PyModule_Type.tp_members;
    Nuitka_BuiltinModule_Type.tp_base = &PyModule_Type;
    Nuitka_BuiltinModule_Type.tp_dictoffset = PyModule_Type.tp_dictoffset;
    Nuitka_BuiltinModule_Type.tp_init = PyModule_Type.tp_init;
    Nuitka_BuiltinModule_Type.tp_alloc = PyModule_Type.tp_alloc;
    Nuitka_BuiltinModule_Type.tp_new = PyModule_Type.tp_new;
    Nuitka_BuiltinModule_Type.tp_free = PyModule_Type.tp_free;
    int ret = PyType_Ready( &Nuitka_BuiltinModule_Type );
    assert( ret == 0 );

    // Replace type of builtin module to take over.
    ((PyObject *)builtin_module)->ob_type = &Nuitka_BuiltinModule_Type;
    assert( PyModule_Check( builtin_module ) == 1 );
}


PyObject *callPythonFunction( PyObject *func, PyObject **args, int count )
{
    PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE( func );
    PyObject *globals = PyFunction_GET_GLOBALS( func );
    PyObject *argdefs = PyFunction_GET_DEFAULTS( func );

#if PYTHON_VERSION >= 300
    PyObject *kwdefs = PyFunction_GET_KW_DEFAULTS( func );

    if ( kwdefs == NULL && argdefs == NULL && co->co_argcount == count &&
        co->co_flags == ( CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE ))
#else
    if ( argdefs == NULL && co->co_argcount == count &&
        co->co_flags == ( CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE ))
#endif
    {
        PyThreadState *tstate = PyThreadState_GET();
        CHECK_OBJECT( globals );

        PyFrameObject *frame = PyFrame_New( tstate, co, globals, NULL );

        if (unlikely( frame == NULL ))
        {
            return NULL;
        };

        for ( int i = 0; i < count; i++ )
        {
            frame->f_localsplus[i] = args[i];
            Py_INCREF( frame->f_localsplus[i] );
        }

        PyObject *result = PyEval_EvalFrameEx( frame, 0 );

        // Frame release protects against recursion as it may lead to variable
        // destruction.
        ++tstate->recursion_depth;
        Py_DECREF( frame );
        --tstate->recursion_depth;

        return result;
    }

    PyObject **defaults = NULL;
    int nd = 0;

    if ( argdefs != NULL )
    {
        defaults = &PyTuple_GET_ITEM( argdefs, 0 );
        nd = int( Py_SIZE( argdefs ) );
    }

    PyObject *result = PyEval_EvalCodeEx(
#if PYTHON_VERSION >= 300
        (PyObject *)co,
#else
        co,        // code object
#endif
        globals,   // globals
        NULL,      // no locals
        args,      // args
        count,     // argcount
        NULL,      // kwds
        0,         // kwcount
        defaults,  // defaults
        nd,        // defcount
#if PYTHON_VERSION >= 300
        kwdefs,
#endif
        PyFunction_GET_CLOSURE( func )
    );

    return result;
}

static PyObject *_fast_function_noargs( PyObject *func )
{
    PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE( func );
    PyObject *globals = PyFunction_GET_GLOBALS( func );
    PyObject *argdefs = PyFunction_GET_DEFAULTS( func );

#if PYTHON_VERSION >= 300
    PyObject *kwdefs = PyFunction_GET_KW_DEFAULTS( func );

    if ( kwdefs == NULL && argdefs == NULL && co->co_argcount == 0 &&
        co->co_flags == ( CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE ))
#else
    if ( argdefs == NULL && co->co_argcount == 0 &&
        co->co_flags == ( CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE ))
#endif
    {
        PyThreadState *tstate = PyThreadState_GET();
        CHECK_OBJECT( globals );

        PyFrameObject *frame = PyFrame_New( tstate, co, globals, NULL );

        if (unlikely( frame == NULL ))
        {
            return NULL;
        };

        PyObject *result = PyEval_EvalFrameEx( frame, 0 );

        // Frame release protects against recursion as it may lead to variable
        // destruction.
        ++tstate->recursion_depth;
        Py_DECREF( frame );
        --tstate->recursion_depth;

        return result;
    }

    PyObject **defaults = NULL;
    int nd = 0;

    if ( argdefs != NULL )
    {
        defaults = &PyTuple_GET_ITEM( argdefs, 0 );
        nd = int( Py_SIZE( argdefs ) );
    }

    PyObject *result = PyEval_EvalCodeEx(
#if PYTHON_VERSION >= 300
        (PyObject *)co,
#else
        co,        // code object
#endif
        globals,   // globals
        NULL,      // no locals
        NULL,      // args
        0,         // argcount
        NULL,      // kwds
        0,         // kwcount
        defaults,  // defaults
        nd,        // defcount
#if PYTHON_VERSION >= 300
        kwdefs,
#endif
        PyFunction_GET_CLOSURE( func )
    );

    return result;
}

PyObject *CALL_FUNCTION_NO_ARGS( PyObject *called )
{
    CHECK_OBJECT( called );

    if ( Nuitka_Function_Check( called ) )
    {
        if (unlikely( Py_EnterRecursiveCall( (char *)" while calling a Python object" ) ))
        {
            return NULL;
        }

        Nuitka_FunctionObject *function = (Nuitka_FunctionObject *)called;
        PyObject *result;

        if ( function->m_direct_arg_parser )
        {
            result = function->m_direct_arg_parser(
                function,
                NULL,
                0
            );
        }
        else
        {
            result = function->m_code(
                function,
                NULL,
                0,
                NULL
            );
        }

        Py_LeaveRecursiveCall();

        return result;
    }
    else if ( Nuitka_Method_Check( called ) )
    {
        Nuitka_MethodObject *method = (Nuitka_MethodObject *)called;

        // Unbound method without arguments, let the error path be slow.
        if ( method->m_object != NULL )
        {
            if (unlikely( Py_EnterRecursiveCall( (char *)" while calling a Python object" ) ))
            {
                return NULL;
            }

            PyObject *args[1] = {
                method->m_object
            };
            PyObject *result;

            if ( method->m_function->m_direct_arg_parser )
            {
                result = method->m_function->m_direct_arg_parser(
                    method->m_function,
                    args,
                    1
                );
            }
            else
            {
                result = method->m_function->m_code(
                    method->m_function,
                    args,
                    1,
                    NULL
                );
            }

            Py_LeaveRecursiveCall();

            return result;
        }
    }
    else if ( PyFunction_Check( called ) )
    {
        return _fast_function_noargs( called );
    }

    return CALL_FUNCTION(
        called,
        const_tuple_empty,
        NULL
    );
}

#if defined(_NUITKA_STANDALONE) || _NUITKA_FROZEN > 0

#if _NUITKA_FROZEN > 0
extern void copyFrozenModulesTo(void* destination);
#endif

#ifdef _NUITKA_STANDALONE
extern PyObject *const_str_plain___file__;

void setEarlyFrozenModulesFileAttribute( void )
{
    Py_ssize_t ppos = 0;
    PyObject *key, *value;

    PyObject *file_value = getBinaryDirectoryObject();

    char sep[2] = { SEP, 0 };

#if PYTHON_VERSION < 300
    file_value = PyNumber_InPlaceAdd( file_value, PyString_FromString( sep ) );
#else
    file_value = PyNumber_InPlaceAdd( file_value, PyUnicode_FromString( sep ) );
#endif

    assert( file_value );

#if PYTHON_VERSION < 300
    file_value = PyNumber_InPlaceAdd( file_value, PyString_FromString( "not_present.py" ) );
#else
    file_value = PyNumber_InPlaceAdd( file_value, PyUnicode_FromString( "not_present.py" ) );
#endif

    assert( file_value );

    while( PyDict_Next( PyImport_GetModuleDict(), &ppos, &key, &value ) )
    {
        if ( key != NULL && value != NULL && PyModule_Check( value ) )
        {
            if ( !PyObject_HasAttr( value, const_str_plain___file__ ) )
            {
                PyObject_SetAttr( value, const_str_plain___file__, file_value );
            }
        }
    }

    assert( !ERROR_OCCURRED() );
}
#endif

static char *original_home;
static char *original_path;

void prepareStandaloneEnvironment()
{
    // Tell the CPython library to use our pre-compiled modules as frozen
    // modules. This for those modules/packages like "encoding" that will be
    // loaded during "Py_Initialize" already, for the others they may be
    // compiled.

#if _NUITKA_FROZEN > 0
    // The CPython library has some pre-existing frozen modules, we only append
    // to that.
    const _frozen *search = PyImport_FrozenModules;
    while( search->name )
    {
        search++;
    }
    int pre_existing_count = int( search - PyImport_FrozenModules );

    // Allocate new memory and merge the tables.
    _frozen *merged = new _frozen[ _NUITKA_FROZEN + pre_existing_count + 1 ];
    memcpy(
        merged,
        PyImport_FrozenModules,
        pre_existing_count * sizeof( struct _frozen )
    );
    copyFrozenModulesTo(merged + pre_existing_count);
    PyImport_FrozenModules = merged;
#endif

#ifdef _NUITKA_STANDALONE
    // Setup environment variables to tell CPython that we would like it to use
    // the provided binary directory as the place to look for DLLs.
    char *binary_directory = getBinaryDirectoryHostEncoded();

#if defined( _WIN32 ) && defined( _MSC_VER )
    SetDllDirectory( binary_directory );
#endif

    // get original value
    original_home = getenv( "PYTHONHOME" );
    original_path = getenv( "PYTHONPATH" );
    size_t original_home_size = ( original_home ) ? strlen( original_home ) : 0;
    size_t original_path_size = ( original_path ) ? strlen( original_path ) : 0;

    // get insert value
    size_t insert_size = strlen( binary_directory ) * 2 + 50;
    char *insert_path = (char *) malloc( insert_size );

#if defined( _WIN32 )
    char const env_string[] = "%s;";
#else
    char const env_string[] = "%s:";
#endif

    memset( insert_path, 0, insert_size );
    snprintf( insert_path, insert_size, env_string, binary_directory );

    // set environment
    size_t python_home_size = original_home_size + insert_size;
    size_t python_path_size = original_path_size + insert_size;
    char *python_home = (char *) malloc( python_home_size );
    char *python_path = (char *) malloc( python_path_size );
    memset( python_home, 0, python_home_size );
    memset( python_path, 0, python_path_size );
    snprintf( python_home, python_home_size, "%s%s",
        insert_path, original_home ? original_home : "" );
    snprintf( python_path, python_path_size, "%s%s",
        insert_path, original_path ? original_path : "" );

    if ( !( original_home && strstr( original_home, insert_path ) ) )
    {
#if defined( _WIN32 )
        SetEnvironmentVariable( "PYTHONHOME", python_home );
#else
        setenv( "PYTHONHOME", python_home, 1 );
#endif
    }
    if ( !( original_path && strstr( original_path, insert_path ) ) )
    {
#if defined( _WIN32 )
        SetEnvironmentVariable( "PYTHONPATH", python_path );
#else
        setenv( "PYTHONPATH", python_path, 1 );
#endif
    }

    // clean up
    free( insert_path );
#endif
}

void restoreStandaloneEnvironment()
{
#if defined( _WIN32 )
    SetEnvironmentVariable( "PYTHONHOME", original_home );
#else
    if ( original_home == NULL )
    {
        unsetenv( "PYTHONHOME" );
    }
    else
    {
        setenv( "PYTHONHOME", original_home, 1 );
    }
#endif

#if defined( _WIN32 )
    SetEnvironmentVariable( "PYTHONHOME", original_path );
#else
    if ( original_path == NULL )
    {
        unsetenv( "PYTHONHOME" );
    }
    else
    {
        setenv( "PYTHONHOME", original_path, 1 );
    }
#endif
}

#endif


PyObject *MAKE_RELATIVE_PATH( PyObject *relative )
{
    static PyObject *our_path_object = NULL;

    if ( our_path_object == NULL )
    {
#if defined( _NUITKA_EXE )
        our_path_object = getBinaryDirectoryObject();
#else
#if PYTHON_VERSION >= 300
        our_path_object = PyUnicode_FromString( getDllDirectory() );
#else
        our_path_object = PyString_FromString( getDllDirectory() );
#endif

#endif
    }

    CHECK_OBJECT( our_path_object );

    static PyObject *os_path_join = NULL;

    if ( os_path_join == NULL )
    {
        PyObject *os_path = PyImport_ImportModule("os.path");
        CHECK_OBJECT(os_path);

        os_path_join = PyObject_GetAttrString(os_path, "join");
        CHECK_OBJECT(os_path_join);

        Py_DECREF(os_path);
    }

    PyObject *result = PyObject_CallFunctionObjArgs( os_path_join, our_path_object, relative, NULL );

    if (unlikely( result == NULL ))
    {
        PyErr_Print();
        abort();
    }

    return result;
}


#ifdef _NUITKA_EXE

#define DEFINE_BUILTIN( name ) extern PyObject *const_str_plain_##name; PyObject *_python_original_builtin_value_##name = NULL;

DEFINE_BUILTIN( type )
DEFINE_BUILTIN( len )
DEFINE_BUILTIN( range )
DEFINE_BUILTIN( repr )
DEFINE_BUILTIN( int )
DEFINE_BUILTIN( iter )
#if PYTHON_VERSION < 300
DEFINE_BUILTIN( long )
#endif

void _initBuiltinOriginalValues()
{
    ASSIGN_BUILTIN( type );
    ASSIGN_BUILTIN( len );
    ASSIGN_BUILTIN( range );
    ASSIGN_BUILTIN( repr );
    ASSIGN_BUILTIN( int );
    ASSIGN_BUILTIN( iter );
#if PYTHON_VERSION < 300
    ASSIGN_BUILTIN( long );
#endif

    CHECK_OBJECT( _python_original_builtin_value_range );
}

#endif

// Used for threading.
#if PYTHON_VERSION >= 300
volatile int _Py_Ticker = _Py_CheckInterval;
#endif

// Reverse operation mapping.
static int const swapped_op[] =
{
    Py_GT, Py_GE, Py_EQ, Py_NE, Py_LT, Py_LE
};

#if PYTHON_VERSION >= 270
iternextfunc default_iternext;

extern PyObject *const_str_plain___iter__;

void _initSlotIternext()
{
    PyObject *pos_args = PyTuple_New(1);
    PyTuple_SET_ITEM(
        pos_args,
        0,
        INCREASE_REFCOUNT( (PyObject *)&PyBaseObject_Type )
    );

    PyObject *kw_args = PyDict_New();
    PyDict_SetItem( kw_args, const_str_plain___iter__, Py_True );

    PyObject *c = PyObject_CallFunctionObjArgs(
        (PyObject *)&PyType_Type,
        const_str_plain___iter__,
        pos_args,
        kw_args,
        NULL
    );
    Py_DECREF( pos_args );
    Py_DECREF( kw_args );

    PyObject *r = PyObject_CallFunctionObjArgs(
        c,
        NULL
    );
    Py_DECREF( c );

    CHECK_OBJECT( r );
    assert( Py_TYPE( r )->tp_iternext );

    default_iternext = Py_TYPE(r)->tp_iternext;

    Py_DECREF( r );
}
#endif

#if PYTHON_VERSION < 300

extern PyObject *const_str_plain___cmp__;
static cmpfunc default_tp_compare;

void _initSlotCompare()
{
    // Create a class with "__cmp__" attribute, to get a hand at the default
    // implementation of tp_compare. It's not part of the API and with shared
    // libraries it's not accessible. The name does not matter, nor does the
    // actual value used for "__cmp__".

    PyObject *pos_args = PyTuple_New(1);
    PyTuple_SET_ITEM(
        pos_args,
        0,
        INCREASE_REFCOUNT( (PyObject *)&PyInt_Type )
    );

    PyObject *kw_args = PyDict_New();
    PyDict_SetItem( kw_args, const_str_plain___cmp__, Py_True );

    PyObject *c = PyObject_CallFunctionObjArgs(
        (PyObject *)&PyType_Type,
        const_str_plain___cmp__,
        pos_args,
        kw_args,
        NULL
    );
    Py_DECREF( pos_args );
    Py_DECREF( kw_args );

    PyObject *r = PyObject_CallFunctionObjArgs(
        c,
        NULL
    );
    Py_DECREF( c );

    CHECK_OBJECT( r );
    assert( Py_TYPE( r )->tp_compare );

    default_tp_compare = Py_TYPE( r )->tp_compare;

    Py_DECREF( r );
}

#define RICHCOMPARE( t ) ( PyType_HasFeature((t), Py_TPFLAGS_HAVE_RICHCOMPARE) ? (t)->tp_richcompare : NULL )

static inline int adjust_tp_compare( int c )
{
    if ( PyErr_Occurred() )
    {
        return -2;
    }
    else if (c < -1 || c > 1)
    {
        return c < -1 ? -1 : 1;
    }
    else
    {
        return c;
    }
}

static inline int coerce_objects( PyObject **pa, PyObject **pb )
{
    PyObject *a = *pa;
    PyObject *b = *pb;

    // Shortcut only for old-style types
    if ( a->ob_type == b->ob_type && !PyType_HasFeature( a->ob_type, Py_TPFLAGS_CHECKTYPES ))
    {
        Py_INCREF( a );
        Py_INCREF( b );

        return 0;
    }
    if ( a->ob_type->tp_as_number && a->ob_type->tp_as_number->nb_coerce )
    {
        int res = (*a->ob_type->tp_as_number->nb_coerce)( pa, pb );

        if ( res <= 0 )
        {
            return res;
        }
    }
    if ( b->ob_type->tp_as_number && b->ob_type->tp_as_number->nb_coerce )
    {
        int res = (*b->ob_type->tp_as_number->nb_coerce)( pb, pa );

        if ( res <= 0 )
        {
            return res;
        }
    }

    return 1;
}

static int try_3way_compare( PyObject *a, PyObject *b )
{
    cmpfunc f1 = a->ob_type->tp_compare;
    cmpfunc f2 = b->ob_type->tp_compare;
    int c;

    // Same compares, just use it.
    if ( f1 != NULL && f1 == f2 )
    {
        c = (*f1)( a, b );
        return adjust_tp_compare( c );
    }

    // If one slot is _PyObject_SlotCompare (which we got our hands on under a
    // different name in case it's a shared library), prefer it.
    if ( f1 == default_tp_compare || f2 == default_tp_compare )
    {
        return default_tp_compare( a, b );
    }

    // Try coercion.
    c = coerce_objects( &a, &b );

    if (c < 0)
    {
        return -2;
    }
    if (c > 0)
    {
        return 2;
    }

    f1 = a->ob_type->tp_compare;
    if ( f1 != NULL && f1 == b->ob_type->tp_compare )
    {
        c = (*f1)( a, b );
        Py_DECREF( a );
        Py_DECREF( b );

        return adjust_tp_compare(c);
    }

    // No comparison defined.
    Py_DECREF( a );
    Py_DECREF( b );
    return 2;
}

PyObject *MY_RICHCOMPARE( PyObject *a, PyObject *b, int op )
{
    CHECK_OBJECT( a );
    CHECK_OBJECT( b );

    // TODO: Type a-ware rich comparison would be really nice, but this is what
    // CPython does, and should be even in "richcomparisons.hpp" as the first
    // thing, so it's even cheaper.
    if ( PyInt_CheckExact( a ) && PyInt_CheckExact( b ))
    {
        long aa, bb;
#ifdef __NUITKA_NO_ASSERT__
        bool res;
#else
        bool res = false;
#endif

        aa = PyInt_AS_LONG( a );
        bb = PyInt_AS_LONG( b );

        switch( op )
        {
            case Py_LT: res = aa <  bb; break;
            case Py_LE: res = aa <= bb; break;
            case Py_EQ: res = aa == bb; break;
            case Py_NE: res = aa != bb; break;
            case Py_GT: res = aa >  bb; break;
            case Py_GE: res = aa >= bb; break;
            default: assert( false );
        }
        return INCREASE_REFCOUNT( BOOL_FROM( res ) );
    }

    // TODO: Get hint from recursion control if that's needed.
    if (unlikely( Py_EnterRecursiveCall((char *)" in cmp") ))
    {
        return NULL;
    }

    PyObject *result;

    // If the types are equal, we may get away immediately.
    if ( a->ob_type == b->ob_type && !PyInstance_Check( a ) )
    {
        richcmpfunc frich = RICHCOMPARE( a->ob_type );

        if ( frich != NULL )
        {
            result = (*frich)( a, b, op );

            if (result != Py_NotImplemented)
            {
                Py_LeaveRecursiveCall();
                return result;
            }

            Py_DECREF( result );
        }

        // No rich comparison, but maybe compare works.
        cmpfunc fcmp = a->ob_type->tp_compare;
        if ( fcmp != NULL )
        {
            int c = (*fcmp)( a, b );
            c = adjust_tp_compare( c );

            Py_LeaveRecursiveCall();

            if ( c == -2 )
            {
                return NULL;
            }

            switch( op )
            {
                case Py_LT: c = c <  0; break;
                case Py_LE: c = c <= 0; break;
                case Py_EQ: c = c == 0; break;
                case Py_NE: c = c != 0; break;
                case Py_GT: c = c >  0; break;
                case Py_GE: c = c >= 0; break;
            }

            return INCREASE_REFCOUNT( BOOL_FROM( c != 0 ) );
        }
    }

    // Fast path was not successful or not taken
    richcmpfunc f;

    if ( a->ob_type != b->ob_type && PyType_IsSubtype( b->ob_type, a->ob_type ) )
    {
        f = RICHCOMPARE( b->ob_type );

        if ( f != NULL )
        {
            result = (*f)( b, a, swapped_op[ op ] );

            if ( result != Py_NotImplemented )
            {
                Py_LeaveRecursiveCall();
                return result;
            }

           Py_DECREF( result );
        }
    }

    f = RICHCOMPARE( a->ob_type );
    if ( f != NULL )
    {
        result = (*f)( a, b, op );

        if ( result != Py_NotImplemented )
        {
            Py_LeaveRecursiveCall();
            return result;
        }

        Py_DECREF( result );
    }

    f = RICHCOMPARE( b->ob_type );
    if ( f != NULL )
    {
        result = (*f)( b, a, swapped_op[ op ] );

        if ( result != Py_NotImplemented )
        {
            Py_LeaveRecursiveCall();
            return result;
        }

        Py_DECREF( result );
    }

    int c;

    if ( PyInstance_Check( a ) )
    {
        c = (*a->ob_type->tp_compare)( a, b );
    }
    else if ( PyInstance_Check( b ) )
    {
        c = (*b->ob_type->tp_compare)( a, b );
    }
    else
    {
        c = try_3way_compare( a, b );
    }

    if ( c >= 2 )
    {
        if ( a->ob_type == b->ob_type )
        {
            Py_uintptr_t aa = (Py_uintptr_t)a;
            Py_uintptr_t bb = (Py_uintptr_t)b;

            c = ( aa < bb ) ? -1 : ( aa > bb ) ? 1 : 0;
        }
        else if ( a == Py_None )
        {
            // None is smaller than everything else
            c = -1;
        }
        else if ( b == Py_None )
        {
            // None is smaller than everything else
            c = 1;
        }
        else if ( PyNumber_Check( a ) )
        {
            // different type: compare type names but numbers are smaller than
            // others.
            if ( PyNumber_Check( b ) )
            {
                // Both numbers, need to make a decision based on types.
                Py_uintptr_t aa = (Py_uintptr_t)Py_TYPE( a );
                Py_uintptr_t bb = (Py_uintptr_t)Py_TYPE( b );

                c = ( aa < bb ) ? -1 : ( aa > bb ) ? 1 : 0;
            }
            else
            {
                c = -1;
            }
        }
        else if ( PyNumber_Check( b ) )
        {
            c = 1;
        }
        else
        {
            int s = strcmp( a->ob_type->tp_name, b->ob_type->tp_name );

            if ( s < 0 )
            {
                c = -1;
            }
            else if ( s > 0 )
            {
                c = 1;
            }
            else
            {
                // Same type name need to make a decision based on types.
                Py_uintptr_t aa = (Py_uintptr_t)Py_TYPE( a );
                Py_uintptr_t bb = (Py_uintptr_t)Py_TYPE( b );

                c = ( aa < bb ) ? -1 : ( aa > bb ) ? 1 : 0;
            }
        }
    }

    Py_LeaveRecursiveCall();

    if (unlikely( c <= -2 ))
    {
        return NULL;
    }

    switch( op )
    {
        case Py_LT: c = c <  0; break;
        case Py_LE: c = c <= 0; break;
        case Py_EQ: c = c == 0; break;
        case Py_NE: c = c != 0; break;
        case Py_GT: c = c >  0; break;
        case Py_GE: c = c >= 0; break;
    }

    return INCREASE_REFCOUNT( BOOL_FROM( c != 0 ) );
}

PyObject *MY_RICHCOMPARE_NORECURSE( PyObject *a, PyObject *b, int op )
{
    CHECK_OBJECT( a );
    CHECK_OBJECT( b );

    // TODO: Type a-ware rich comparison would be really nice, but this is what
    // CPython does, and should be even in "richcomparisons.hpp" as the first
    // thing, so it's even cheaper.
    if ( PyInt_CheckExact( a ) && PyInt_CheckExact( b ))
    {
        long aa, bb;
#ifdef __NUITKA_NO_ASSERT__
        bool res;
#else
        bool res = false;
#endif

        aa = PyInt_AS_LONG( a );
        bb = PyInt_AS_LONG( b );

        switch( op )
        {
            case Py_LT: res = aa <  bb; break;
            case Py_LE: res = aa <= bb; break;
            case Py_EQ: res = aa == bb; break;
            case Py_NE: res = aa != bb; break;
            case Py_GT: res = aa >  bb; break;
            case Py_GE: res = aa >= bb; break;
            default: assert( false );
        }
        return INCREASE_REFCOUNT( BOOL_FROM( res ) );
    }

    PyObject *result;

    // If the types are equal, we may get away immediately.
    if ( a->ob_type == b->ob_type && !PyInstance_Check( a ) )
    {
        richcmpfunc frich = RICHCOMPARE( a->ob_type );

        if ( frich != NULL )
        {
            result = (*frich)( a, b, op );

            if (result != Py_NotImplemented)
            {
                return result;
            }

            Py_DECREF( result );
        }

        // No rich comparison, but maybe compare works.
        cmpfunc fcmp = a->ob_type->tp_compare;
        if ( fcmp != NULL )
        {
            int c = (*fcmp)( a, b );
            c = adjust_tp_compare( c );

            if ( c == -2 )
            {
                return NULL;
            }

            switch( op )
            {
                case Py_LT: c = c <  0; break;
                case Py_LE: c = c <= 0; break;
                case Py_EQ: c = c == 0; break;
                case Py_NE: c = c != 0; break;
                case Py_GT: c = c >  0; break;
                case Py_GE: c = c >= 0; break;
            }

            return INCREASE_REFCOUNT( BOOL_FROM( c != 0 ) );
        }
    }

    // Fast path was not successful or not taken
    richcmpfunc f;

    if ( a->ob_type != b->ob_type && PyType_IsSubtype( b->ob_type, a->ob_type ) )
    {
        f = RICHCOMPARE( b->ob_type );

        if ( f != NULL )
        {
            result = (*f)( b, a, swapped_op[ op ] );

            if ( result != Py_NotImplemented )
            {
                return result;
            }

           Py_DECREF( result );
        }
    }

    f = RICHCOMPARE( a->ob_type );
    if ( f != NULL )
    {
        result = (*f)( a, b, op );

        if ( result != Py_NotImplemented )
        {
            return result;
        }

        Py_DECREF( result );
    }

    f = RICHCOMPARE( b->ob_type );
    if ( f != NULL )
    {
        result = (*f)( b, a, swapped_op[ op ] );

        if ( result != Py_NotImplemented )
        {
            return result;
        }

        Py_DECREF( result );
    }

    int c;

    if ( PyInstance_Check( a ) )
    {
        c = (*a->ob_type->tp_compare)( a, b );
    }
    else if ( PyInstance_Check( b ) )
    {
        c = (*b->ob_type->tp_compare)( a, b );
    }
    else
    {
        c = try_3way_compare( a, b );
    }

    if ( c >= 2 )
    {
        if ( a->ob_type == b->ob_type )
        {
            Py_uintptr_t aa = (Py_uintptr_t)a;
            Py_uintptr_t bb = (Py_uintptr_t)b;

            c = ( aa < bb ) ? -1 : ( aa > bb ) ? 1 : 0;
        }
        else if ( a == Py_None )
        {
            // None is smaller than everything else
            c = -1;
        }
        else if ( b == Py_None )
        {
            // None is smaller than everything else
            c = 1;
        }
        else if ( PyNumber_Check( a ) )
        {
            // different type: compare type names but numbers are smaller than
            // others.
            if ( PyNumber_Check( b ) )
            {
                // Both numbers, need to make a decision based on types.
                Py_uintptr_t aa = (Py_uintptr_t)Py_TYPE( a );
                Py_uintptr_t bb = (Py_uintptr_t)Py_TYPE( b );

                c = ( aa < bb ) ? -1 : ( aa > bb ) ? 1 : 0;
            }
            else
            {
                c = -1;
            }
        }
        else if ( PyNumber_Check( b ) )
        {
            c = 1;
        }
        else
        {
            int s = strcmp( a->ob_type->tp_name, b->ob_type->tp_name );

            if ( s < 0 )
            {
                c = -1;
            }
            else if ( s > 0 )
            {
                c = 1;
            }
            else
            {
                // Same type name need to make a decision based on types.
                Py_uintptr_t aa = (Py_uintptr_t)Py_TYPE( a );
                Py_uintptr_t bb = (Py_uintptr_t)Py_TYPE( b );

                c = ( aa < bb ) ? -1 : ( aa > bb ) ? 1 : 0;
            }
        }
    }

    if (unlikely( c <= -2 ))
    {
        return NULL;
    }

    switch( op )
    {
        case Py_LT: c = c <  0; break;
        case Py_LE: c = c <= 0; break;
        case Py_EQ: c = c == 0; break;
        case Py_NE: c = c != 0; break;
        case Py_GT: c = c >  0; break;
        case Py_GE: c = c >= 0; break;
    }

    return INCREASE_REFCOUNT( BOOL_FROM( c != 0 ) );
}

#else

// Table for operation names as strings.
static char const *op_strings[] =
{
    "<", "<=", "==", "!=", ">", ">="
};

PyObject *MY_RICHCOMPARE( PyObject *a, PyObject *b, int op )
{
    CHECK_OBJECT( a );
    CHECK_OBJECT( b );

    if (unlikely( Py_EnterRecursiveCall( (char *)" in comparison" ) ))
    {
        return NULL;
    }

    bool checked_reverse_op = false;
    PyObject *result = NULL;
    richcmpfunc f;

    if ( a->ob_type != b->ob_type && PyType_IsSubtype( b->ob_type, a->ob_type ) )
    {
        f = b->ob_type->tp_richcompare;
        if ( f != NULL )
        {
            checked_reverse_op = true;

            result = (*f)( b, a, swapped_op[ op ] );

            if (unlikely( result == NULL ))
            {
                Py_LeaveRecursiveCall();
                return NULL;
            }

            if ( result == Py_NotImplemented )
            {
                Py_DECREF( result );
                result = NULL;
            }

        }
    }

    if ( result == NULL )
    {
        f = a->ob_type->tp_richcompare;

        if ( f != NULL )
        {
            result = (*f)( a, b, op );

            if (unlikely( result == NULL ))
            {
                Py_LeaveRecursiveCall();
                return NULL;
            }

            if ( result == Py_NotImplemented )
            {
                Py_DECREF( result );
                result = NULL;
            }
        }
    }

    if ( result == NULL && checked_reverse_op == false )
    {
        f = b->ob_type->tp_richcompare;

        if ( f != NULL )
        {
            result = (*f)( b, a, swapped_op[ op ] );

            if (unlikely( result == NULL ))
            {
                Py_LeaveRecursiveCall();
                return NULL;
            }

            if ( result == Py_NotImplemented )
            {
                Py_DECREF( result );
                result = NULL;
            }
        }
    }

    Py_LeaveRecursiveCall();

    if ( result != NULL )
    {
        return result;
    }

    // If it is not implemented, do identify checks as "==" and "!=" and
    // otherwise give an error
    if ( op == Py_EQ )
    {
        return INCREASE_REFCOUNT( BOOL_FROM( a == b ) );
    }
    else if ( op == Py_NE )
    {
        return INCREASE_REFCOUNT( BOOL_FROM( a != b ) );
    }
    else
    {
        PyErr_Format(
            PyExc_TypeError,
            "unorderable types: %s() %s %s()",
            a->ob_type->tp_name,
            op_strings[ op ],
            b->ob_type->tp_name
        );

        return NULL;
    }
}

PyObject *MY_RICHCOMPARE_NORECURSE( PyObject *a, PyObject *b, int op )
{
    CHECK_OBJECT( a );
    CHECK_OBJECT( b );

    bool checked_reverse_op = false;
    PyObject *result = NULL;
    richcmpfunc f;

    if ( a->ob_type != b->ob_type && PyType_IsSubtype( b->ob_type, a->ob_type ) )
    {
        f = b->ob_type->tp_richcompare;
        if ( f != NULL )
        {
            checked_reverse_op = true;

            result = (*f)( b, a, swapped_op[ op ] );

            if (unlikely( result == NULL ))
            {
                return NULL;
            }

            if ( result == Py_NotImplemented )
            {
                Py_DECREF( result );
                result = NULL;
            }

        }
    }

    if ( result == NULL )
    {
        f = a->ob_type->tp_richcompare;

        if ( f != NULL )
        {
            result = (*f)( a, b, op );

            if (unlikely( result == NULL ))
            {
                return NULL;
            }

            if ( result == Py_NotImplemented )
            {
                Py_DECREF( result );
                result = NULL;
            }
        }
    }

    if ( result == NULL && checked_reverse_op == false )
    {
        f = b->ob_type->tp_richcompare;

        if ( f != NULL )
        {
            result = (*f)( b, a, swapped_op[ op ] );

            if (unlikely( result == NULL ))
            {
                return NULL;
            }

            if ( result == Py_NotImplemented )
            {
                Py_DECREF( result );
                result = NULL;
            }
        }
    }

    if ( result != NULL )
    {
        return result;
    }

    // If it is not implemented, do identify checks as "==" and "!=" and
    // otherwise give an error
    if ( op == Py_EQ )
    {
        return INCREASE_REFCOUNT( BOOL_FROM( a == b ) );
    }
    else if ( op == Py_NE )
    {
        return INCREASE_REFCOUNT( BOOL_FROM( a != b ) );
    }
    else
    {
        PyErr_Format(
            PyExc_TypeError,
            "unorderable types: %s() %s %s()",
            a->ob_type->tp_name,
            op_strings[ op ],
            b->ob_type->tp_name
        );

        return NULL;
    }
}


#endif

PyObject *DEEP_COPY( PyObject *value )
{
    if ( PyDict_Check( value ) )
    {
        // For Python3.3, this can be done much faster in the same way as it is
        // done in parameter parsing.

#if PYTHON_VERSION < 330
        PyObject *result = _PyDict_NewPresized( ((PyDictObject *)value)->ma_used  );

        for ( Py_ssize_t i = 0; i <= ((PyDictObject *)value)->ma_mask; i++ )
        {
            PyDictEntry *entry = &((PyDictObject *)value)->ma_table[ i ];

            if ( entry->me_value != NULL )
            {
                PyObject *deep_copy = DEEP_COPY( entry->me_value );

                int res = PyDict_SetItem(
                    result,
                    entry->me_key,
                    deep_copy
                );

                Py_DECREF( deep_copy );

                if (unlikely( res != 0 ))
                {
                    return NULL;
                }
            }
        }

        return result;
#else
        if ( _PyDict_HasSplitTable( (PyDictObject *)value) )
        {
            PyDictObject *mp = (PyDictObject *)value;

            PyObject **newvalues = PyMem_NEW( PyObject *, mp->ma_keys->dk_size );
            assert( newvalues != NULL );

            PyDictObject *result = PyObject_GC_New( PyDictObject, &PyDict_Type );
            assert( result != NULL );

            result->ma_values = newvalues;
            result->ma_keys = mp->ma_keys;
            result->ma_used = mp->ma_used;

            mp->ma_keys->dk_refcnt += 1;

            Nuitka_GC_Track( result );

            Py_ssize_t size = mp->ma_keys->dk_size;
            for ( Py_ssize_t i = 0; i < size; i++ )
            {
                if ( mp->ma_values[ i ] )
                {
                    result->ma_values[ i ] = DEEP_COPY( mp->ma_values[ i ] );
                }
                else
                {
                    result->ma_values[ i ] = NULL;
                }
            }

            return (PyObject *)result;
        }
        else
        {
            PyObject *result = _PyDict_NewPresized( ((PyDictObject *)value)->ma_used  );

            PyDictObject *mp = (PyDictObject *)value;

            Py_ssize_t size = mp->ma_keys->dk_size;
            for ( Py_ssize_t i = 0; i < size; i++ )
            {
                PyDictKeyEntry *entry = &mp->ma_keys->dk_entries[i];

                PyObject *value;

                if ( mp->ma_values )
                {
                    value = mp->ma_values[ i ];
                }
                else
                {
                    value = entry->me_value;
                }

                if ( value != NULL )
                {
                    PyObject *deep_copy = DEEP_COPY( value );

                    PyDict_SetItem(
                        result,
                        entry->me_key,
                        deep_copy
                    );

                    Py_DECREF( deep_copy );
                }
            }

            return result;
        }
#endif
    }
    else if ( PyTuple_Check( value ) )
    {
        Py_ssize_t n = PyTuple_Size( value );
        PyObject *result = PyTuple_New( n );

        for( Py_ssize_t i = 0; i < n; i++ )
        {
            PyTuple_SET_ITEM( result, i, DEEP_COPY( PyTuple_GET_ITEM( value, i ) ) );
        }

        return result;
    }
    else if ( PyList_Check( value ) )
    {
        Py_ssize_t n = PyList_GET_SIZE( value );
        PyObject *result = PyList_New( n );

        for( Py_ssize_t i = 0; i < n; i++ )
        {
            PyList_SET_ITEM( result, i, DEEP_COPY( PyList_GET_ITEM( value, i ) ) );
        }

        return result;
    }
    else if ( PySet_Check( value ) )
    {
        // Sets cannot contain unhashable types, so they must be immutable.
        return PySet_New( value );
    }
    else if (
#if PYTHON_VERSION < 300
        PyString_Check( value )  ||
#endif
        PyUnicode_Check( value ) ||
#if PYTHON_VERSION < 300
        PyInt_Check( value )     ||
#endif
        PyLong_Check( value )    ||
        value == Py_None         ||
        PyBool_Check( value )    ||
        PyFloat_Check( value )   ||
        PyBytes_Check( value )   ||
#if PYTHON_VERSION >= 300
        PyRange_Check( value )   ||
#endif
        PyType_Check( value )    ||
        PySlice_Check( value )   ||
        PyComplex_Check( value ) ||
        value == Py_Ellipsis
        )
    {
        return INCREASE_REFCOUNT( value );
    }
    else
    {
        PyErr_Format(
            PyExc_TypeError,
            "DEEP_COPY does not implement: %s",
            value->ob_type->tp_name
        );

        return NULL;
    }
}

#ifndef __NUITKA_NO_ASSERT__

static Py_hash_t DEEP_HASH_INIT( PyObject *value )
{
    Py_hash_t result = Py_hash_t( value );

    if ( Py_TYPE( value ) != &PyType_Type )
    {
        result ^= DEEP_HASH( (PyObject *)Py_TYPE( value ) );
    }

    return result;
}

static void DEEP_HASH_BLOB( Py_hash_t *hash, char const *s, Py_ssize_t size )
{
    while( size > 0 )
    {
        *hash = ( 1000003 * (*hash) ) ^ Py_hash_t( *s++ );
        size--;
    }
}

static void DEEP_HASH_CSTR( Py_hash_t *hash, char const *s )
{
    DEEP_HASH_BLOB( hash, s, strlen( s ) );
}

// Hash function that actually verifies things done to the bit level. Can be
// used to detect corruption.
Py_hash_t DEEP_HASH( PyObject *value )
{
    assert( value != NULL );

    if ( PyType_Check( value ) )
    {
        Py_hash_t result = DEEP_HASH_INIT( value );

        DEEP_HASH_CSTR( &result, ((PyTypeObject *)value)->tp_name );
        return result;
    }
    else if ( PyDict_Check( value ) )
    {
        Py_hash_t result = DEEP_HASH_INIT( value );

        Py_ssize_t ppos = 0;
        PyObject *key, *dict_value;

        while( PyDict_Next( value, &ppos, &key, &dict_value ) )
        {
            if ( key != NULL && value != NULL )
            {
                result ^= DEEP_HASH( key );
                result ^= DEEP_HASH( dict_value );
            }
        }

        return result;
    }
    else if ( PyTuple_Check( value ) )
    {
        Py_hash_t result = DEEP_HASH_INIT( value );

        Py_ssize_t n = PyTuple_Size( value );

        for( Py_ssize_t i = 0; i < n; i++ )
        {
            result ^= DEEP_HASH( PyTuple_GET_ITEM( value, i ) );
        }

        return result;
    }
    else if ( PyList_Check( value ) )
    {
        Py_hash_t result = DEEP_HASH_INIT( value );

        Py_ssize_t n = PyList_GET_SIZE( value );

        for( Py_ssize_t i = 0; i < n; i++ )
        {
            result ^= DEEP_HASH( PyList_GET_ITEM( value, i ) );
        }

        return result;
    }
    else if ( PySet_Check( value ) )
    {
        Py_hash_t result = DEEP_HASH_INIT( value );

        PyObject *iterator = PyObject_GetIter( value );
        CHECK_OBJECT( iterator );

        while( true )
        {
            PyObject *item = PyIter_Next( iterator );
            if (!item) break;

            CHECK_OBJECT( item );

            result ^= DEEP_HASH( item );

            Py_DECREF( item );
        }

        Py_DECREF( iterator );

        return result;
    }
    else if ( PyLong_Check( value ) )
    {
        Py_hash_t result = DEEP_HASH_INIT( value );

        PyObject *exception_type, *exception_value;
        PyTracebackObject *exception_tb;

        FETCH_ERROR_OCCURRED_UNTRACED( &exception_type, &exception_value, &exception_tb );

        // Use string to hash the long value, which relies on that to not
        // use the object address.
        PyObject *str = PyObject_Str( value );
        result ^= DEEP_HASH( str );
        Py_DECREF( str );

        RESTORE_ERROR_OCCURRED_UNTRACED( exception_type, exception_value, exception_tb );

        return result;
    }
    else if ( PyUnicode_Check( value ) )
    {
        Py_hash_t result = DEEP_HASH( (PyObject *)Py_TYPE( value ) );

        PyObject *exception_type, *exception_value;
        PyTracebackObject *exception_tb;

        FETCH_ERROR_OCCURRED_UNTRACED( &exception_type, &exception_value, &exception_tb );

#if PYTHON_PYTHON >= 330
        Py_ssize_t size;
        char *s = PyUnicode_AsUTF8AndSize( value, &size );

        if ( s != NULL )
        {
            DEEP_HASH_BLOB( &result, s, size );
        }
#elif PYTHON_VERSION >= 300
        // Not done for Python3.2 really yet.
#else
        PyObject *str = PyUnicode_AsUTF8String( value );

        if ( str )
        {
            result ^= DEEP_HASH( str );
        }

        Py_DECREF( str );
#endif
        RESTORE_ERROR_OCCURRED_UNTRACED( exception_type, exception_value, exception_tb );

        return result;
    }
#if PYTHON_VERSION < 300
    else if ( PyString_Check( value ) )
    {
        Py_hash_t result = DEEP_HASH( (PyObject *)Py_TYPE( value ) );

        Py_ssize_t size;
        char *s;

        int res = PyString_AsStringAndSize( value, &s, &size );
        assert( res != -1 );

        DEEP_HASH_BLOB( &result, s, size );

        return result;
    }
#else
    else if ( PyBytes_Check( value ) )
    {
        Py_hash_t result = DEEP_HASH_INIT( value );

        Py_ssize_t size;
        char *s;

        int res = PyBytes_AsStringAndSize( value, &s, &size );
        assert( res != -1 );

        DEEP_HASH_BLOB( &result, s, size );

        return result;
    }
#endif
    else if ( value == Py_None || value == Py_Ellipsis )
    {
        return DEEP_HASH_INIT( value );
    }
    else if ( PyComplex_Check( value ) )
    {
        Py_complex c = PyComplex_AsCComplex( value );

        Py_hash_t result = DEEP_HASH_INIT( value );

        Py_ssize_t size = sizeof(c);
        char *s = (char *)&c;

        DEEP_HASH_BLOB( &result, s, size );

        return result;
    }
    else if ( PyFloat_Check( value ) )
    {
        double f = PyFloat_AsDouble( value );

        Py_hash_t result = DEEP_HASH_INIT( value );

        Py_ssize_t size = sizeof(f);
        char *s = (char *)&f;

        DEEP_HASH_BLOB( &result, s, size );

        return result;
    }
    else if (
#if PYTHON_VERSION < 300
        PyInt_Check( value )     ||
#endif
        PyBool_Check( value )    ||
#if PYTHON_VERSION >= 300
        PyRange_Check( value )   ||
#endif
        PySlice_Check( value )
        )
    {
        Py_hash_t result = DEEP_HASH_INIT( value );

#if 0
        printf("Too simple deep hash: %s\n", Py_TYPE( value )->tp_name );
#endif

        return result;
    }
    else
    {
        assert( false );

        return -1;
    }
}
#endif
