/* nl-import.c --- shared library interface for newLISP

    Copyright (C) 2016 Lutz Mueller

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/


#include "newlisp.h"
#include "protos.h"

#ifndef WINDOWS
#include <dlfcn.h>
#endif

#ifdef MAC_OSX
#include <sys/mman.h>
#endif

UINT cdeclFunction(UINT fAddress, UINT * args, int count);
extern int evalCatchFlag;

#ifdef FFI
CELL * ffiPreparation(CELL * pCell, CELL * params, int type);
CELL * ffiTypeToCell(ffi_type *type, void * src);
CELL * unpackFFI(ffi_type * ffi, char * data);
void ffi_trampoline(ffi_cif *cif, void *ret, void **args, void *symbol);
#endif


#if defined(WINDOWS) || defined(CYGWIN)
UINT stdcallFunction(UINT fAddress, UINT * args, int count);
#endif

#ifdef WINDOWS

CELL * p_importLib(CELL * params)
{
char * libName;
char * funcName;
char * options = NULL;
HINSTANCE hLibrary;
CELL * pCell;
SYMBOL * symbol;
FARPROC initProc;
int type = CELL_IMPORT_DLL;

params = getString(params, &libName);
params = getString(params, &funcName);
if(params != nilCell)
    {
    if(params->next == nilCell)
        params = getString(params, &options);
#ifdef FFI
    else
        type = CELL_IMPORT_FFI;
#endif
    }

if( (UINT)(hLibrary = LoadLibrary(libName)) < 32)
    return(errorProcExt2(ERR_IMPORT_LIB_NOT_FOUND, stuffString(libName)));

if(options != NULL && strcmp(options, "cdecl") ==  0)
	type = CELL_IMPORT_CDECL;

symbol = translateCreateSymbol(funcName, type, currentContext, TRUE);
if(isFFIsymbol(symbol->flags)) /* don't redefine return current def */
        return (copyCell((CELL *)symbol->contents));

if(isProtected(symbol->flags))
    return(errorProcExt2(ERR_SYMBOL_PROTECTED, stuffSymbol(symbol)));

pCell = getCell(type);

deleteList((CELL *)symbol->contents);
symbol->contents = (UINT)pCell;
if((pCell->contents = (UINT)GetProcAddress(hLibrary, (LPCSTR)funcName)) == 0)
    return(errorProcExt2(ERR_IMPORT_FUNC_NOT_FOUND, stuffString(funcName)));

/* put name of imported DLL into DLLs space for loadStartup() */
initProc = GetProcAddress(hLibrary, (LPCSTR)"dllName");
if(initProc != 0) (*initProc)(libName);

#ifdef FFI
symbol->flags |= SYMBOL_FFI | SYMBOL_PROTECTED;
if(pCell->type == CELL_IMPORT_FFI)
    {
    pCell->aux = (UINT)calloc(sizeof(FFIMPORT), 1);
    ((FFIMPORT *)pCell->aux)->name = symbol->name;
    return(copyCell(ffiPreparation(pCell, params, FFI_FUNCTION)));
    }
#endif

pCell->aux = (UINT)symbol->name;

return(copyCell(pCell));
}

#else  /* UNIX and compatible operating systems */

CELL * p_importLib(CELL * params)
{
char * libName;
char * funcName;
void * hLibrary;
CELL * pCell;
SYMBOL * symbol;
char * error;
#ifdef CYGWIN
char * options = NULL;
int type = CELL_IMPORT_DLL;
#else
int type = CELL_IMPORT_CDECL;
#endif


params = getString(params, &libName);
if(params != nilCell)
    params = getString(params, &funcName);
else funcName = NULL;

#ifdef CYGWIN
if(params != nilCell)
    { 
    if(params->next == nilCell)   
        {
        params = getString(params, &options);
        if(strcmp(options, "cdecl") ==  0)
            type = CELL_IMPORT_CDECL;
        }
#ifdef FFI
    else type = CELL_IMPORT_FFI;
#endif
    }
#else
if(params->next != nilCell)
    type = CELL_IMPORT_FFI;
#endif

hLibrary = 0;                

#ifdef TRU64
if((hLibrary = dlopen(libName, RTLD_LAZY)) == 0)
#else
if((hLibrary = dlopen(libName, RTLD_GLOBAL|RTLD_LAZY)) == 0)
#endif
    return(errorProcExt2(ERR_IMPORT_LIB_NOT_FOUND, stuffString((char *)dlerror())));

if(funcName == NULL)
    return(trueCell);

symbol = translateCreateSymbol(funcName, type, currentContext, TRUE);
if(isFFIsymbol(symbol->flags)) /* don't redefine */
        return (copyCell((CELL *)symbol->contents));

if(isProtected(symbol->flags))
    return(errorProcExt2(ERR_SYMBOL_PROTECTED, stuffSymbol(symbol)));
    
pCell = getCell(type);
deleteList((CELL *)symbol->contents);
symbol->contents = (UINT)pCell;

dlerror(); /* clear potential error */
pCell->contents = (UINT)dlsym(hLibrary, funcName);

if((error = (char *)dlerror()) != NULL)
    return(errorProcExt2(ERR_IMPORT_FUNC_NOT_FOUND, stuffString(error)));

#ifdef FFI
symbol->flags |= SYMBOL_FFI | SYMBOL_PROTECTED;
if(pCell->type == CELL_IMPORT_FFI)
    {
    pCell->aux = (UINT)calloc(sizeof(FFIMPORT), 1);
    ((FFIMPORT *)pCell->aux)->name = symbol->name;
    return(copyCell(ffiPreparation(pCell, params, FFI_FUNCTION)));
    }
#endif

pCell->aux = (UINT)symbol->name;
return(copyCell(pCell));
}
#endif


