/*
 * Copyright (c) 2013-2018 Mellanox Technologies, Inc.
 *                         All rights reserved.
 * Copyright (c) 2016      Research Organization for Information Science
 *                         and Technology (RIST). All rights reserved.
 * Copyright (c) 2017      Cisco Systems, Inc.  All rights reserved
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */
#ifndef OSHMEM_PROC_PROC_H
#define OSHMEM_PROC_PROC_H

#include "oshmem_config.h"
#include "oshmem/types.h"
#include "oshmem/constants.h"

#include "opal/class/opal_list.h"
#include "opal/util/proc.h"
#include "opal/dss/dss_types.h"
#include "opal/mca/hwloc/hwloc-internal.h"

#include "orte/types.h"
#include "orte/runtime/orte_globals.h"

#include "ompi/proc/proc.h"
#include "ompi/communicator/communicator.h"

#include "oshmem/mca/scoll/scoll.h"
#include "oshmem/runtime/runtime.h"
#include "oshmem/shmem/shmem_api_logger.h"

BEGIN_C_DECLS

/* ******************************************************************** */

struct oshmem_group_t;

#define OSHMEM_PE_INVALID   (-1)

/* This struct will be copied into the padding field of an ompi_proc_t
 * so the size of oshmem_proc_data_t must be less or equal than
 * OMPI_PROC_PADDING_SIZE */
struct oshmem_proc_data_t {
    char * transport_ids;
    int num_transports;
};

typedef struct oshmem_proc_data_t oshmem_proc_data_t;

#define OSHMEM_PROC_DATA(proc) \
    ((oshmem_proc_data_t *)(proc)->padding)

/**
 * Group of Open SHMEM processes structure
 *
 * Set of processes used in collective operations.
 */
struct oshmem_group_t {
    opal_object_t               base;
    int                         id;             /**< index in global array */
    int                         my_pe;
    int                         proc_count;     /**< number of processes in group */
    int                         is_member;   /* true if my_pe is part of the group, participate in collectives */
    struct ompi_proc_t          **proc_array; /**< list of pointers to ompi_proc_t structures
                                                   for each process in the group */
    opal_list_t                 peer_list;

    /* Collectives module interface and data */
    mca_scoll_base_group_scoll_t g_scoll;
    ompi_communicator_t*         ompi_comm;
};
typedef struct oshmem_group_t oshmem_group_t;
OSHMEM_DECLSPEC OBJ_CLASS_DECLARATION(oshmem_group_t);

OSHMEM_DECLSPEC extern oshmem_group_t* oshmem_group_all;
OSHMEM_DECLSPEC extern oshmem_group_t* oshmem_group_self;
OSHMEM_DECLSPEC extern oshmem_group_t* oshmem_group_null;


/* ******************************************************************** */

/**
 * Initialize the OSHMEM process subsystem
 *
 * Initialize the Open SHMEM process subsystem.  This function will
 * query the run-time environment and build a list of the proc
 * instances in the current pe set.  The local information not
 * easily determined by the run-time ahead of time (architecture and
 * hostname) will be published during this call.
 *
 * @note While an ompi_proc_t will exist with mostly valid information
 * for each process in the pe set at the conclusion of this
 * call, some information will not be immediately available.  This
 * includes the architecture and hostname, which will be available by
 * the conclusion of the stage gate.
 *
 * @retval OSHMEM_SUCESS  System successfully initialized
 * @retval OSHMEM_ERROR   Initialization failed due to unspecified error
 */
OSHMEM_DECLSPEC int oshmem_proc_init(void);

/**
 * Finalize the OSHMEM Process subsystem
 *
 * Finalize the Open SHMEM process subsystem.  This function will
 * release all memory created during the life of the application,
 * including all ompi_proc_t structures.
 *
 * @retval OSHMEM_SUCCESS  System successfully finalized
 */
OSHMEM_DECLSPEC int oshmem_proc_finalize(void);

/**
 * Returns a pointer to the local process
 *
 * Returns a pointer to the local process.  Unlike oshmem_proc_self(),
 * the reference count on the local proc instance is not modified by
 * this function.
 *
 * @return Pointer to the local process structure
 */
static inline ompi_proc_t *oshmem_proc_local(void)
{
    return (ompi_proc_t *)ompi_proc_local_proc;
}

/**
 * Returns the proc instance for a given name
 *
 * Returns the proc instance for the specified process name.  The
 * reference count for the proc instance is not incremented by this
 * function.
 *
 * @param[in] name     The process name to look for
 *
 * @return Pointer to the process instance for \c name
 */
static inline ompi_proc_t *oshmem_proc_for_find(const orte_process_name_t name)
{
    return (ompi_proc_t *)ompi_proc_for_name(name);
}

static inline ompi_proc_t *oshmem_proc_find(int pe)
{
    orte_process_name_t name;

    name.jobid = ORTE_PROC_MY_NAME->jobid;
    name.vpid = pe;
    return oshmem_proc_for_find(name);
}

