/* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */
/*
 * Copyright (c) 2010      Cisco Systems, Inc.  All rights reserved.
 * Copyright (c) 2004-2011 The University of Tennessee and The University
 *                         of Tennessee Research Foundation.  All rights
 *                         reserved.
 * Copyright (c) 2011-2014 Los Alamos National Security, LLC.  All rights
 *                         reserved.
 * Copyright (c) 2014-2015 Intel, Inc. All rights reserved.
 * Copyright (c) 2014      Research Organization for Information Science
 *                         and Technology (RIST). All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 *
 */

#include "opal_config.h"
#include "opal/constants.h"

#include <time.h>
#include <string.h>

#include "opal_stdint.h"
#include "opal/class/opal_hash_table.h"
#include "opal/class/opal_pointer_array.h"
#include "opal/dss/dss_types.h"
#include "opal/util/error.h"
#include "opal/util/output.h"
#include "opal/util/proc.h"
#include "opal/util/show_help.h"

#include "opal/mca/pmix/base/base.h"
#include "opal/mca/pmix/base/pmix_base_hash.h"

/**
 * Data for a particular opal process
 * The name association is maintained in the
 * proc_data hash table.
 */
typedef struct {
    /** Structure can be put on lists (including in hash tables) */
    opal_list_item_t super;
    bool loaded;
    /* List of opal_value_t structures containing all data
       received from this process, sorted by key. */
    opal_list_t data;
} opal_pmix_proc_data_t;
static void proc_data_construct(opal_pmix_proc_data_t *ptr)
{
    ptr->loaded = false;
    OBJ_CONSTRUCT(&ptr->data, opal_list_t);
}

static void proc_data_destruct(opal_pmix_proc_data_t *ptr)
{
    OPAL_LIST_DESTRUCT(&ptr->data);
}
OBJ_CLASS_INSTANCE(opal_pmix_proc_data_t,
                   opal_list_item_t,
                   proc_data_construct,
                   proc_data_destruct);

/**
 * Find data for a given key in a given proc_data_t
 * container.
 */
static opal_value_t* lookup_keyval(opal_pmix_proc_data_t *proc_data,
                                   const char *key)
{
    opal_value_t *kv;

    OPAL_LIST_FOREACH(kv, &proc_data->data, opal_value_t) {
        if (0 == strcmp(key, kv->key)) {
            return kv;
        }
    }
    return NULL;
}

/**
 * Find proc_data_t container associated with given
 * opal_process_name_t.
 */
static opal_pmix_proc_data_t* lookup_proc(opal_proc_table_t *ptable,
                                          opal_process_name_t id, bool create)
{
    opal_pmix_proc_data_t *proc_data = NULL;

    opal_proc_table_get_value(ptable, id, (void**)&proc_data);
    if (NULL == proc_data && create) {
        proc_data = OBJ_NEW(opal_pmix_proc_data_t);
        if (NULL == proc_data) {
            opal_output(0, "pmix:hash:lookup_proc: unable to allocate proc_data_t\n");
            return NULL;
        }
        opal_proc_table_set_value(ptable, id, proc_data);
    }

    return proc_data;
}


static opal_proc_table_t ptable;

/* Initialize our hash table */
void opal_pmix_base_hash_init(void)
{
    OBJ_CONSTRUCT(&ptable, opal_proc_table_t);
    opal_proc_table_init(&ptable, 16, 256);
}

void opal_pmix_base_hash_finalize(void)
{
    opal_pmix_proc_data_t *proc_data;
    opal_process_name_t key;
    void *node1, *node2;

    /* to assist in getting a clean valgrind, cycle thru the hash table
     * and release all data stored in it
     */
    if (OPAL_SUCCESS == opal_proc_table_get_first_key(&ptable, &key,
                                                      (void**)&proc_data,
                                                      &node1, &node2)) {
        if (NULL != proc_data) {
            OBJ_RELEASE(proc_data);
        }
        while (OPAL_SUCCESS == opal_proc_table_get_next_key(&ptable, &key,
                                                            (void**)&proc_data,
                                                            node1, &node1,
                                                            node2, &node2)) {
            if (NULL != proc_data) {
                OBJ_RELEASE(proc_data);
            }
        }
    }
    OBJ_DESTRUCT(&ptable);
}



int opal_pmix_base_store(const opal_process_name_t *id,
                         opal_value_t *val)
{
    opal_pmix_proc_data_t *proc_data;
    opal_value_t *kv;
    int rc;

    opal_output_verbose(1, opal_pmix_base_framework.framework_output,
                        "%s pmix:hash:store storing data for proc %s",
                        OPAL_NAME_PRINT(OPAL_PROC_MY_NAME), OPAL_NAME_PRINT(*id));

    /* lookup the proc data object for this proc */
    if (NULL == (proc_data = lookup_proc(&ptable, *id, true))) {
        /* unrecoverable error */
        OPAL_OUTPUT_VERBOSE((5, opal_pmix_base_framework.framework_output,
                             "%s pmix:hash:store: storing data for proc %s unrecoverably failed",
                             OPAL_NAME_PRINT(OPAL_PROC_MY_NAME), OPAL_NAME_PRINT(*id)));
        return OPAL_ERR_OUT_OF_RESOURCE;
    }

    /* see if we already have this key in the data - means we are updating
     * a pre-existing value
     */
    kv = lookup_keyval(proc_data, val->key);
#if OPAL_ENABLE_DEBUG
    char *_data_type = opal_dss.lookup_data_type(val->type);
    OPAL_OUTPUT_VERBOSE((5, opal_pmix_base_framework.framework_output,
                         "%s pmix:hash:store: %s key %s[%s] for proc %s",
                         OPAL_NAME_PRINT(OPAL_PROC_MY_NAME),
                         (NULL == kv ? "storing" : "updating"),
                         val->key, _data_type, OPAL_NAME_PRINT(*id)));
    free (_data_type);
#endif

    if (NULL != kv) {
        opal_list_remove_item(&proc_data->data, &kv->super);
        OBJ_RELEASE(kv);
    }
    /* create the copy */
    if (OPAL_SUCCESS != (rc = opal_dss.copy((void**)&kv, val, OPAL_VALUE))) {
        OPAL_ERROR_LOG(rc);
        return rc;
    }
    opal_list_append(&proc_data->data, &kv->super);

    return OPAL_SUCCESS;
}

