/******************************************************************************
 * Copyright 1986, 2016 NVIDIA Corporation. All rights reserved.
 *****************************************************************************/

// examples/example_shared.h
//
// Code shared by all examples

#ifndef EXAMPLE_SHARED_H
#define EXAMPLE_SHARED_H

#include <cstdio>
#include <cstdlib>

#include <mi/neuraylib.h>

#ifdef MI_PLATFORM_WINDOWS
#include <mi/base/miwindows.h>
#else
#include <dlfcn.h>
#include <unistd.h>
#endif

#include "authentication.h"

// Pointer to the DSO handle. Cached here for unload().
void* g_dso_handle = 0;
// Pointer to the DSO filename. Cached here for unload().
const char* g_filename = 0;

// Ensures that the console with the log messages does not close immediately. On Windows, the user
// is asked to press enter. On other platforms, nothing is done as the examples are most likely
// started from the console anyway.
void keep_console_open() {
#ifdef MI_PLATFORM_WINDOWS
    if( IsDebuggerPresent()) {
        fprintf( stderr, "Press enter to continue . . . \n");
        fgetc( stdin);
    }
#endif // MI_PLATFORM_WINDOWS
}

// Helper macro. Checks whether the expression is true and if not prints a message and exits.
#define check_success( expr) \
    do { \
        if( !(expr)) { \
            fprintf( stderr, "Error in file %s, line %u: \"%s\".\n", __FILE__, __LINE__, #expr); \
            keep_console_open(); \
            exit( EXIT_FAILURE); \
        } \
    } while( false)

// Helper function similar to check_success(), but specifically for the result of
// #mi::neuraylib::INeuray::start().
void check_start_success( mi::Sint32 result)
{
    if( result == 0)
        return;
    fprintf( stderr, "mi::neuraylib::INeuray::start() failed with return code %d.\n", result);
    fprintf( stderr, "Typical failure reasons are related to authentication, see the\n");
    fprintf( stderr, "documentation of this method for details.\n");
    keep_console_open();
    exit( EXIT_FAILURE);
}

// printf() format specifier for arguments of type LPTSTR (Windows only).
#ifdef MI_PLATFORM_WINDOWS
#ifdef UNICODE
#define FMT_LPTSTR "%ls"
#else // UNICODE
#define FMT_LPTSTR "%s"
#endif // UNICODE
#endif // MI_PLATFORM_WINDOWS

// Loads the neuray library and calls the main factory function.
//
// This convenience function loads the neuray DSO, locates and calls the #mi_neuray_factory()
// function. It returns an instance of the main #mi::neuraylib::INeuray interface. It also
// supplies a authentication key (only needed by some variants of the neuray library).
// The function may be called only once.
//
// \param filename    The file name of the DSO. It is feasible to pass \c NULL, which uses a
//                    built-in default value.
// \return            A pointer to an instance of the main #mi::neuraylib::INeuray interface
mi::neuraylib::INeuray* load_and_get_ineuray( const char* filename = 0)
{
#ifdef MI_PLATFORM_WINDOWS
    if( !filename)
        filename = "libneuray.dll";
    g_filename = filename;
    void* handle = LoadLibraryA((LPSTR) filename);
    if( !handle) {
        LPTSTR buffer = 0;
        LPTSTR message = TEXT("unknown failure");
        DWORD error_code = GetLastError();
        if( FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS, 0, error_code,
            MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &buffer, 0, 0))
            message = buffer;
        fprintf( stderr, "Failed to load library (%u): " FMT_LPTSTR, error_code, message);
        if( buffer)
            LocalFree( buffer);
        return 0;
    }
    void* symbol = GetProcAddress((HMODULE) handle, "mi_neuray_factory");
    if( !symbol) {
        LPTSTR buffer = 0;
        LPTSTR message = TEXT("unknown failure");
        DWORD error_code = GetLastError();
        if( FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS, 0, error_code,
            MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &buffer, 0, 0))
            message = buffer;
        fprintf( stderr, "GetProcAddress error (%u): " FMT_LPTSTR, error_code, message);
        if( buffer)
            LocalFree( buffer);
        return 0;
    }
#else // MI_PLATFORM_WINDOWS
    if( !filename)
        filename = "libneuray.so";
    g_filename = filename;
#ifdef MI_PLATFORM_MACOSX
    void* handle = dlopen( filename, RTLD_LAZY);
#else // MI_PLATFORM_MACOSX
    void* handle = dlopen( filename, RTLD_LAZY|RTLD_DEEPBIND);
#endif // MI_PLATFORM_MACOSX
    if( !handle) {
        fprintf( stderr, "%s\n", dlerror());
        return 0;
    }
    void* symbol = dlsym( handle, "mi_neuray_factory");
    if( !symbol) {
        fprintf( stderr, "%s\n", dlerror());
        return 0;
    }
#endif // MI_PLATFORM_WINDOWS
    g_dso_handle = handle;

    typedef mi::neuraylib::INeuray* (INeuray_factory)
        (mi::neuraylib::IAllocator*, mi::Uint32);
    INeuray_factory* factory = (INeuray_factory*) symbol;
    mi::neuraylib::INeuray* neuray = factory( 0, MI_NEURAYLIB_API_VERSION);
    if( neuray)
        check_success( authenticate( neuray) == 0);
    return neuray;
}

// Unloads the neuray library.
bool unload()
{
#ifdef MI_PLATFORM_WINDOWS
    int result = FreeLibrary( (HMODULE)g_dso_handle);
    if( result == 0) {
        LPTSTR buffer = 0;
        LPTSTR message = TEXT("unknown failure");
        DWORD error_code = GetLastError();
        if( FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS, 0, error_code,
            MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &buffer, 0, 0))
            message = buffer;
        fprintf( stderr, "Failed to unload library (%u): " FMT_LPTSTR, error_code, message);
        if( buffer)
            LocalFree( buffer);
        return false;
    }
    return true;
#else
    int result = dlclose( g_dso_handle);
    if( result != 0)
        fprintf( stderr, "%s\n", dlerror());
#ifdef MI_PLATFORM_MACOSX
    void* handle = dlopen( g_filename, RTLD_LAZY|RTLD_NOLOAD);
#else // MI_PLATFORM_MACOSX
    void* handle = dlopen( g_filename, RTLD_LAZY|RTLD_NOLOAD|RTLD_DEEPBIND);
#endif // MI_PLATFORM_MACOSX
    return handle == 0;
#endif
}

// Sleep the indicated number of seconds.
void sleep_seconds( mi::Uint32 seconds)
{
#ifdef MI_PLATFORM_WINDOWS
    Sleep( seconds * 1000);
#else
    sleep( seconds);
#endif
}

// Map snprintf to _snprintf on Windows.
#ifdef MI_PLATFORM_WINDOWS
#define snprintf _snprintf
#endif

#endif // MI_EXAMPLE_SHARED_H