/************************************************************************************************
 *  Copyright Dewetron Ges.m.b.H 2014                                                           *
 *                                                                                              *
 *   DLL loader helper for the dmd reader api dynamic library.                                  *
 ************************************************************************************************/

/**
 * @file dmd_reader_api_load.h
 * @author Dewetron Ges.m.b.H.
 * @date 8 Oct 2014
 * @brief DMD dynamic library loader helper.
 *
 * Here the dynamic load of the dmd library is done.
 * Example usage:
 *   DMDReader_LoadLibrary(DMD_READER_DLL_NAME) // loads the dmd dynamic library
 *   DMDReader_CloseLibrary() // unloads the dmd dynamic library
 */

#ifndef _DEWETRON_DMD_READER_API_LOAD_
#define _DEWETRON_DMD_READER_API_LOAD_

#ifdef __cplusplus
extern "C" {
#endif

#ifdef __cplusplus
    #define EXTERN    extern
#else
    #define EXTERN
#endif

//
// Define BUILD_X64 for 64bit builds
// Define BUILD_X86 for 32bit builds (default)
//
#ifndef BUILD_X64
  #ifndef BUILD_X86
    // if nothing is defined default to 32bit
    #define BUILD_X86
  #endif
#endif

#include "dmd_reader_api_interface.h"
#include <stdio.h>

#ifdef BUILD_X64
  // 64bit API
  #ifdef WIN32
    #include <windows.h>
    static const char DMD_READER_DLL_NAME[] = "dmd_reader_api_x64.dll";
  #elif defined(__APPLE__)
    #include <dlfcn.h>
    static const char DMD_READER_DLL_NAME[]   = "libdmd_reader_api_x64.dylib";
  #elif defined(UNIX)
    #include <dlfcn.h>
    static const char DMD_READER_DLL_NAME[]   = "libdmd_reader_api_x64.so";
  #endif //UNIX
#endif
#ifdef BUILD_X86
  // 32bit API
  #ifdef WIN32
    #include <windows.h>
    static const char DMD_READER_DLL_NAME[] = "dmd_reader_api.dll";
  #elif defined(__APPLE__)
    #include <dlfcn.h>
    static const char DMD_READER_DLL_NAME[]   = "libdmd_reader_api.dylib";
  #elif defined(UNIX)
    #include <dlfcn.h>
    static const char DMD_READER_DLL_NAME[]   = "libdmd_reader_api.so";
  #endif //UNIX
#endif

#ifdef _INITDMD
#define DMDAPI
#else
#define DMDAPI extern
#endif	//	_INITDMD

//*************************************************************************************
// MAIN LOAD / UNLOAD INTERFACE
//*************************************************************************************

//TODO does it make sense to let the user specify the library name here?
//TODO trion api puts the definition of these functions into the header to avoid linking a C file. good idea?
//TODO for the future we must define how load library reacts if an older version of the dll is found. failing could be acceptable.
DMDAPI DMDReader_ErrorCode DMDReader_LoadLibrary(const char* libraryName); // Initialize
DMDAPI DMDReader_ErrorCode DMDReader_CloseLibrary();

//*************************************************************************************
// MAIN LOAD / UNLOAD INTERFACE TYPES
//*************************************************************************************
DMDAPI FP_DMDReader_Dispose                                 DMDReader_Dispose;
DMDAPI FP_DMDReader_OpenFile                                DMDReader_OpenFile;
DMDAPI FP_DMDReader_CloseFile                               DMDReader_CloseFile;
DMDAPI FP_DMDReader_GetNumHeaderFields                      DMDReader_GetNumHeaderFields;
DMDAPI FP_DMDReader_GetHeaderFields                         DMDReader_GetHeaderFields;
DMDAPI FP_DMDReader_GetHeaderField                          DMDReader_GetHeaderField;
DMDAPI FP_DMDReader_GetMeasurementStartTime                 DMDReader_GetMeasurementStartTime;
DMDAPI FP_DMDReader_GetNumMarkers                           DMDReader_GetNumMarkers;
DMDAPI FP_DMDReader_GetMarkers                              DMDReader_GetMarkers;
DMDAPI FP_DMDReader_GetNumChannels                          DMDReader_GetNumChannels;
DMDAPI FP_DMDReader_GetChannels                             DMDReader_GetChannels;
DMDAPI FP_DMDReader_GetNumDataSweeps                        DMDReader_GetNumDataSweeps;
DMDAPI FP_DMDReader_GetDataSweeps                           DMDReader_GetDataSweeps;
DMDAPI FP_DMDReader_GetNumReducedSweeps                     DMDReader_GetNumReducedSweeps;
DMDAPI FP_DMDReader_GetReducedSweeps                        DMDReader_GetReducedSweeps;
DMDAPI FP_DMDReader_GetSampleType                           DMDReader_GetSampleType;
DMDAPI FP_DMDReader_GetVectorSampleType                     DMDReader_GetVectorSampleType;
DMDAPI FP_DMDReader_GetChannelInformation                   DMDReader_GetChannelInformation;
DMDAPI FP_DMDReader_GetSamplesWithTS_ScaledValue_Seconds    DMDReader_GetSamplesWithTS_ScaledValue_Seconds;
DMDAPI FP_DMDReader_GetSamplesAndTS_ScaledValue_Seconds     DMDReader_GetSamplesAndTS_ScaledValue_Seconds;
DMDAPI FP_DMDReader_GetSamplesWithTS_ReducedValue_Seconds   DMDReader_GetSamplesWithTS_ReducedValue_Seconds;
DMDAPI FP_DMDReader_GetSamplesAndTS_ReducedValue_Seconds    DMDReader_GetSamplesAndTS_ReducedValue_Seconds;
DMDAPI FP_DMDReader_GetSamplesWithTS_DigitalValue_Seconds   DMDReader_GetSamplesWithTS_DigitalValue_Seconds;
DMDAPI FP_DMDReader_GetSamplesAndTS_DigitalValue_Seconds    DMDReader_GetSamplesAndTS_DigitalValue_Seconds;
DMDAPI FP_DMDReader_GetSamplesAndTS_ScalarVector_Seconds    DMDReader_GetSamplesAndTS_ScalarVector_Seconds;
DMDAPI FP_DMDReader_GetSamplesAndTS_ComplexVector_Seconds   DMDReader_GetSamplesAndTS_ComplexVector_Seconds;
DMDAPI FP_DMDReader_GetConfigurationXML                     DMDReader_GetConfigurationXML;
DMDAPI FP_DMDReader_GetGlobalConfigItem                     DMDReader_GetGlobalConfigItem;


#ifdef WIN32
static HINSTANCE   hLib = 0;
#endif //WIN32

#ifdef UNIX
static void*       hLib = 0;
#endif //UNIX


//*************************************************************************************
// MACROS
//*************************************************************************************

//TODO if we really use a corresponding .c file we can move all this macro stuff (below and above!) there
#ifdef WIN32
  #ifdef LOADDLLFUNCTION
    #error "LOADDLLFUNCTION already defined"
  #else
    #define LOADDLLFUNCTION(lib, type, name) name=(type)loadFunction(#name, 0);

    #define LOAD_LIBRARY(dll_name) LoadLibraryA(dll_name)
    #define CLOSE_LIBRARY(so_name) FreeLibrary(hLib);
  #endif //LOADDLLFUNCTION

  //local helper function
  static void* loadFunction( const char* pcName, BOOLEAN* bTotalOK );
#endif //WIN32


#ifdef UNIX
  #ifdef LOADDLLFUNCTION
    #error "LOADDLLFUNCTION already defined"
  #else
    #define LOADDLLFUNCTION(lib, type, name)  name = (type) dlsym(lib, #name); \
    if ((error = dlerror()) != 0) {                                  \
        DMDReader_CloseLibrary();                                          \
    }

    #define LOAD_LIBRARY(so_name) dlopen(so_name, RTLD_LAZY);   \
    if (!hLib) {                                                \
        error = dlerror();                                      \
    }

    #define CLOSE_LIBRARY(so_name) dlclose(so_name)
  #endif //LOADDLLFUNCTION
#endif //UNIX

#ifdef _INITDMD

#ifdef WIN32
static
void*
loadFunction(
             const char*        pcName,
             BOOLEAN*           bTotalOK
             )
{
    void*       pf = NULL;
    BOOLEAN     bDummy = FALSE;

    if ( NULL == bTotalOK )
    {
        bTotalOK = &bDummy;
    }
    if ( !hLib )
    {
        *bTotalOK = FALSE;
        return pf;
    }
    if ( NULL == pcName )
    {
        *bTotalOK = FALSE;
        return pf;
    }

    pf = GetProcAddress(hLib, pcName);

    if ( NULL == pf )
    {
        *bTotalOK = FALSE;
    }
    return pf;
}
#endif //WIN32

#ifdef UNLOADDLLFUNCTION
  #error "UNLOADDLLFUNCTION already defined"
#else
  #define UNLOADDLLFUNCTION(f)       f = 0;
#endif

#define DMDReader_CHECK_NULL(f) if (!f) { goto ERROR_HANDLER; }

DMDReader_ErrorCode DMDReader_LoadLibrary(const char* libraryName)
{
    char* error = 0;
    DMDReader_ErrorCode error_code;
    FP_DMDReader_GetVersion DMDReader_GetVersion = NULL;
    FP_DMDReader_Initialize DMDReader_Initialize = NULL;

    hLib = LOAD_LIBRARY(libraryName);
    DMDReader_CHECK_NULL(hLib);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetVersion, DMDReader_GetVersion);
    DMDReader_CHECK_NULL(DMDReader_GetVersion);

    // TODO: compiler struct aligment awareness
    //       struct size verification + reference struct (forward offsets of reference struct -> offsetof(struct referenceStruct, a) + last member offset)

    LOADDLLFUNCTION(hLib, FP_DMDReader_Initialize, DMDReader_Initialize);
    DMDReader_CHECK_NULL(DMDReader_Initialize);

    error_code = DMDReader_Initialize(DMDREADER_INTERFACE_VERSION_MAJOR, DMDREADER_INTERFACE_VERSION_MINOR);
    if (error_code != DMDReader_NoError)
    {
        UNLOADDLLFUNCTION(DMDReader_GetVersion);
        UNLOADDLLFUNCTION(DMDReader_Initialize);
        return error_code;
    }

    // TODO: check compatibility result: compare interface major/minor version
    //       -> interface version [i] -> interface version dll [internal]: true/false
    //       use result to map the right interface version (backwards compatibility)

    LOADDLLFUNCTION(hLib, FP_DMDReader_Dispose, DMDReader_Dispose);
    DMDReader_CHECK_NULL(DMDReader_Dispose);

    LOADDLLFUNCTION(hLib, FP_DMDReader_OpenFile, DMDReader_OpenFile);
    DMDReader_CHECK_NULL(DMDReader_OpenFile);

    LOADDLLFUNCTION(hLib, FP_DMDReader_CloseFile, DMDReader_CloseFile);
    DMDReader_CHECK_NULL(DMDReader_CloseFile);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetNumHeaderFields, DMDReader_GetNumHeaderFields);
    DMDReader_CHECK_NULL(DMDReader_GetNumHeaderFields);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetHeaderFields, DMDReader_GetHeaderFields);
    DMDReader_CHECK_NULL(DMDReader_GetHeaderFields);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetHeaderField, DMDReader_GetHeaderField);
    DMDReader_CHECK_NULL(DMDReader_GetHeaderField);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetMeasurementStartTime, DMDReader_GetMeasurementStartTime);
    DMDReader_CHECK_NULL(DMDReader_GetMeasurementStartTime);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetNumMarkers, DMDReader_GetNumMarkers);
    DMDReader_CHECK_NULL(DMDReader_GetNumMarkers);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetMarkers, DMDReader_GetMarkers);
    DMDReader_CHECK_NULL(DMDReader_GetMarkers);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetNumChannels, DMDReader_GetNumChannels);
    DMDReader_CHECK_NULL(DMDReader_GetNumChannels);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetChannels, DMDReader_GetChannels);
    DMDReader_CHECK_NULL(DMDReader_GetChannels);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetNumDataSweeps, DMDReader_GetNumDataSweeps);
    DMDReader_CHECK_NULL(DMDReader_GetNumDataSweeps);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetDataSweeps, DMDReader_GetDataSweeps);
    DMDReader_CHECK_NULL(DMDReader_GetDataSweeps);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetNumReducedSweeps, DMDReader_GetNumReducedSweeps);
    DMDReader_CHECK_NULL(DMDReader_GetNumReducedSweeps);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetReducedSweeps, DMDReader_GetReducedSweeps);
    DMDReader_CHECK_NULL(DMDReader_GetReducedSweeps);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetSampleType, DMDReader_GetSampleType);
    DMDReader_CHECK_NULL(DMDReader_GetSampleType);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetVectorSampleType, DMDReader_GetVectorSampleType);
    DMDReader_CHECK_NULL(DMDReader_GetVectorSampleType);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetChannelInformation, DMDReader_GetChannelInformation);
    DMDReader_CHECK_NULL(DMDReader_GetChannelInformation);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetSamplesWithTS_ScaledValue_Seconds, DMDReader_GetSamplesWithTS_ScaledValue_Seconds);
    DMDReader_CHECK_NULL(DMDReader_GetSamplesWithTS_ScaledValue_Seconds);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetSamplesAndTS_ScaledValue_Seconds, DMDReader_GetSamplesAndTS_ScaledValue_Seconds);
    DMDReader_CHECK_NULL(DMDReader_GetSamplesAndTS_ScaledValue_Seconds);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetSamplesWithTS_ReducedValue_Seconds, DMDReader_GetSamplesWithTS_ReducedValue_Seconds);
    DMDReader_CHECK_NULL(DMDReader_GetSamplesWithTS_ReducedValue_Seconds);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetSamplesAndTS_ReducedValue_Seconds, DMDReader_GetSamplesAndTS_ReducedValue_Seconds);
    DMDReader_CHECK_NULL(DMDReader_GetSamplesAndTS_ReducedValue_Seconds);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetSamplesWithTS_DigitalValue_Seconds, DMDReader_GetSamplesWithTS_DigitalValue_Seconds);
    DMDReader_CHECK_NULL(DMDReader_GetSamplesWithTS_DigitalValue_Seconds);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetSamplesAndTS_DigitalValue_Seconds, DMDReader_GetSamplesAndTS_DigitalValue_Seconds);
    DMDReader_CHECK_NULL(DMDReader_GetSamplesAndTS_DigitalValue_Seconds);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetSamplesAndTS_ScalarVector_Seconds, DMDReader_GetSamplesAndTS_ScalarVector_Seconds);
    DMDReader_CHECK_NULL(DMDReader_GetSamplesAndTS_ScalarVector_Seconds);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetSamplesAndTS_ComplexVector_Seconds, DMDReader_GetSamplesAndTS_ComplexVector_Seconds);
    DMDReader_CHECK_NULL(DMDReader_GetSamplesAndTS_ComplexVector_Seconds);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetConfigurationXML, DMDReader_GetConfigurationXML);

    LOADDLLFUNCTION(hLib, FP_DMDReader_GetGlobalConfigItem, DMDReader_GetGlobalConfigItem);

    UNLOADDLLFUNCTION(DMDReader_GetVersion);
    UNLOADDLLFUNCTION(DMDReader_Initialize);
    return DMDReader_NoError;

