//     Copyright 2018, 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.
//
/** For calling built-ins, calls it and uses keyword dictionary if necessary.
 *
 * This helper simplifies calling built-ins with optional arguments that can
 * be given as keyword arguments. We basically re-construct the minimal call
 * using keywords here. This obviously is for inefficient calls to the original
 * built-in and should be avoided.
 *
 **/

static PyObject *CALL_BUILTIN_KW_ARGS( PyObject *callable, PyObject **args, char const **arg_names, int max_args )
{
    int i = 0;

    while( i < max_args )
    {
        if ( args[i] == NULL ) break;

        CHECK_OBJECT( args[i] );

        i++;
    }

    int usable_args = i;

    PyObject *kw_dict = NULL;

    while( i < max_args )
    {
        if ( args[i] != NULL )
        {
            CHECK_OBJECT( args[i] );

            if ( kw_dict == NULL )
            {
                kw_dict = PyDict_New();
            }

            int res = PyDict_SetItemString( kw_dict, arg_names[i], args[i] );
            assert( res == 0 );
        }

        i++;
    }

    PyObject *args_tuple = PyTuple_New( usable_args );
    for ( i = 0; i < usable_args; i++ )
    {
        PyTuple_SET_ITEM( args_tuple, i, args[i] );

        Py_INCREF( args[i] );
    }

    PyObject *result = CALL_FUNCTION( callable, args_tuple, kw_dict );
    Py_XDECREF( kw_dict );
    Py_DECREF( args_tuple );

    return result;
}


/** The "compile" built-in.
 *
 */

NUITKA_DEFINE_BUILTIN( 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 ) )
    {
        Py_INCREF( source_code );
        return source_code;
    }

    PyObject *pos_args = PyTuple_New(3);
    PyTuple_SET_ITEM( pos_args, 0, source_code );
    Py_INCREF( source_code );
    PyTuple_SET_ITEM( pos_args, 1, file_name );
    Py_INCREF( file_name );
    PyTuple_SET_ITEM( pos_args, 2, mode );
    Py_INCREF( 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

    NUITKA_ASSIGN_BUILTIN( compile );

    PyObject *result = CALL_FUNCTION(
        NUITKA_ACCESS_BUILTIN( compile ),
        pos_args,
        kw_args
    );

    Py_DECREF( pos_args );
    Py_XDECREF( kw_args );

    return result;
}

/**
 *  The "eval" implementation, used for "exec" too.
 */


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;
}

/** The "open" built-in.
 *
 * Different for Python2 and Python3, the later has more arguments and
 * both accept keyword arguments.
 *
 **/


NUITKA_DEFINE_BUILTIN( open );

#if PYTHON_VERSION < 300
PyObject *BUILTIN_OPEN( PyObject *file_name, PyObject *mode, PyObject *buffering )
{
    NUITKA_ASSIGN_BUILTIN( open );

    PyObject *args[] =
    {
        file_name,
        mode,
        buffering
    };

    char const *arg_names[] = {
        "file_name",
        "mode",
        "buffering"
    };

    return CALL_BUILTIN_KW_ARGS( NUITKA_ACCESS_BUILTIN( open ), args, arg_names, 3 );
}
#else
PyObject *BUILTIN_OPEN( PyObject *file_name, PyObject *mode, PyObject *buffering,
                        PyObject *encoding, PyObject *errors, PyObject *newline,
                        PyObject *closefd, PyObject *opener)
{
    NUITKA_ASSIGN_BUILTIN( open );

    PyObject *args[] =
    {
        file_name,
        mode,
        buffering,
        encoding,
        errors,
        newline,
        closefd,
        opener
    };

    char const *arg_names[] = {
        "file_name",
        "mode",
        "buffering",
        "encoding",
        "errors",
        "newline",
        "closefd",
        "opener"
    };

    return CALL_BUILTIN_KW_ARGS( NUITKA_ACCESS_BUILTIN( open ), args, arg_names, 8 );
}

#endif

/** The "staticmethod" built-in.
 *
 **/


NUITKA_DEFINE_BUILTIN(staticmethod)

