/*
 * Copyright (c) 2015-2020      Mellanox Technologies, Inc.
 *                              All rights reserved.
 * Copyright (c) 2018           Amazon.com, Inc. or its affiliates.  All Rights reserved.
 * Copyright (c) 2019           Research Organization for Information Science
 *                              and Technology (RIST).  All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */

#include "oshmem_config.h"

#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "opal/util/opal_environ.h"
#include "opal/util/show_help.h"
#include "opal/util/output.h"
#include "opal/util/printf.h"

#include "ompi/runtime/ompi_rte.h"

#include "oshmem/version.h"
#include "oshmem/constants.h"
#include "oshmem/info/info.h"
#include "oshmem/shmem/shmem_api_logger.h"


/*
 * Global variables
 */
oshmem_info_t oshmem_shmem_info_env = {
    false,
    false,
    false,
    256 * 1024 * 1024
};

#define OSHMEM_MAX_LIBRARY_VERSION_STRING 256

/*
 * Local functions
 */
static int oshmem_info_value_to_bool(char *value, bool *interp);
static int oshmem_info_value_to_int(char *value, int *interp);
static int oshmem_info_get_heap_size(const char *name, char *value, size_t *interp);
static int oshmem_info_get_library_version(char *version, int *len);


/*
 * This function is called during oshmem_init.
 */
int oshmem_info_init(void)
{
    int ret = OSHMEM_SUCCESS;
    char *cptr;

    /* fill the env info object */

    if (NULL != (cptr = getenv(OSHMEM_ENV_VERSION))) {
        ret = oshmem_info_value_to_bool(cptr, &oshmem_shmem_info_env.print_version);
        if (OSHMEM_SUCCESS != ret) {
            goto out;
        }
    }
    if (oshmem_shmem_info_env.print_version && 0 == OMPI_PROC_MY_NAME->vpid) {
        char version[OSHMEM_MAX_LIBRARY_VERSION_STRING];
        int len;

        ret = oshmem_info_get_library_version(version, &len);
        if (OSHMEM_SUCCESS != ret || 0 == len) {
            goto out;
        }
        opal_show_help("help-shmem-runtime.txt",
                       "oshmem_init:print-version",
                       true,
                       version);
    }

    if (NULL != (cptr = getenv(OSHMEM_ENV_INFO))) {
        ret = oshmem_info_value_to_bool(cptr, &oshmem_shmem_info_env.print_info);
        if (OSHMEM_SUCCESS != ret) {
            goto out;
        }
    }
    if (oshmem_shmem_info_env.print_info && 0 == OMPI_PROC_MY_NAME->vpid) {
        opal_show_help("help-shmem-runtime.txt",
                       "oshmem_init:print-info",
                       true,
                       OSHMEM_ENV_VERSION,
                       OSHMEM_ENV_INFO,
                       OSHMEM_ENV_SYMMETRIC_SIZE,
                       OSHMEM_ENV_DEBUG);
    }

    if (NULL != (cptr = getenv(OSHMEM_ENV_DEBUG))) {
        ret = oshmem_info_value_to_bool(cptr, &oshmem_shmem_info_env.debug);
        if (OSHMEM_SUCCESS != ret) {
            goto out;
        }
    }

    if (NULL != (cptr = getenv(OSHMEM_ENV_SYMMETRIC_SIZE))) {
        char *p1;
        if (NULL != (p1 = getenv(SHMEM_HEAP_SIZE))) {
            SHMEM_API_WARNING("SYMMETRIC_HEAP_SIZE and SMA_HEAP_SIZE are both set;"
             "SYMMETRIC_HEAP_SIZE takes precedence.");
        }
        ret = oshmem_info_get_heap_size(OSHMEM_ENV_SYMMETRIC_SIZE, cptr,
                                        &oshmem_shmem_info_env.symmetric_heap_size);
        if (OSHMEM_SUCCESS != ret) {
            goto out;
        }
    } else if (NULL != (cptr = getenv(SHMEM_HEAP_SIZE))) {
        ret = oshmem_info_get_heap_size(SHMEM_HEAP_SIZE, cptr,
                                        &oshmem_shmem_info_env.symmetric_heap_size);
        if (OSHMEM_SUCCESS != ret) {
            goto out;
        }
    }

    /* All done */

    return OSHMEM_SUCCESS;

out:
    return ret;
}


/*
 * Shut down oshmem_info handling
 */
int oshmem_info_finalize(void)
{

    /* All done -- destroy the table */

    return OSHMEM_SUCCESS;
}


/**
 * Convert value string to boolean
 *
 * Convert value string \c value into a boolean.  The
 * strings "true", "false", and integer numbers can be converted
 * into booleans.  All others will return \c OSHMEM_ERR_BAD_PARAM
 *
 * @param value Value string for info key to interpret
 * @param interp returned interpretation of the value key
 *
 * @retval OSHMEM_SUCCESS string was successfully interpreted
 * @retval OSHMEM_ERR_BAD_PARAM string was not able to be interpreted
 */
static int oshmem_info_value_to_bool(char *value, bool *interp)
{
    int tmp;

    /* idiot case */
    if (NULL == value || NULL == interp) return OSHMEM_ERR_BAD_PARAM;

    /* is it true / false? */
    if (0 == strcmp(value, "true")) {
        *interp = true;
        return OSHMEM_SUCCESS;
    } else if (0 == strcmp(value, "false")) {
        *interp = false;
        return OSHMEM_SUCCESS;

    /* is it a number? */
    } else if (OSHMEM_SUCCESS == oshmem_info_value_to_int(value, &tmp)) {
        if (tmp == 0) {
            *interp = false;
        } else {
            *interp = true;
        }
        return OSHMEM_SUCCESS;
    }

    return OSHMEM_ERR_BAD_PARAM;
}