int opal_pmix_base_fetch(const opal_process_name_t *id,
                         const char *key, opal_list_t *kvs)
{
    opal_pmix_proc_data_t *proc_data;
    opal_value_t *kv, *knew;
    int rc;

    OPAL_OUTPUT_VERBOSE((5, opal_pmix_base_framework.framework_output,
                         "%s pmix:hash:fetch: searching for key %s on proc %s",
                         OPAL_NAME_PRINT(OPAL_PROC_MY_NAME),
                         (NULL == key) ? "NULL" : key, OPAL_NAME_PRINT(*id)));

    /* lookup the proc data object for this proc */
    if (NULL == (proc_data = lookup_proc(&ptable, *id, true))) {
        OPAL_OUTPUT_VERBOSE((5, opal_pmix_base_framework.framework_output,
                             "%s pmix_hash:fetch data for proc %s not found",
                             OPAL_NAME_PRINT(OPAL_PROC_MY_NAME),
                             OPAL_NAME_PRINT(*id)));
        return OPAL_ERR_NOT_FOUND;
    }

    /* if the key is NULL, that we want everything */
    if (NULL == key) {
        /* must provide an output list or this makes no sense */
        if (NULL == kvs) {
            OPAL_ERROR_LOG(OPAL_ERR_BAD_PARAM);
            return OPAL_ERR_BAD_PARAM;
        }
        OPAL_LIST_FOREACH(kv, &proc_data->data, opal_value_t) {
            /* copy the value */
            if (OPAL_SUCCESS != (rc = opal_dss.copy((void**)&knew, kv, OPAL_VALUE))) {
                OPAL_ERROR_LOG(rc);
                return rc;
            }
            OPAL_OUTPUT_VERBOSE((5, opal_pmix_base_framework.framework_output,
                                 "%s pmix:hash:fetch: adding data for key %s on proc %s",
                                 OPAL_NAME_PRINT(OPAL_PROC_MY_NAME),
                                 (NULL == kv->key) ? "NULL" : kv->key,
                                 OPAL_NAME_PRINT(*id)));

            /* add it to the output list */
            opal_list_append(kvs, &knew->super);
        }
        return OPAL_SUCCESS;
    }

    /* find the value */
    if (NULL == (kv = lookup_keyval(proc_data, key))) {
        OPAL_OUTPUT_VERBOSE((5, opal_pmix_base_framework.framework_output,
                             "%s pmix_hash:fetch key %s for proc %s not found",
                             OPAL_NAME_PRINT(OPAL_PROC_MY_NAME),
                             (NULL == key) ? "NULL" : key,
                             OPAL_NAME_PRINT(*id)));
        return OPAL_ERR_NOT_FOUND;
    }

    /* if the user provided a NULL list object, then they
     * just wanted to know if the key was present */
    if (NULL == kvs) {
        return OPAL_SUCCESS;
    }

    /* create the copy */
    if (OPAL_SUCCESS != (rc = opal_dss.copy((void**)&knew, kv, OPAL_VALUE))) {
        OPAL_ERROR_LOG(rc);
        return rc;
    }
    /* add it to the output list */
    opal_list_append(kvs, &knew->super);

    return OPAL_SUCCESS;
}

int opal_pmix_base_remove(const opal_process_name_t *id, const char *key)
{
    opal_pmix_proc_data_t *proc_data;
    opal_value_t *kv;

    /* lookup the specified proc */
    if (NULL == (proc_data = lookup_proc(&ptable, *id, false))) {
        /* no data for this proc */
        return OPAL_SUCCESS;
    }

    /* if key is NULL, remove all data for this proc */
    if (NULL == key) {
        while (NULL != (kv = (opal_value_t *) opal_list_remove_first(&proc_data->data))) {
            OBJ_RELEASE(kv);
        }
        /* remove the proc_data object itself from the jtable */
        opal_proc_table_remove_value(&ptable, *id);
        /* cleanup */
        OBJ_RELEASE(proc_data);
        return OPAL_SUCCESS;
    }

    /* remove this item */
    OPAL_LIST_FOREACH(kv, &proc_data->data, opal_value_t) {
        if (0 == strcmp(key, kv->key)) {
            opal_list_remove_item(&proc_data->data, &kv->super);
            OBJ_RELEASE(kv);
            break;
        }
    }

    return OPAL_SUCCESS;
}