ERROR_HANDLER:
    UNLOADDLLFUNCTION(DMDReader_GetVersion);
    UNLOADDLLFUNCTION(DMDReader_Initialize);
    return DMDReader_InternalError;
}

DMDReader_ErrorCode DMDReader_CloseLibrary()
{
    DMDReader_ErrorCode ret;
    if (!hLib)
        return DMDReader_NoError;

    ret = DMDReader_NoError;

    if (DMDReader_Dispose)
    {
        DMDReader_Dispose();
    }
    else
    {
        ret = DMDReader_InternalError;
    }

    CLOSE_LIBRARY(hLib);

    UNLOADDLLFUNCTION(DMDReader_OpenFile);
    UNLOADDLLFUNCTION(DMDReader_CloseFile);
    UNLOADDLLFUNCTION(DMDReader_GetMeasurementStartTime);
    UNLOADDLLFUNCTION(DMDReader_GetNumMarkers);
    UNLOADDLLFUNCTION(DMDReader_GetMarkers);
    UNLOADDLLFUNCTION(DMDReader_GetNumChannels);
    UNLOADDLLFUNCTION(DMDReader_GetChannels);
    UNLOADDLLFUNCTION(DMDReader_GetNumDataSweeps);
    UNLOADDLLFUNCTION(DMDReader_GetDataSweeps);
    UNLOADDLLFUNCTION(DMDReader_GetSampleType);
    UNLOADDLLFUNCTION(DMDReader_GetChannelInformation);
    UNLOADDLLFUNCTION(DMDReader_GetSamplesWithTS_ScaledValue_Seconds);
    UNLOADDLLFUNCTION(DMDReader_GetSamplesAndTS_ScaledValue_Seconds);
    UNLOADDLLFUNCTION(DMDReader_GetSamplesWithTS_ReducedValue_Seconds);
    UNLOADDLLFUNCTION(DMDReader_GetSamplesAndTS_ReducedValue_Seconds);
    UNLOADDLLFUNCTION(DMDReader_GetSamplesWithTS_DigitalValue_Seconds);
    UNLOADDLLFUNCTION(DMDReader_GetSamplesAndTS_DigitalValue_Seconds);
    UNLOADDLLFUNCTION(DMDReader_GetConfigurationXML);
    UNLOADDLLFUNCTION(DMDReader_GetGlobalConfigItem);

    return ret;
}

#endif // _INITDMD

#undef LOADDLLFUNCTION
#undef UNLOADDLLFUNCTION
#undef DMDReader_CHECK_NULL

#ifdef __cplusplus
}
#endif

#endif // #define _DEWETRON_DMD_READER_API_LOAD_