PyObject *BUILTIN_STATICMETHOD( PyObject *value )
{
    NUITKA_ASSIGN_BUILTIN( staticmethod );
    PyObject *args[] = { value };

    return CALL_FUNCTION_WITH_ARGS1( NUITKA_ACCESS_BUILTIN( staticmethod ), args );
}

/** The "classmethod" built-in.
 *
 **/

NUITKA_DEFINE_BUILTIN(classmethod)

PyObject *BUILTIN_CLASSMETHOD( PyObject *value )
{
    NUITKA_ASSIGN_BUILTIN( classmethod );
    PyObject *args[] = { value };

    return CALL_FUNCTION_WITH_ARGS1( NUITKA_ACCESS_BUILTIN( classmethod ), args );
}

#if PYTHON_VERSION >= 300

/** The "bytes" built-in.
 *
 * Only for Python3. There is not BYTES_BUILTIN1 yet, this only delegates to
 * the actual built-in which is wasteful. TODO: Have dedicated implementation
 * for this.
 *
 **/

NUITKA_DEFINE_BUILTIN( bytes );

PyObject *BUILTIN_BYTES3( PyObject *value, PyObject *encoding, PyObject *errors )
{
    NUITKA_ASSIGN_BUILTIN( bytes );

    PyObject *args[] =
    {
        value,
        encoding,
        errors
    };

    char const *arg_names[] = {
        "value",
        "encoding",
        "errors"
    };

    return CALL_BUILTIN_KW_ARGS( NUITKA_ACCESS_BUILTIN( bytes ), args, arg_names, 3 );
}
#endif

/** The "bin" built-in.
 *
 **/

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

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

    return result;
}

/** The "oct" built-in.
 *
 **/

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
}

/** The "hex" built-in.
 *
 **/

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
}

/** The "hash" built-in.
 *
 **/

PyObject *BUILTIN_HASH( PyObject *value )
{
    Py_hash_t hash = PyObject_Hash( value );

    if (unlikely( hash == -1 ))
    {
        return NULL;
    }

#if PYTHON_VERSION < 300
    return PyInt_FromLong( hash );
#else
    return PyLong_FromSsize_t( hash );
#endif
}

/** The "bytearray" built-in.
 *
 * These should be more in-lined maybe, as a lot of checks are not necessary
 * and the error checking for the 3 arguments variant may even not be enough,
 * as it could be keyword arguments.
 *
 **/

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

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

    return result;
}

NUITKA_DEFINE_BUILTIN( bytearray )

PyObject *BUILTIN_BYTEARRAY3( PyObject *string, PyObject *encoding, PyObject *errors )
{
    CHECK_OBJECT( string );
    CHECK_OBJECT( encoding );

    NUITKA_ASSIGN_BUILTIN( bytearray );

    if ( errors == NULL )
    {
        PyObject *args[] = { string, encoding };

        PyObject *result = CALL_FUNCTION_WITH_ARGS2(
            NUITKA_ACCESS_BUILTIN( bytearray ),
            args
        );

        return result;
    }
    else
    {
        PyObject *args[] = { string, encoding, errors };

        PyObject *result = CALL_FUNCTION_WITH_ARGS3(
            NUITKA_ACCESS_BUILTIN( bytearray ),
            args
        );

        return result;
    }
}

/** The "iter" built-in.
 *
 * This comes in two flavors, with one or two arguments. The second one
 * creates a "calliterobject" that is private to CPython. We define it here
 * for ourselves. The one argument version is in headers for in-lining of
 * the code.
 *
 **/


// 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 = callable;
    Py_INCREF( callable );
    result->it_sentinel = sentinel;
    Py_INCREF( sentinel );

    Nuitka_GC_Track( result );

    return (PyObject *)result;
}

/** The "type" built-in.
 *
 * This comes in two flavors, one being the detection of a values type,
 * and 3 argument variant creates a new type.
 *
 **/

PyObject *BUILTIN_TYPE1( PyObject *arg )
{
    PyObject *result = (PyObject *)Py_TYPE( arg );

    Py_INCREF( result );
    return result;
}

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, name );
    Py_INCREF( name );
    PyTuple_SET_ITEM( pos_args, 1, bases );
    Py_INCREF( bases );
    PyTuple_SET_ITEM( pos_args, 2, dict );
    Py_INCREF( 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;
}