CELL * executeLibfunction(CELL * pCell, CELL * params)
{
CELL * arg;
UINT args[14];
int count;

#ifdef FFI
if(pCell->type == CELL_IMPORT_FFI)
    if(((FFIMPORT *)pCell->aux)->type != 0)
    return executeLibFFI(pCell, params);
#endif

count = 0;
while(params->type != CELL_NIL && count < 14)
    {
    arg = evaluateExpression(params);
    switch(arg->type)
        {
        case CELL_LONG:
        case CELL_STRING:
        case CELL_PRIMITIVE:
            args[count++] = arg->contents;
            break;
#ifndef NEWLISP64
        /* change 64-bit to 32-bit */
        case CELL_INT64: 
            args[count++] = *(INT64 *)&arg->aux;
            break;
#endif
        case CELL_FLOAT:
#ifndef NEWLISP64
            args[count++] = arg->aux;
#endif
            args[count++] = arg->contents;
            break;
        default:
            args[count++] = (UINT)arg;
            break;
        }
    params = (CELL *)params->next;
    }

#if defined(WINDOWS) || defined(CYGWIN)
if(pCell->type == CELL_IMPORT_DLL)
    return(stuffInteger(stdcallFunction(pCell->contents, args, count)));
else
#endif
return(stuffInteger(cdeclFunction(pCell->contents, args, count)));
}


UINT cdeclFunction(UINT fAddress, UINT * args, int count)
{
UINT (*function)();

function = (UINT (*)())fAddress;

switch(count)
    {
    case 0:
            return (*function)();

    case 1:
            return  (*function)(args[0]);

    case 2:
            return  (*function)(args[0], args[1]);

    case 3:
            /* printf("args[0] %llx, args[1] %llx, args[2] %llx, args[1]-args[2] %llx\n ",
                    args[0], args[1], args[2], args[1] - args[2]); */

            return  (*function)(args[0], args[1], args[2]);
    case 4:

            return  (*function)(args[0], args[1], args[2], args[3]);

    case 5:
            return  (*function)(args[0], args[1], args[2], args[3],
                     args[4]);
    case 6:
            return  (*function)(args[0], args[1], args[2], args[3],
                     args[4], args[5]);
    case 7:
            return  (*function)(args[0], args[1], args[2], args[3],
                     args[4], args[5], args[6]);
    case 8:
            return  (*function)(args[0], args[1], args[2], args[3],
                     args[4], args[5], args[6], args[7]);

    case 9:
            return  (*function)(args[0], args[1], args[2], args[3],
                     args[4], args[5], args[6], args[7], args[8]);

    case 10:
            return  (*function)(args[0], args[1], args[2], args[3],
                     args[4], args[5], args[6], args[7], args[8], args[9]);
    case 11:
            return  (*function)(args[0], args[1], args[2], args[3],
                args[4], args[5], args[6], args[7],
                args[8], args[9], args[10]);
    case 12:
            return  (*function)(args[0], args[1], args[2], args[3],
                args[4], args[5], args[6], args[7],
                args[8], args[9], args[10], args[11]);

    case 13:
            return  (*function)(args[0], args[1], args[2], args[3],
                args[4], args[5], args[6], args[7],
                args[8], args[9], args[10], args[11],
                args[12]);
    case 14:
            return  (*function)(args[0], args[1], args[2], args[3],
                args[4], args[5], args[6], args[7],
                args[8], args[9], args[10], args[11],
                args[12], args[13]);
    default:
        break;
    }

return(0);
}