/**
 * Convert value string to integer
 *
 * Convert value string \c value into a integer.
 * All others will return \c OSHMEM_ERR_BAD_PARAM
 *
 * @param value Value string for info key to interpret
 * @param interp returned interpretation of the value key
 *
 * @retval OSHMEM_SUCCESS string was successfully interpreted
 * @retval OSHMEM_ERR_BAD_PARAM string was not able to be interpreted
 */
static int oshmem_info_value_to_int(char *value, int *interp)
{
    long tmp;
    char *endp;

    if (NULL == value || '\0' == value[0]) return OSHMEM_ERR_BAD_PARAM;

    errno = 0;
    tmp = strtol(value, &endp, 10);
    /* we found something not a number */
    if (*endp != '\0') return OSHMEM_ERR_BAD_PARAM;
    /* underflow */
    if (tmp == 0 && errno == EINVAL) return OSHMEM_ERR_BAD_PARAM;

    *interp = (int) tmp;

    return OSHMEM_SUCCESS;
}

static int oshmem_info_get_heap_size(const char *name, char *value, size_t *interp)
{
    char *p;
    long long factor = 1;
    int idx;
    long long size = 0;

    p = value;
    *interp = 0;

    if (!p) {
        return OSHMEM_ERR_BAD_PARAM;
    }

    /* Sanity check for buffer overflow attack (coverity issue: Use of untrusted string value) */
    if (16 < strlen(p)) {
        return OSHMEM_ERR_BAD_PARAM;
    }

    if (1 == sscanf(p, "%lld%n", &size, &idx)) {
        if (p[idx] != '\0') {
            if (p[idx + 1] == '\0') {
                if (p[idx] == 'k' || p[idx] == 'K') {
                    factor = 1024;
                } else if (p[idx] == 'm' || p[idx] == 'M') {
                    factor = 1024 * 1024;
                } else if (p[idx] == 'g' || p[idx] == 'G') {
                    factor = 1024 * 1024 * 1024;
                } else if (p[idx] == 't' || p[idx] == 'T') {
                    factor = 1024UL * 1024UL * 1024UL * 1024UL;
                } else {
                    size = 0;
                }
            } else {
                size = 0;
            }
        }
    }

    if (size <= 0) {
        return OSHMEM_ERR_BAD_PARAM;
    } else {
        opal_setenv(name, p, true, &environ);
/* Probably needless code */
#if 0
        char *tmp = p;

        if(!p) {
            opal_asprintf(&tmp, "%lld", size * factor);
        }

        if (tmp) {
            opal_setenv(OSHMEM_ENV_SYMMETRIC_SIZE, p, true, &environ);
            opal_setenv(SHMEM_HEAP_SIZE, p, true, &environ);
        }

        if (!p && tmp) {
            free(tmp);
        }
#endif
    }

    *interp = size * factor;

    return OSHMEM_SUCCESS;
}

static int oshmem_info_get_library_version(char *version, int *resultlen)
{
    int len_left;
    char *ptr, tmp[OSHMEM_MAX_LIBRARY_VERSION_STRING];

    ptr = tmp;
    len_left = sizeof(tmp);
    memset(tmp, 0, OSHMEM_MAX_LIBRARY_VERSION_STRING);

    snprintf(tmp, OSHMEM_MAX_LIBRARY_VERSION_STRING, "Open SHMEM v%d.%d.%d",
             OSHMEM_MAJOR_VERSION, OSHMEM_MINOR_VERSION, OSHMEM_RELEASE_VERSION);
    ptr += strlen(tmp);
    len_left -= strlen(tmp);

    if (strlen(OSHMEM_GREEK_VERSION) > 0) {
        snprintf(ptr, len_left, "%s", OSHMEM_GREEK_VERSION);
        ptr = tmp + strlen(tmp);
        len_left = OSHMEM_MAX_LIBRARY_VERSION_STRING - strlen(tmp);
    }

    /* Package name */
    if (strlen(OPAL_PACKAGE_STRING) > 0) {
        snprintf(ptr, len_left, ", package: %s", OPAL_PACKAGE_STRING);
        ptr = tmp + strlen(tmp);
        len_left = OSHMEM_MAX_LIBRARY_VERSION_STRING - strlen(tmp);
    }

    /* Ident string */
    if (strlen(OSHMEM_IDENT_STRING) > 0) {
        snprintf(ptr, len_left, ", ident: %s", OSHMEM_IDENT_STRING);
        ptr = tmp + strlen(tmp);
        len_left = OSHMEM_MAX_LIBRARY_VERSION_STRING - strlen(tmp);
    }

    /* Repository revision */
    if (strlen(OSHMEM_REPO_REV) > 0) {
        snprintf(ptr, len_left, ", repo rev: %s", OSHMEM_REPO_REV);
        ptr = tmp + strlen(tmp);
        len_left = OSHMEM_MAX_LIBRARY_VERSION_STRING - strlen(tmp);
    }

    /* Release date */
    if (strlen(OSHMEM_RELEASE_DATE) > 0) {
        snprintf(ptr, len_left, ", %s", OSHMEM_RELEASE_DATE);
        ptr = tmp + strlen(tmp);
        len_left = OSHMEM_MAX_LIBRARY_VERSION_STRING - strlen(tmp);
    }

    memcpy(version, tmp, strlen(tmp) + 1);
    *resultlen = strlen(tmp) + 1;

    return OSHMEM_SUCCESS;
}
