/* This example demonstrates the following features in a native module:
    - using floats
    - defining additional code in Python (see test.py)
    - have extra C code in a separate file (see prod.c)
*/

// Include the header file to get access to the MicroPython API
#include "py/dynruntime.h"

// Include the header for auxiliary C code for this module
#include "prod.h"

// Automatically detect if this module should include double-precision code.
// If double precision is supported by the target architecture then it can
// be used in native module regardless of what float setting the target
// MicroPython runtime uses (being none, float or double).
#if defined(__i386__) || defined(__x86_64__) || (defined(__ARM_FP) && (__ARM_FP & 8))
#define USE_DOUBLE 1
#else
#define USE_DOUBLE 0
#endif

// A function that uses the default float type configured for the current target
// This default can be overridden by specifying MICROPY_FLOAT_IMPL at the make level
STATIC mp_obj_t add(mp_obj_t x, mp_obj_t y) {
    return mp_obj_new_float(mp_obj_get_float(x) + mp_obj_get_float(y));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(add_obj, add);

// A function that explicitly uses single precision floats
STATIC mp_obj_t add_f(mp_obj_t x, mp_obj_t y) {
    return mp_obj_new_float_from_f(mp_obj_get_float_to_f(x) + mp_obj_get_float_to_f(y));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(add_f_obj, add_f);

#if USE_DOUBLE
// A function that explicitly uses double precision floats
STATIC mp_obj_t add_d(mp_obj_t x, mp_obj_t y) {
    return mp_obj_new_float_from_d(mp_obj_get_float_to_d(x) + mp_obj_get_float_to_d(y));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(add_d_obj, add_d);
#endif

// A function that computes the product of floats in an array.
// This function uses the most general C argument interface, which is more difficult
// to use but has access to the globals dict of the module via self->globals.
STATIC mp_obj_t productf(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {
    // Check number of arguments is valid
    mp_arg_check_num(n_args, n_kw, 1, 1, false);

    // Extract buffer pointer and verify typecode
    mp_buffer_info_t bufinfo;
    mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_RW);
    if (bufinfo.typecode != 'f') {
        mp_raise_ValueError("expecting float array");
    }

    // Compute product, store result back in first element of array
    float *ptr = bufinfo.buf;
    float prod = prod_array(bufinfo.len / sizeof(*ptr), ptr);
    ptr[0] = prod;

    return mp_const_none;
}

// This is the entry point and is called when the module is imported
mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {
    // This must be first, it sets up the globals dict and other things
    MP_DYNRUNTIME_INIT_ENTRY

    // Make the functions available in the module's namespace
    mp_store_global(MP_QSTR_add, MP_OBJ_FROM_PTR(&add_obj));
    mp_store_global(MP_QSTR_add_f, MP_OBJ_FROM_PTR(&add_f_obj));
    #if USE_DOUBLE
    mp_store_global(MP_QSTR_add_d, MP_OBJ_FROM_PTR(&add_d_obj));
    #endif

    // The productf function uses the most general C argument interface
    mp_store_global(MP_QSTR_productf, MP_DYNRUNTIME_MAKE_FUNCTION(productf));

    // This must be last, it restores the globals dict
    MP_DYNRUNTIME_INIT_EXIT
}
