/*
 * This work (Common Community Physics Package), identified by NOAA, NCAR,
 * CU/CIRES, is free of known copyright restrictions and is placed in the
 * public domain.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * @file ccpp_dl.c
 *
 * Routines for the function/subroutine calls using dynamic loaded shared
 * objects.
 *
 * @ingroup CCPP
 * @{
 **/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <dlfcn.h>
#include <err.h>
#include <sysexits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "ccpp_dl.h"

/** Shared library prefix and suffix for different platforms **/
static const char prefix[] = "lib";
#if __APPLE__
static const char suffix[] = ".dylib";
#elif __unix__
static const char suffix[] = ".so";
#endif

/**
 * Function call initialization routine.
 *
 * This dlopen()'s the library specified and tries to
 * obtain a handle to the function/scheme cap.
 *
 * @param[in]  scheme    The scheme name to call.
 * @param[in]  lib       The library continaing the physics scheme.
 * @param[in]  ver       The library version number.
 * @param[out] fhdl      The scheme function pointer handle.
 * @param[out] lhdl      The library handle.
 * @retval     0         If it was sucessful
 * @retval     1         If there was an error
 **/
int
ccpp_dl_open(const char *scheme, const char *lib, const char *ver,
	     void **fhdl, void **lhdl)
{
	int i = 0;
	int n = 0;
	const char cap[] = "_cap";
	const char *l = NULL;
	char *library = NULL;
	char *scheme_cap = NULL;
	char *error = NULL;
	struct stat sbuf = {0};

	/* Did we get an actual library file? */
	if (stat(lib, &sbuf) == 0) {
		l = lib;
	} else {
		/* Generate the library name with the platform suffix */
		n = (strlen(prefix) + strlen(lib) + strlen(suffix)
		     + strlen(ver) +2) *sizeof(char);
		library = malloc(n);
		memset(library, 0, n);
		if (strcmp(ver, "") != 0) {
#ifdef __APPLE__
			snprintf(library, n, "%s%s.%s%s", prefix, lib,
				 ver, suffix);
#elif defined(__linux__) || defined(__unix__)
			snprintf(library, n, "%s%s%s.%s", prefix, lib,
				 suffix, ver);
#else
		 	warnx("CCPP library name not configured for this operating system");
		 	return(EXIT_FAILURE);
#endif
		} else {
			snprintf(library, n, "%s%s%s", prefix, lib, suffix);
		}
		l = library;
	}

	/* Generate the scheme cap function name */
	n = (strlen(scheme) +strlen(cap) +1)*sizeof(char);
	scheme_cap = malloc(n);
	memset(scheme_cap, 0, n);

	n = strlen(scheme);
	for (i=0; i < n; ++i) {
		scheme_cap[i] = tolower(scheme[i]);
	}

	strncat(scheme_cap, cap, n);

	/* Open a handle to the library */
	*lhdl = dlopen(l, RTLD_NOW);
	if (!*lhdl) {
		warnx("%s", dlerror());
		return(EXIT_FAILURE);
	}

	dlerror();
	*(void **)fhdl = dlsym(*lhdl, scheme_cap);
	if ((error = dlerror()) != NULL)  {
		warnx("%s", error);
		return(EXIT_FAILURE);
	}

	/* Free the library filename */
	if (library) {
		free(library);
		library = NULL;
	}

	/* Free the scheme cap function name */
	if (scheme_cap) {
		free(scheme_cap);
		scheme_cap = NULL;
	}

	return(EXIT_SUCCESS);
}

/**
 * Function call library closing routine.
 *
 * @param[in] lhdl      The library handle.
 * @retval     0        If it was sucessful
 * @retval     1        If there was an error
 **/
int
ccpp_dl_close(void **lhdl)
{
	char *error = NULL;

	dlerror();
	dlclose(*lhdl);
	if ((error = dlerror()) != NULL)  {
		warnx("%s", error);
		return(EXIT_FAILURE);
	}

	return(EXIT_SUCCESS);
}

/**
 * The function cap calling routine.
 *
 * @param[in] f_ptr     The scheme function pointer to call.
 * @param[in] data      The opaque ccpp_t data type to pass.
 * @retval     0        If it was sucessful
 * @retval    !=0       If there was an error
 **/
int
ccpp_dl_call(void **f_ptr, void **data)
{
	int (*fun)(void **) = NULL;

	*(int **)(&fun) = *f_ptr;

	return(fun(data));
}

/**
 * @}
 **/