#include "lib_handler.h"
#include "pardiso_loader.h"

#include "glob_opts.h"
#include "constants.h"

#ifdef IS_WINDOWS
#define PARDISOLIBNAME "mkl_rt." SHAREDLIBEXT
#else
#define PARDISOLIBNAME "libmkl_rt." SHAREDLIBEXT
#endif

typedef void (*voidfun)(void);

voidfun lh_load_sym (soHandle_t h, const char *symName);


// Interfaces for Pardiso functions
typedef void (*pardiso_t)(void**, const c_int*, const c_int*, const c_int*,
                          const c_int*, const c_int*, const c_float*,
                          const c_int*, const c_int*, c_int*,
                          const c_int*, c_int*, const c_int*, c_float*,
                          c_float*, c_int*);
typedef int (*mkl_set_ifl_t)(int);
typedef int (*mkl_get_mt_t)(void);


// Handlers are static variables
static soHandle_t Pardiso_handle = OSQP_NULL;
static pardiso_t func_pardiso = OSQP_NULL;
static mkl_set_ifl_t func_mkl_set_interface_layer = OSQP_NULL;
static mkl_get_mt_t func_mkl_get_max_threads = OSQP_NULL;

// Wrappers for loaded Pardiso function handlers
void pardiso(void** pt, const c_int* maxfct, const c_int* mnum,
                  const c_int* mtype, const c_int* phase, const c_int* n,
                  const c_float* a, const c_int* ia, const c_int* ja,
                  c_int* perm, const c_int* nrhs, c_int* iparm,
                  const c_int* msglvl, c_float* b, c_float* x,
                  c_int* error) {
	if(func_pardiso){
            // Call function pardiso only if it has been initialized
	    func_pardiso(pt, maxfct, mnum, mtype, phase, n, a, ia, ja,
			 perm, nrhs, iparm, msglvl, b, x, error);
	}
	else
	{
#ifdef PRINTING
		c_eprint("Pardiso not loaded correctly");
#endif
	}
}

c_int mkl_set_interface_layer(c_int code) {
    return (c_int)func_mkl_set_interface_layer((int)code);
}

c_int mkl_get_max_threads(void) {
    return (c_int)func_mkl_get_max_threads();
}


c_int lh_load_pardiso(const char* libname) {
    // DEBUG
    // if (Pardiso_handle) return 0;

    // Load Pardiso library
    if (libname) {
        Pardiso_handle = lh_load_lib(libname);
    } else { /* try a default library name */
        Pardiso_handle = lh_load_lib(PARDISOLIBNAME);
    }
    if (!Pardiso_handle) return 1;

    // Load Pardiso functions
    func_pardiso = (pardiso_t)lh_load_sym(Pardiso_handle, "pardiso");
    if (!func_pardiso) return 1;

    func_mkl_set_interface_layer = (mkl_set_ifl_t)lh_load_sym(Pardiso_handle,
                                                    "MKL_Set_Interface_Layer");
    if (!func_mkl_set_interface_layer) return 1;

    func_mkl_get_max_threads = (mkl_get_mt_t)lh_load_sym(Pardiso_handle,
                                                    "MKL_Get_Max_Threads");
    if (!func_mkl_get_max_threads) return 1;

    return 0;
}

c_int lh_unload_pardiso(void) {

    if (Pardiso_handle == OSQP_NULL) return 0;

    return lh_unload_lib(Pardiso_handle);

    /* If multiple OSQP objects are laoded, the lines below cause a crash */
    // Pardiso_handle = OSQP_NULL;
    // func_pardiso = OSQP_NULL;
    // func_mkl_set_interface_layer = OSQP_NULL;
    // func_mkl_get_max_threads = OSQP_NULL;

}
