/*
 * Copyright (C) by Argonne National Laboratory
 *     See COPYRIGHT in top-level directory
 */

#include <adio.h>

#include <stdio.h>

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_IO_H
#include <io.h>
#endif

/*#define SYSHINT_DEBUG 1  */

#define ROMIO_HINT_DEFAULT_CFG "/etc/romio-hints"
#define ROMIO_HINT_ENV_VAR "ROMIO_HINTS"

/* debug function: a routine I want in the library to make my life easier when
 * using a source debugger.  Now optionally used in ADIO_Open. */
void ADIOI_Info_print_keyvals(MPI_Info info)
{
    int i, nkeys, flag;
    char key[MPI_MAX_INFO_KEY + 1];
    char value[MPI_MAX_INFO_VAL + 1];

    if (info == MPI_INFO_NULL)
        return;

    MPI_Info_get_nkeys(info, &nkeys);

    for (i = 0; i < nkeys; i++) {
        MPI_Info_get_nthkey(info, i, key);
        ADIOI_Info_get(info, key, MPI_MAX_INFO_VAL, value, &flag);
        printf("key = %-25s value = %-10s\n", key, value);
    }
    return;
}

/* if user set the environment variable, use its value to find the
 * file-of-hints.  Otherwise, we'll look for the default config file.  i.e. let
 * the user override systemwide hint processing */

static int find_file(void)
{
    int fd = -1;
    char *hintfile;

    hintfile = getenv(ROMIO_HINT_ENV_VAR);
    if (hintfile)
        fd = open(hintfile, O_RDONLY);
    if (fd < 0)
        fd = open(ROMIO_HINT_DEFAULT_CFG, O_RDONLY);

    return fd;
}

/* parse the file-of-hints.  Format is zero or more lines of "<key> <value>\n".
 * A # in collumn zero is a comment and the line will be ignored.  Do our best
 * to ignore badly formed lines too.
 *
 * The caller provides an 'info' object.  Each key-value pair found by the
 * parser will get added to the info object.  any keys already set will be left
 * alone on the assumption that the caller knows best.
 *
 * because MPI-IO hints are optional, we can get away with limited error
 * reporting.
 *
 * for better scalability, the config file will be read on one processor and
 * broadcast to all others */
static int file_to_info_all(int fd, MPI_Info info, int rank, MPI_Comm comm)
{
    char *buffer, *token, *key, *val, *garbage;
    char *pos1 = NULL, *pos2 = NULL;
    int flag;
    ssize_t ret;
    int valuelen;

    /* assumption: config files will be small */
#define HINTFILE_MAX_SIZE 1024*4
    buffer = (char *) ADIOI_Calloc(HINTFILE_MAX_SIZE, sizeof(char));

    if (rank == 0) {
        ret = (fd >= 0) ? read(fd, buffer, HINTFILE_MAX_SIZE) : -1;
        /* any error: bad/nonexistent fd, no perms, anything: set up a null
         * buffer and the subsequent string parsing will quit immediately */
        if (ret == -1)
            buffer[0] = '\0';
    }
    MPI_Bcast(buffer, HINTFILE_MAX_SIZE, MPI_BYTE, 0, comm);

    token = strtok_r(buffer, "\n", &pos1);
    if (token == NULL)
        goto fn_exit;
    do {
        if ((key = strtok_r(token, " \t", &pos2)) == NULL)
            /* malformed line: found no items */
            continue;
        if (token[0] == '#')
            /* ignore '#'-delimited comments */
            continue;
        if ((val = strtok_r(NULL, " \t", &pos2)) == NULL)
            /* malformed line: found key without value */
            continue;
        if ((garbage = strtok_r(NULL, " \t", &pos2)) != NULL)
            /* malformed line: more than two items */
            continue;

#ifdef SYSHINT_DEBUG
        printf("found: key=%s val=%s\n", key, val);
#endif
        /* don't actually care what the value is. only want to know if key
         * exists: we leave it alone if so*/
        ADIOI_Info_get_valuelen(info, key, &valuelen, &flag);
        if (flag == 1)
            continue;
        ADIOI_Info_set(info, key, val);
    } while ((token = strtok_r(NULL, "\n", &pos1)) != NULL);

  fn_exit:
    ADIOI_Free(buffer);
    return 0;
}

void ADIOI_process_system_hints(ADIO_File fd, MPI_Info info)
{
    int hintfd = -1, rank;

    MPI_Comm_rank(fd->comm, &rank);
    if (rank == 0) {
        hintfd = find_file();
    }
    /* hintfd only significant on rank 0.  -1 (on rank 0) means no hintfile found  */
    file_to_info_all(hintfd, info, rank, fd->comm);

    if (hintfd != -1)
        close(hintfd);
}

/* given 'info', incorporate any hints in 'sysinfo' that are not already set
 * into 'new_info'.  Caller must free 'new_info' later. */
void ADIOI_incorporate_system_hints(MPI_Info info, MPI_Info sysinfo, MPI_Info * new_info)
{
    int i, nkeys_sysinfo, nkeys_info = 0, flag = 0;     /* must initialize flag to 0 */
    int valuelen;

    char val[MPI_MAX_INFO_VAL + 1], key[MPI_MAX_INFO_KEY + 1];

    if (sysinfo == MPI_INFO_NULL)
        nkeys_sysinfo = 0;
    else
        MPI_Info_get_nkeys(sysinfo, &nkeys_sysinfo);

    /* short-circuit: return immediately if no hints to process */
    if (info == MPI_INFO_NULL && nkeys_sysinfo == 0) {
        *new_info = MPI_INFO_NULL;
        return;
    }

    if (info == MPI_INFO_NULL)
        MPI_Info_create(new_info);
    else {
        /* tiny optimization: if 'info' has no keys, we can skip the check if a
         * hint is set: no keys means nothing has been set, and there's nothing
         * we might step on */
        MPI_Info_get_nkeys(info, &nkeys_info);
        MPI_Info_dup(info, new_info);
    }

    for (i = 0; i < nkeys_sysinfo; i++) {
        MPI_Info_get_nthkey(sysinfo, i, key);
        /* don't care about the value, just want to know if hint set already */
        if (info != MPI_INFO_NULL && nkeys_info)
            ADIOI_Info_get_valuelen(info, key, &valuelen, &flag);
        if (flag == 1)
            continue;   /* skip any hints already set by user */
        ADIOI_Info_get(sysinfo, key, MPI_MAX_INFO_VAL, val, &flag);
        ADIOI_Info_set(*new_info, key, val);
        flag = 0;
    }

    return;
}