#if defined(WINDOWS) || defined(CYGWIN)
UINT stdcallFunction(UINT fAddress, UINT * args, int count)
{
UINT _stdcall (*function)();

function = (UINT _stdcall (*)())fAddress;

switch(count)
    {
    case 0:
            return (*function)();

    case 1:
            return  (*function)(args[0]);

    case 2:
            return  (*function)(args[0], args[1]);

    case 3:
            return  (*function)(args[0], args[1], args[2]);

    case 4:
            return  (*function)(args[0], args[1], args[2], args[3]);

    case 5:
            return  (*function)(args[0], args[1], args[2], args[3],
                     args[4]);
    case 6:
            return  (*function)(args[0], args[1], args[2], args[3],
                     args[4], args[5]);
    case 7:
            return  (*function)(args[0], args[1], args[2], args[3],
                     args[4], args[5], args[6]);
    case 8:
            return  (*function)(args[0], args[1], args[2], args[3],
                     args[4], args[5], args[6], args[7]);

    case 9:
            return  (*function)(args[0], args[1], args[2], args[3],
                     args[4], args[5], args[6], args[7], args[8]);

    case 10:
            return  (*function)(args[0], args[1], args[2], args[3],
                     args[4], args[5], args[6], args[7], args[8], args[9]);
    case 11:
            return  (*function)(args[0], args[1], args[2], args[3],
                args[4], args[5], args[6], args[7],
                args[8], args[9], args[10]);
    case 12:
            return  (*function)(args[0], args[1], args[2], args[3],
                args[4], args[5], args[6], args[7],
                args[8], args[9], args[10], args[11]);

    case 13:
            return  (*function)(args[0], args[1], args[2], args[3],
                args[4], args[5], args[6], args[7],
                args[8], args[9], args[10], args[11],
                args[12]);
    case 14:
            return  (*function)(args[0], args[1], args[2], args[3],
                args[4], args[5], args[6], args[7],
                args[8], args[9], args[10], args[11],
                args[12], args[13]);
    default:
        break;
    }

return(0);
}
#endif


/* 16 callback functions for up to 8 parameters */

INT template(INT n, INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8);