/** The "super" built-in.
 *
 * This uses a private structure "superobject" that we declare here too.
 *
 **/

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 *)type;
    Py_INCREF( type );
    if ( object )
    {
        result->obj = object;
        Py_INCREF( object );

        if ( PyType_Check( object ) && PyType_IsSubtype( (PyTypeObject *)object, (PyTypeObject *)type ))
        {
            result->obj_type = (PyTypeObject *)object;
            Py_INCREF( object );
        }
        else if ( PyType_IsSubtype( Py_TYPE(object ), (PyTypeObject *)type) )
        {
            result->obj_type = Py_TYPE( object );
            Py_INCREF( result->obj_type );
        }
        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;
}

/** The "callable" built-in.
 *
 **/

PyObject *BUILTIN_CALLABLE( PyObject *value )
{
    int res = PyCallable_Check( value );
    PyObject *result = BOOL_FROM( res != 0 );
    Py_INCREF( result );
    return result;
}

/* The "getattr" built-in with default value.
 *
 * We might want to split it off for a variant without default value.
 *
 **/

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();

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

/** The "setattr" built-in.
 *
 **/

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

    if ( res < 0 )
    {
        return NULL;
    }

    Py_INCREF( Py_None );
    return Py_None;
}

/** The "divmod" built-in.
 *
 * This is really a binary number operation and probably should live
 * where the others are.
 *
 **/

PyObject *BUILTIN_DIVMOD( PyObject *operand1, PyObject *operand2 )
{
	CHECK_OBJECT( operand1 );
	CHECK_OBJECT( operand2 );

	binaryfunc slot1 = NULL;
	binaryfunc slot2 = NULL;

	PyTypeObject *type1 = Py_TYPE( operand1 );
	PyTypeObject *type2 = Py_TYPE( operand2 );

	if ( type1->tp_as_number != NULL && NEW_STYLE_NUMBER( operand1 ) )
	{
		slot1 = type1->tp_as_number->nb_divmod;
	}

	if ( type1 != type2 )
	{
		if ( type2->tp_as_number != NULL && NEW_STYLE_NUMBER( operand2 ) )
		{
			slot2 = type2->tp_as_number->nb_divmod;

			if ( slot1 == slot2 )
			{
				slot2 = NULL;
			}
		}
	}

	if ( slot1 != NULL )
	{
		if ( slot2 && PyType_IsSubtype( type2, type1 ) )
		{
			PyObject *x = slot2( operand1, operand2 );

			if ( x != Py_NotImplemented )
			{
				if (unlikely( x == NULL ))
				{
					return NULL;
				}

				return x;
			}

			Py_DECREF( x );
			slot2 = NULL;
		}

		PyObject *x = slot1( operand1, operand2 );

		if ( x != Py_NotImplemented )
		{
			if (unlikely( x == NULL ))
			{
				return NULL;
			}

			return x;
		}

		Py_DECREF( x );
	}

	if ( slot2 != NULL )
	{
		PyObject *x = slot2( operand1, operand2 );

		if ( x != Py_NotImplemented )
		{
			if (unlikely( x == NULL ))
			{
				return NULL;
			}

			return x;
		}

		Py_DECREF( x );
	}

#if PYTHON_VERSION < 300
	if ( !NEW_STYLE_NUMBER( operand1 ) || !NEW_STYLE_NUMBER( operand2 ) )
	{
		int err = PyNumber_CoerceEx( &operand1, &operand2 );

		if ( err < 0 )
		{
			return NULL;
		}

		if ( err == 0 )
		{
			PyNumberMethods *mv = Py_TYPE( operand1 )->tp_as_number;

			if ( mv )
			{
				binaryfunc slot = mv->nb_divmod;

				if ( slot != NULL )
				{
					PyObject *x = slot( operand1, operand2 );

					Py_DECREF( operand1 );
					Py_DECREF( operand2 );

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

					return x;
				}
			}

			// CoerceEx did that
			Py_DECREF( operand1 );
			Py_DECREF( operand2 );
		}
	}
#endif

	PyErr_Format(
		PyExc_TypeError,
		"unsupported operand type(s) for divmod(): '%s' and '%s'",
		type1->tp_name,
		type2->tp_name
	);

	return NULL;
}