static inline int oshmem_proc_pe(ompi_proc_t *proc)
{
    return (proc ? (int) ((orte_process_name_t*)&proc->super.proc_name)->vpid : -1);
}

#define OSHMEM_PROC_JOBID(PROC)    (((orte_process_name_t*)&((PROC)->super.proc_name))->jobid)
#define OSHMEM_PROC_VPID(PROC)     (((orte_process_name_t*)&((PROC)->super.proc_name))->vpid)

/**
 * Initialize the OSHMEM process predefined groups
 *
 * Initialize the Open SHMEM process predefined groups.  This function will
 * query the run-time environment and build a list of the proc
 * instances in the current pe set.  The local information not
 * easily determined by the run-time ahead of time (architecture and
 * hostname) will be published during this call.
 *
 * @note This is primarily used once during SHMEM setup.
 *
 * @retval OSHMEM_SUCESS  System successfully initialized
 * @retval OSHMEM_ERROR   Initialization failed due to unspecified error
 */
OSHMEM_DECLSPEC int oshmem_proc_group_init(void);

/**
 * Finalize the OSHMEM process predefined groups
 *
 * @retval OSHMEM_SUCESS  System successfully initialized
 * @retval OSHMEM_ERROR   Initialization failed due to unspecified error
 */
OSHMEM_DECLSPEC int oshmem_proc_group_finalize(void);

/**
 * Release collectives used by the groups. The function
 * must be called prior to the oshmem_proc_group_finalize()
 */
OSHMEM_DECLSPEC void oshmem_proc_group_finalize_scoll(void);

/**
 * Create processes group.
 *
 * Returns the list of known proc instances located in this group.
 *
 * @param[in] pe_start     The lowest PE in the active set.
 * @param[in] pe_stride    The log (base 2) of the stride between consecutive
 *                         PEs in the active set.
 * @param[in] pe_size      The number of PEs in the active set.
 *
 * @return Array of pointers to proc instances in the current
 * known universe, or NULL if there is an internal failure.
 */
OSHMEM_DECLSPEC oshmem_group_t *oshmem_proc_group_create(int pe_start,
                                                         int pe_stride,
                                                         int pe_size);

/**
 * same as above but abort on failure
 */
static inline oshmem_group_t *
oshmem_proc_group_create_nofail(int pe_start, int pe_stride, int pe_size)
{
    oshmem_group_t *group;

    group = oshmem_proc_group_create(pe_start, pe_stride, pe_size);
    if (NULL == group) {
        goto fatal;
    }
    return group;

fatal:
    SHMEM_API_ERROR("Failed to create group (%d,%d,%d)",
                    pe_start, pe_stride, pe_size);
    oshmem_shmem_abort(-1);
    return NULL;
}


/**
 * Destroy processes group.
 *
 */
OSHMEM_DECLSPEC void oshmem_proc_group_destroy(oshmem_group_t* group);

static inline ompi_proc_t *oshmem_proc_group_all(int pe)
{
    return oshmem_group_all->proc_array[pe];
}

static inline ompi_proc_t *oshmem_proc_group_find(oshmem_group_t* group,
                                                    int pe)
{
    int i = 0;
    ompi_proc_t* proc = NULL;

    if (OPAL_LIKELY(group)) {
        if (OPAL_LIKELY(group == oshmem_group_all)) {
            /* To improve performance use direct index. It is feature of oshmem_group_all */
            proc = group->proc_array[pe];
        } else {
            for (i = 0; i < group->proc_count; i++) {
                if (pe == oshmem_proc_pe(group->proc_array[i])) {
                    proc = group->proc_array[i];
                    break;
                }
            }
        }
    } else {
        orte_process_name_t name;

        name.jobid = ORTE_PROC_MY_NAME->jobid;
        name.vpid = pe;
        proc = oshmem_proc_for_find(name);
    }

    return proc;
}

static inline int oshmem_proc_group_find_id(oshmem_group_t* group, int pe)
{
    int i = 0;
    int id = -1;

    if (group) {
        for (i = 0; i < group->proc_count; i++) {
            if (pe == oshmem_proc_pe(group->proc_array[i])) {
                id = i;
                break;
            }
        }
    }

    return id;
}

static inline int oshmem_proc_group_is_member(oshmem_group_t *group)
{
    return group->is_member;
}

static inline int oshmem_num_procs(void)
{
    return (oshmem_group_all ?
        oshmem_group_all->proc_count : (int)opal_list_get_size(&ompi_proc_list));
}

static inline int oshmem_my_proc_id(void)
{
    return oshmem_group_self->my_pe;
}

static inline int oshmem_get_transport_id(int pe)
{
    ompi_proc_t *proc;

    proc = oshmem_proc_group_find(oshmem_group_all, pe);

    return (int) OSHMEM_PROC_DATA(proc)->transport_ids[0];
}

static inline int oshmem_get_transport_count(int pe)
{
    ompi_proc_t *proc;
    proc = oshmem_proc_group_find(oshmem_group_all, pe);
    return OSHMEM_PROC_DATA(proc)->num_transports;
}

END_C_DECLS

#endif /* OSHMEM_PROC_PROC_H */