INT callback0(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(0, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback1(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(1, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback2(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(2, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback3(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(3, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback4(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(4, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback5(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(5, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback6(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(6, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback7(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(7, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback8(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(8, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback9(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(9, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback10(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(10, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback11(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(11, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback12(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(12, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback13(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(13, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback14(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(14, p1, p2, p3, p4, p5, p6, p7, p8);}
INT callback15(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
    {return template(15, p1, p2, p3, p4, p5, p6, p7, p8);}

typedef INT (*lib_callback_t)(INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8);

typedef struct {
    SYMBOL * sym;
    lib_callback_t func;
    } LIBCALLBACK;

LIBCALLBACK callback[] = {
    { NULL, callback0 },
    { NULL, callback1 },
    { NULL, callback2 },
    { NULL, callback3 },
    { NULL, callback4 },
    { NULL, callback5 },
    { NULL, callback6 },
    { NULL, callback7 },
    { NULL, callback8 },
    { NULL, callback9 },
    { NULL, callback10 },
    { NULL, callback11 },
    { NULL, callback12 },
    { NULL, callback13 },
    { NULL, callback14 },
    { NULL, callback15 },
};


INT template(INT n, INT p1, INT p2, INT p3, INT p4, INT p5, INT p6, INT p7, INT p8) 
{
CELL * args;
CELL * cell;
INT result;
jmp_buf errorJumpSave;

memcpy(errorJumpSave, errorJump, sizeof(errorJump));
if(setjmp(errorJump))
    {
    reset();
    initStacks();
    result = -1;
    goto FINISH_CALLBACK;
    }

args = stuffIntegerList(8, p1, p2, p3, p4, p5, p6, p7, p8);
executeSymbol(callback[n].sym, (CELL *)args->contents, &cell);

#ifndef NEWLISP64
if(cell->type == CELL_INT64)
    result = *(INT64 *)&cell->aux;
else 
#endif
    result = (INT)cell->contents;

args->contents = (UINT)nilCell;

deleteList(args);
/* before 10.4.4 this was pushResult(cell) but caused resultStack overflow
   in 10.4.4 changed to deleteList(cell), but now return value on
   simple callback not available anymore, use callback with libffi instead
   available on -DFFI compiled versions. See also:
       qa-specific-tests/qa-simplecallback
*/
deleteList(cell); 

FINISH_CALLBACK:
memcpy(errorJump, errorJumpSave, sizeof(errorJump));
return(result);
}


CELL * p_callback(CELL * params)
{
CELL * cell;
SYMBOL * sPtr;
UINT n;


#ifdef FFI
SYMBOL * symbol;
CELL * ffiCell;
FFIMPORT *ffi;
char * cb_name;
int result, len;

#ifdef MAC_OSX
ffi_closure *closure;
#endif

#endif /* FFI */

cell = evaluateExpression(params);
if(cell->type == CELL_SYMBOL)
	sPtr = (SYMBOL *)cell->contents;
else
	goto CALLBACK_SIMPLE;

#ifdef FFI
len = strlen(sPtr->name);
cb_name = calloc(sizeof(char) * (len + 6), 1);
/*
strncpy(cb_name, "$ffi-", 6);
strncat(cb_name, sPtr->name, len);
*/
memcpy(cb_name, "$ffi-", 5);
memcpy(cb_name + 5, sPtr->name, len + 1);

symbol = translateCreateSymbol(cb_name, CELL_NIL, mainContext, TRUE);
if(isFFIsymbol(symbol->flags)) /* already defined */
    {
    ffiCell = (CELL *)symbol->contents;
    ffi = (FFIMPORT *) ffiCell->aux;
    return(stuffInteger((UINT) ffi->code));
    }

if(isProtected(symbol->flags))
    return(errorProcExt2(ERR_SYMBOL_PROTECTED, stuffSymbol(symbol)));

symbol->flags |= SYMBOL_FFI | SYMBOL_PROTECTED;

if(params->next == nilCell)
    return(errorProcExt(ERR_FFI_PREP_FAILED,nilCell));

ffiCell = getCell(CELL_IMPORT_FFI);
ffiCell->aux = (UINT)calloc(sizeof(FFIMPORT), 1);
((FFIMPORT *)ffiCell->aux)->name = cb_name;
ffiCell = ffiPreparation(ffiCell,params->next,FFI_CLOSURE);

if(ffiCell != nilCell)
    {
    ffi = (FFIMPORT *) ffiCell->aux;
    ffi->data = calloc(sizeof(ffi_closure_data), 1);
#ifndef MAC_OSX
    ffi->clos = ffi_closure_alloc(sizeof(ffi_closure),&ffi->code);
    if(!ffi->clos)
        {
        free(ffi->data);
        return(errorProcExt(ERR_FFI_PREP_FAILED,nilCell));
        }
    result = ffi_prep_closure_loc(ffi->clos, &ffi->cif, ffi_trampoline, ffi->data, ffi->code);
    if(result != FFI_OK)
        {
        free(ffi->data);
        return(errorProcExt(ERR_FFI_PREP_FAILED, stuffSymbol(sPtr)));
        }
#else /* MAC_OSX */
    if((closure = mmap(NULL, sizeof(ffi_closure), PROT_READ | PROT_WRITE,
             MAP_ANON | MAP_PRIVATE, -1, 0)) == (void*)-1)
        {
        free(ffi->data);
        return(errorProcExt(ERR_FFI_PREP_FAILED, stuffSymbol(sPtr)));
        }
    ffi->clos = closure;
    if((result = ffi_prep_closure(closure, &ffi->cif, ffi_trampoline, ffi->data)) != FFI_OK)
        {
        free(ffi->data);
        munmap(closure, sizeof(closure));
        return(errorProcExt(ERR_FFI_PREP_FAILED, stuffSymbol(sPtr)));
        }
    if(mprotect(closure, sizeof(closure), PROT_READ | PROT_EXEC) == -1)
        {
        free(ffi->data);
        munmap(closure, sizeof(closure));
        return(errorProcExt(ERR_FFI_PREP_FAILED, stuffSymbol(sPtr)));
        }
    /* ffi->code = ffi_trampoline; */
    ffi->code = closure;
#endif
    ffi->data->symbol = sPtr;
    ffi->data->code = ffi->code;

    deleteList((CELL *)symbol->contents);
    symbol->contents = (UINT)ffiCell;

    return(stuffInteger((UINT) ffi->code));
    }
else
    return(errorProcExt(ERR_FFI_PREP_FAILED, stuffString(sPtr->name)));
#endif /* FFI */

CALLBACK_SIMPLE:
getIntegerExt(cell, &n, FALSE);

if(n > 15) return(errorProc(ERR_NUMBER_OUT_OF_RANGE));

getSymbol(params->next, &sPtr);
callback[n].sym = sPtr;

return(stuffInteger((UINT)callback[n].func));
}

/* ========================= FFFI using ffilib ========================== */

/* because of the non-standard cell in FFI symbol->contents, it cannot be
   memory managed in an efficient way. This is why FFI functions and structs
   can only be defined once. A repeated definitions will return nil and leave
   the symbol's original definition untouched. 

   Thanks to Stefan Sonnenberg for doing most of the coding in this section.
*/


#ifdef FFI

typedef struct 
    {
    char * name;
    ffi_type * type;
    int size;
    } FFITYPE;

/* of the native C-types only types, which are the same 
   on LP64, LLP64 and ILP32, are supported
*/

/* custom ffi_type_charpointer see initFFI() */
ffi_type ffi_type_charpointer = {0, 0, 0, NULL};

FFITYPE ffi_types[] =
    {
    {"void",        &ffi_type_void},
    {"char",        &ffi_type_sint8},
    {"byte",        &ffi_type_uint8},
    {"unsigned short int",  &ffi_type_uint16},
    {"short int",   &ffi_type_sint16},
    {"unsigned int",&ffi_type_uint32},
    {"int",         &ffi_type_sint32},
#ifdef NEWLISP64
    {"long",        &ffi_type_sint64},
#else
    {"long",        &ffi_type_sint32},
#endif
    {"long long",   &ffi_type_sint64},
    {"float",       &ffi_type_float},
    {"double",      &ffi_type_double},
    {"char*",       &ffi_type_charpointer}, /* zero terminated string buffers with textual info */
    {"void*",       &ffi_type_pointer}, /* string or address with binary info, address return */
    {NULL,          NULL}
    };

void initFFI(void)
{
memcpy(&ffi_type_charpointer, &ffi_type_pointer, sizeof(ffi_type));
}

/* creates extended ffi usable struct information */
CELL * p_struct(CELL * params)
{
CELL * ffiCell;
SYMBOL * symbol;
FFIMPORT * ffi;
int i;

    params = getSymbol(params, &symbol);
    if(isFFIsymbol(symbol->flags)) /* don't redefine */
        return(stuffSymbol(symbol));

    if(isProtected(symbol->flags))
        return(errorProcExt2(ERR_SYMBOL_PROTECTED, stuffSymbol(symbol)));

    if(params == nilCell)
        return(errorProc(ERR_MISSING_ARGUMENT));

    symbol->flags |= SYMBOL_FFI | SYMBOL_PROTECTED;

    ffiCell = getCell(CELL_IMPORT_FFI);
    ffiCell->aux = (UINT)calloc(sizeof(FFIMPORT), 1);
    ffi = (FFIMPORT *) ffiCell->aux;
    ffi->name = symbol->name;
    ffiCell = ffiPreparation(ffiCell, params, FFI_STRUCT);
    ffi->cstruct = calloc(sizeof(ffi_type),1);
    
    ffi->cstruct->elements = calloc(sizeof(ffi_type *) * (ffi->cif.nargs + 1), 1);

    for(i = 0; i < ffi->cif.nargs; i++)
        ffi->cstruct->elements[i] = ffi->cif.arg_types[i];
	/* last elements[nargs] must be left 0, NULL */
	/* size and alignment are already 0, to let ffi know, it has to calculate */

    ffi->cstruct->type = FFI_TYPE_STRUCT; 

    if(ffi_prep_cif(&ffi->cif, FFI_DEFAULT_ABI, 0, ffi->cstruct, 0) != FFI_OK)
        return(errorProc(ERR_FFI_PREP_FAILED));

    deleteList((CELL *)symbol->contents);
    symbol->contents = (UINT)ffiCell;

    return(stuffSymbol(symbol));

}

CELL * packFFIstruct(CELL * cell, CELL * params)
{
char chrV;
unsigned char byteV;
short int shortV;
unsigned short int uint16V;  /* 16 bit */
unsigned int uint32V;         /* 32 bit */
int int32V;                 /* 32 bit */
unsigned long long  uint64V; /* 64 bit */
float floatV;
double doubleV;
ffi_type ** elements;
ffi_type *elem;
size_t offset = 0;
unsigned short pad;
char * data;
int listFlag = 0;
FFIMPORT * ffi;

ffi = (FFIMPORT *) cell->aux;
if(ffi->cstruct && ffi->cstruct->type != FFI_TYPE_STRUCT)
    return(errorProc(ERR_FFI_STRUCT_EXPECTED));

elements = (ffi_type **) ffi->cstruct->elements;

data = allocMemory(ffi->cstruct->size + 1);
memset(data, 0, ffi->cstruct->size + 1);
/* if no data are given the structure stays with 0s */
    
/* computing offsets and copy data */
/* data may come in as a list or flat */
elements = (ffi_type **) ffi->cstruct->elements;
while( (elem = *elements++) != NULL)
    {
    if(params->type == CELL_NIL) break;
    if(listFlag)
        cell = params;
    else
        cell = evaluateExpression(params);
    /* accept data in a list */
    if(isList(cell->type))
        {
        params = cell = (CELL *)cell->contents;
        listFlag = 1;
        }

    /* aligned ? */
    pad = offset % elem->alignment;
    /* no, add size of alignment boundary - padding bytes */
    if (pad != 0) offset += elem->alignment - pad;

#ifndef NEWLISP64
    if(cell->type == CELL_FLOAT || cell->type == CELL_INT64)
        uint64V = *(INT64 *)&cell->aux;
    else /* CELL_LONG and CELL_STRING */
        uint64V = cell->contents;
#else
    uint64V = cell->contents;
#endif

    if( elem == &ffi_type_sint8 )
        {
        chrV = (char)uint64V;
        memcpy(data + offset, &chrV, sizeof(char));
        }
    else if( elem == &ffi_type_uint8)
        {
        byteV = (char)uint64V;
        memcpy(data + offset, &byteV, sizeof(unsigned char));
        }
    else if( elem == &ffi_type_uint16)
        {
        uint16V = (unsigned short int)uint64V;
        memcpy(data + offset, &uint16V, sizeof(unsigned short int));
        }
    else if( elem == &ffi_type_sint16)
        {
        shortV = (short int)uint64V;
        memcpy(data + offset, &shortV, sizeof(short int));
        }
    else if( elem == &ffi_type_uint32)
        {
        uint32V = (unsigned int)uint64V;
        memcpy(data + offset, &uint32V, sizeof(unsigned int));
        }
    else if(elem == &ffi_type_sint32)
        {
        int32V = (int)uint64V;
        memcpy(data + offset, &int32V, sizeof(int));
        }
    else if(elem == &ffi_type_sint64)
        memcpy(data + offset, &uint64V, sizeof(long long));
    else if(elem == &ffi_type_float)
        {
        floatV = (float) *(double *)&cell->aux;
        memcpy(data + offset, &floatV, sizeof(float));
        }
    else if(elem == &ffi_type_double)
        {
        doubleV = getDirectFloat(cell);
        memcpy(data+offset, &doubleV, sizeof(double));
        }
    else if(elem == &ffi_type_pointer || elem == &ffi_type_charpointer)
        memcpy(data + offset, (void *)&cell->contents, sizeof(void *));
    else /* just copy what's there, must be struct */
        memcpy(data + offset, (void *)cell->contents, elem->size);

    /* grow offset by size of last element */
    offset += elem->size;
    /* go to next param and repeat */
    params = params->next;
    }

cell = makeStringCell(data, ffi->cstruct->size);

return(cell);
}

CELL * unpackFFIstruct(CELL * cell, char * data)
{
FFIMPORT * ffi;

ffi = (FFIMPORT *) cell->aux;
if(ffi->cstruct && ffi->cstruct->type != FFI_TYPE_STRUCT)
    return(errorProc(ERR_FFI_STRUCT_EXPECTED));

/* This is redundant? works with or without it on OSX, Windows, Linux. 
if(ffi->cstruct->size != 0)
    if(ffi_prep_cif(&ffi->cif, FFI_DEFAULT_ABI, 0, ffi->cstruct,0) != FFI_OK)
        return(errorProc(ERR_FFI_PREP_FAILED));
*/
return(unpackFFI(ffi->cstruct, data));
}


CELL * unpackFFI(ffi_type * ffiPtr, char * data)
{
ffi_type * elem;
ffi_type ** elements;
CELL * cell;
size_t offset = 0;
unsigned short pad;

cell = getCell(CELL_EXPRESSION);
elements = ffiPtr->elements;
while( (elem = *elements++) != NULL )
    {
    pad = offset % elem->alignment;
    if (pad != 0) offset += elem->alignment - pad;
    addList(cell, (CELL *) ffiTypeToCell(elem, data + offset));
    offset += elem->size;
    }

return(cell);
}
 

ffi_type * getFFIType(char * type)
{
int i = 0;
char * name;
SYMBOL * sPtr;
CELL * sType;
FFIMPORT * tmp;

while((name = ffi_types[i].name))
    {
    if(strcmp(name, type) == 0)
        return(ffi_types[i].type);
    ++i;
    }

/* ok - no primitive type found: try to lookup struct definition */
sPtr = lookupSymbol(type,currentContext);
if(sPtr)
    {
    sType = (CELL *)sPtr->contents;
    tmp = (FFIMPORT *)sType->aux;
    return( (ffi_type *)tmp->cstruct );
    }

errorProcExt2(ERR_FFI_INVALID_TYPE, stuffString(type) );

return(NULL);
}
    
#define MAX_TYPE_ARGS 32

CELL * ffiPreparation(CELL * pCell, CELL * params, int type)
    {
    CELL * arg;
    FFIMPORT * ffi;
    ffi_type **atypes;
    unsigned int nargs;
    ffi_type *rtype;

    if(pCell->type != CELL_IMPORT_FFI)
        errorProcExt(ERR_INVALID_PARAMETER, pCell);

    ffi = (FFIMPORT *)pCell->aux;
    ffi->func = FFI_FN(pCell->contents);
    ffi->type = type;

    atypes = calloc(sizeof(ffi_type) * MAX_TYPE_ARGS, 1);

    rtype = &ffi_type_void;

    /* get return type if not a structure */
    if(type != FFI_STRUCT)
        {
        arg = evaluateExpression(params);
        if(arg->type == CELL_STRING)
            rtype = getFFIType((char *) arg->contents);
        else
            errorProcExt(ERR_STRING_EXPECTED, arg);
        params = params->next;
        }

    nargs=0;
    while((arg = evaluateExpression(params)) != nilCell)
        {
        if(arg->type == CELL_STRING)
            {
            /* skip void arguments */
            if(strcmp((char *)arg->contents,"void") != 0)
                {
                atypes[nargs] = getFFIType((char *) arg->contents);
                nargs++;
                }
            }
        else
            errorProcExt(ERR_INVALID_PARAMETER,pCell);
        params = params->next;

        if(nargs == MAX_TYPE_ARGS)
            break; 
        } 

    ffi->cif.nargs = nargs;
    ffi->cif.arg_types = atypes;
    if(ffi_prep_cif(&ffi->cif, FFI_DEFAULT_ABI, nargs, rtype, atypes) != FFI_OK)
        errorProcExt(ERR_FFI_PREP_FAILED, pCell);

    return(pCell);
}



CELL * executeLibFFI(CELL * pCell, CELL * params)
{
    FFIMPORT * ffi;
    int c = 0;
    ffi_type * ffiType;
    INT64 value64;
    UINT value;
    void **avalues;
    CELL * cell;
    void * result;
    double valueDouble;

    ffi = (FFIMPORT *)pCell->aux;
    if(ffi->type == FFI_STRUCT)
        return(pCell);

    avalues = alloca(sizeof(void *) * 16);

    while(params != nilCell)
        {
        if(c >= ffi->cif.nargs) /* too much args */
            errorProc(ERR_NUM_ARGS);

        ffiType = ffi->cif.arg_types[c];
        if(ffiType !=  &ffi_type_void)
            avalues[c] = alloca(ffiType->size);
        /* printf("c: %d size:%ld\n", c, ffiType->size); */

        if(ffiType->type == FFI_TYPE_STRUCT)
            {
            cell = evaluateExpression(params);
            if(cell->type == CELL_STRING)
                memcpy(avalues[c],(void*) cell->contents,ffiType->size);
            else
                {
                getIntegerExt(cell, (UINT *) &value, FALSE);
                memcpy(avalues[c], (void **) value, ffiType->size);
                }
            params = params->next;
            }
        else if(ffiType ==  &ffi_type_pointer) /*  "void*" */
            {
            cell = evaluateExpression(params);
            if(cell->type == CELL_STRING)
                *(UINT *)avalues[c] = cell->contents;
            else
                getIntegerExt(cell, avalues[c], FALSE);
            params = params->next;
            }
        else if(ffiType ==  &ffi_type_charpointer) /*  "char*" */
            {
            params = getString(params, avalues[c]);
            }
        else if(ffiType == &ffi_type_float)  /* float 64-bit */
            {
            params = getFloat(params, &valueDouble);
            *(float *)avalues[c] = valueDouble;
            }
        else if(ffiType == &ffi_type_double)  /* double 64-bit */
            params = getFloat(params, avalues[c]);
        else if(ffiType == &ffi_type_sint64 
               || ffiType == &ffi_type_uint64) /* int 64-bit unsiged 64-bit in newLISP */
            {
            params = getInteger64Ext(params, &value64, TRUE);
            avalues[c] = alloca(sizeof(INT64));
            *(INT64 *)avalues[c] = value64;
            }
        else
            { 
            params = getInteger(params, &value);
            if(ffiType == &ffi_type_sint8) /* char 8-bit */
                *(char *)avalues[c] = value;
            else if(ffiType == &ffi_type_uint8) /* unsigned char 8-bit */
                *(unsigned char *)avalues[c] = value;
            else if(ffiType == &ffi_type_sint16) /* short int 16-bit */
                *(short int *)avalues[c] = value;
            else if(ffiType == &ffi_type_uint16) /* unsigned short int 16-bit */
                *(unsigned short int *)avalues[c] = value;
            else if(ffiType == &ffi_type_sint32) /* int 32-bit */
                *(int *)avalues[c] = value;
            else if(ffiType == &ffi_type_uint32) /* unsigned int 32-bit */
                *(unsigned int *)avalues[c] = value;
            }
        c++;
        } 
    if(c < ffi->cif.nargs) /* not enough args */
        errorProc(ERR_NUM_ARGS); 

    result = (ffi_type *) alloca(sizeof(ffi->cif.rtype->size));

    ffi_call(&ffi->cif, FFI_FN(pCell->contents), result, avalues);

    return ffiTypeToCell(ffi->cif.rtype, result);
}


CELL * ffiTypeToCell(ffi_type *type, void * result)
    {
    double valueDouble;
    char * cPtr;

    /* returning a structure */
    if(type->type == FFI_TYPE_STRUCT) 
        return(unpackFFI(type, result));
/*
        return stuffStringN(result, type->size);
*/

    /* for C displayable strings use return type "char*"
       for destructuring pointers to binary info
       use "void*" as return type */
    else if(type == &ffi_type_charpointer)
        {
        cPtr = *(char **)result; 
        if(cPtr == NULL)
                errorProc(ERR_CANNOT_CONVERT_NULL);
        return stuffString(cPtr);
        }
    else if(type == &ffi_type_pointer)
        return stuffInteger(*((UINT  *)result));
    else if(type == &ffi_type_double)
        return stuffFloat(*(double *)result);
    else if(type == &ffi_type_float)
        {
        valueDouble = *(float *)result;
        return stuffFloat(valueDouble);
        }
    else if(type == &ffi_type_sint8)
        return stuffInteger(*((char *)result));
    else if(type == &ffi_type_uint8)
        return stuffInteger(*((unsigned char *)result));
    else if(type == &ffi_type_sint16)
        return stuffInteger(*((short int *)result));
    else if(type == &ffi_type_uint16)
        return stuffInteger(*((unsigned short int *)result));
    else if(type == &ffi_type_sint32)
        return stuffInteger(*((int *)result));
    else if(type == &ffi_type_uint32)
        return stuffInteger64(*((unsigned int *)result));
    else if(type == &ffi_type_sint64 || type == &ffi_type_uint64)
        return stuffInteger64(*((INT64 *)result));

    return nilCell;
    }

/* This function gets called by external C functions/libraries */

void ffi_trampoline(ffi_cif *cif, void *ret, void **args, void *data)
{

    SYMBOL *symbol;    
    CELL *arg = NULL;
    CELL *next;
    CELL *result;
    int c;
    INT64 value64;
    UINT value;

    if(!cif)
        return;

    if(ret)
        memset(ret, 0, cif->rtype->size);

    symbol = (SYMBOL *) ((ffi_closure_data*) data)->symbol;    
    if(!symbol)
        return;
    
    if(cif->nargs)
		{
        arg = next = ffiTypeToCell(cif->arg_types[0], args[0]);
		for(c = 1;c < cif->nargs; c++)
			next = next->next =  ffiTypeToCell(cif->arg_types[c], args[c]);
		}

    executeSymbol(symbol,(CELL *) arg, &result);
    
    if((cif->rtype ==  &ffi_type_pointer) || (cif->rtype->type == FFI_TYPE_STRUCT) ) /* string or void pointer, or struct */
        *(UINT *) ret = (UINT) result->contents;
    else if(cif->rtype == &ffi_type_float)  /* float 64-bit */
        *(float *)ret = (float) getDirectFloat(result);
    else if(cif->rtype == &ffi_type_double)  /* double 64-bit */
        *(double *)ret = getDirectFloat(result);
    /* int 64-bit in newLISP */
    else if(cif->rtype == &ffi_type_sint64 || cif->rtype == &ffi_type_uint64) 
        {
        getInteger64Ext(result, &value64, TRUE);
        *(INT64 *)ret = value64;
        }
    else if(cif->rtype == &ffi_type_void)
        goto TRAMPOLINE_FINISH;
    else 
        {
        getIntegerExt(result, &value, FALSE);
        if(cif->rtype == &ffi_type_sint8) /* char 8-bit */
            *(char *)ret = (char) value;
        else if(cif->rtype == &ffi_type_uint8) /* unsigned char 8-bit */
            *(unsigned char *)ret = (unsigned char) value;
        else if(cif->rtype == &ffi_type_sint16) /* short int 16-bit */
            *(short int *)ret = (short int) value;
        else if(cif->rtype == &ffi_type_uint16) /* unsigned short int 16-bit */
            *(unsigned short int *)ret = (unsigned short int) value;
        else if(cif->rtype == &ffi_type_sint32) /* int 32-bit */
            *(int *)ret = (int) value;
        else if(cif->rtype == &ffi_type_uint32) /* unsigned int 32-bit */
            *(unsigned int *)ret = (unsigned int) value;
        else
            *(double *)ret = sqrt(-1.0); /* return NaN; never happens */
        }

    TRAMPOLINE_FINISH:
    deleteList(result); 
    return;
    }
#endif /* FFI */

/* end of file */
