/* -*- Mode: C; c-basic-offset:4 ; -*- */
/*  
 *  (C) 2007 by Marcelo de Castro Cardoso.
 *      Integrade Project
 */

/*********************** PMI implementation ********************************/
/*
 * This file implements the client-side of the PMI interface.
 *
 * Note that the PMI client code must not print error messages (except 
 * when an abort is required) because MPI error handling is based on
 * reporting error codes to which messages are attached.  
 *
 * In v2, we should require a PMI client interface to use MPI error codes
 * to provide better integration with MPICH2.  
 */
/***************************************************************************/

#include "pmiconf.h" 

#define PMI_VERSION    1
#define PMI_SUBVERSION 1

#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#ifdef USE_PMI_PORT
#ifndef MAXHOSTNAME
#define MAXHOSTNAME 256
#endif
#endif
/* This should be moved to pmiu for shutdown */
#if defined(HAVE_SYS_SOCKET_H)
#include <sys/socket.h>
#endif
/* mpimem includes the definitions for MPIU_Snprintf, MPIU_Malloc, and 
   MPIU_Free */
#include "mpimem.h"

/* Temporary debug definitions */
#if 1
#define DBG_PRINTF(args) printf args ; fflush(stdout)
#else
#define DBG_PRINTF(args)
#endif

#include "pmi.h"
#include "EMConnOil.h"
#include "ig_pmiutils.h"
#include "mpi.h"

/* 
   These are global variable used *ONLY* in this file, and are hence
   declared static.
 */
static char * configFile = "execution.conf";
static char * EMPROXYNAME = "EM";

static int PMI_size = 1;
static int PMI_rank = 0;
static int PMI_appnum = 0;
static char * PMI_appid = NULL;

static char * PMI_kvs_my_name = NULL;
static char * PMI_kvs_my_key = NULL;
static char * PMI_kvs_my_value = NULL;

static int PMI_kvs_iterator = 0;

static char emior[512] = "\0";

typedef struct
{
	CONN_t kvs;
	int size;
} PMI_KVS_IG;

static PMI_KVS_IG kvs_ig = {NULL, 0}; 

/* Set PMI_initialized to 1 for singleton init but no process manager
   to help.  Initialized to 2 for normal initialization.  Initialized
   to values higher than 2 when singleton_init by a process manager.
   All values higher than 1 invlove a PM in some way.
*/
typedef enum { PMI_UNINITIALIZED = 0, 
               PMI_NORMAL_INIT = 1} PMIState;
static PMIState PMI_initialized = PMI_UNINITIALIZED;

/* ALL GLOBAL VARIABLES MUST BE INITIALIZED TO AVOID POLLUTING THE 
   LIBRARY WITH COMMON SYMBOLS */
static int PMI_kvsname_max = 0;
static int PMI_keylen_max = 0;
static int PMI_vallen_max = 0;

static int PMI_debug = 0;
static int PMI_debug_init = 0;    /* Set this to true to debug the init
				     handshakes */
static int PMI_spawned = 0;

/******************************** Group functions *************************/

int PMI_Init( int *spawned )
{
	char * p, name[10];
	int notset = 1;
	int rc;

	/* setvbuf(stdout,0,_IONBF,0); */
	setbuf(stdout,NULL);

	/* Get the value of PMI_DEBUG from the environment if possible, since
	 * we may have set it to help debug the setup process 
	 */
	if ( ( p = getenv( "PMI_DEBUG" ) ) )
		PMI_debug = atoi( p );
	else 
		PMI_debug = 0;

	/* Create proxy comunication to Execution Manager of Integrade
	 */
	if (connCreateProxy(EMPROXYNAME, emior) != CONN_SUCCESS)
		return PMI_FAIL;
		
	/* set variables of the execution
	 */
	if(PMIU_loadConfig(configFile, "numExecs", &p) == UTILS_FAIL)
		return PMI_FAIL;
		
	PMI_size = atoi(p);
	free(p);
	
	if(PMIU_loadConfig(configFile, "processId", &p) == UTILS_FAIL)
		return PMI_FAIL;
		
	PMI_rank = atoi(p);
	free(p);
	
	if(PMIU_loadConfig(configFile, "numApplicationId", &p) == UTILS_FAIL)
		return PMI_FAIL;
		
	PMI_appnum = atoi(p);
	free(p);
	
	if(PMIU_loadConfig(configFile, "applicationId", &PMI_appid) == UTILS_FAIL)
		return PMI_FAIL;

	PMI_kvsname_max = 512;
	PMI_keylen_max  = 512;
	PMI_vallen_max  = 512;

	PMI_kvs_my_key = (char*) malloc(sizeof(char) * (PMI_keylen_max + 1));
	PMI_kvs_my_value = (char*) malloc(sizeof(char) * (PMI_keylen_max + 1));
	PMI_kvs_my_name = (char*) malloc(sizeof(char) * (PMI_kvsname_max + 1));
	
	strcpy(PMI_kvs_my_name, "kvs_ig_app_");
	sprintf(name, "%d", PMI_appnum);
	strcat(PMI_kvs_my_name, name);

	PMI_spawned = 0;
	*spawned = 0;

	PMI_initialized = PMI_NORMAL_INIT;

	return PMI_SUCCESS;
}

int PMI_Initialized( PMI_BOOL *initialized )
{
    /* Turn this into a logical value (1 or 0) .  This allows us
       to use PMI_initialized to distinguish between initialized with
       an PMI service (e.g., via mpiexec) and the singleton init, 
       which has no PMI service */
    *initialized = PMI_initialized != PMI_UNINITIALIZED ? PMI_TRUE : PMI_FALSE;
    return PMI_SUCCESS;
}

int PMI_Get_size( int *size )
{
	if ( PMI_initialized != PMI_UNINITIALIZED )
		*size = PMI_size;
	else
		*size = 1;

	return PMI_SUCCESS;
}

int PMI_Get_rank( int *rank )
{
	if ( PMI_initialized != PMI_UNINITIALIZED )
		*rank = PMI_rank;
	else
		*rank = 0;

	return PMI_SUCCESS;
}

/* 
 * Get_universe_size is one of the routines that needs to communicate
 * with the process manager.  If we started as a singleton init, then
 * we first need to connect to the process manager and acquire the 
 * needed information.
 */
int PMI_Get_universe_size( int *size)
{
    if ( PMI_initialized != PMI_UNINITIALIZED )
		*size = PMI_size;
	else
		*size = 1;

	return PMI_SUCCESS;
}

int PMI_Get_appnum( int *appnum )
{	
	if ( PMI_initialized != PMI_UNINITIALIZED )
		*appnum = PMI_appnum;
	else
		*appnum = 0;

	return PMI_SUCCESS;
}

int PMI_Barrier( )
{
	int err = PMI_SUCCESS;

	if ( PMI_initialized != PMI_UNINITIALIZED) 
    {
    	if (connRegisterNode(EMPROXYNAME, PMI_rank, PMI_kvs_my_key, PMI_kvs_my_value, PMI_appid, &(kvs_ig.kvs), &(kvs_ig.size)) == CONN_ERROR)
    		err = PMI_FAIL;
    }

    return err;
}

int PMI_Get_clique_size( int *size )
{
    *size = 1;
    return PMI_SUCCESS;
}

int PMI_Get_clique_ranks( int ranks[], int length )
{
	if ( length < 1 )
		return PMI_ERR_INVALID_ARG;
	
	return PMI_Get_rank( &ranks[0] );
}

/* Inform the process manager that we're in finalize */
int PMI_Finalize( )
{
	int err = PMI_SUCCESS;

	if ( PMI_initialized != PMI_UNINITIALIZED) 
	{
		
	}
    
	return err;
}

int PMI_Abort(int exit_code, const char error_msg[])
{
    exit(exit_code);
    return -1;
}

/************************************* Keymap functions **********************/

int PMI_KVS_Get_my_name( char kvsname[], int length )
{
	strcpy(kvsname, PMI_kvs_my_name);
    return PMI_SUCCESS;
}

int PMI_KVS_Get_name_length_max( int *maxlen )
{
	if (maxlen == NULL)
		return PMI_ERR_INVALID_ARG;
		
	*maxlen = PMI_kvsname_max;
	return PMI_SUCCESS;
}

int PMI_KVS_Get_key_length_max( int *maxlen )
{
	if (maxlen == NULL)
		return PMI_ERR_INVALID_ARG;

	*maxlen = PMI_keylen_max;
	return PMI_SUCCESS;
}

int PMI_KVS_Get_value_length_max( int *maxlen )
{
	if (maxlen == NULL)
		return PMI_ERR_INVALID_ARG;

	*maxlen = PMI_vallen_max;
	return PMI_SUCCESS;
}

/* We will use the default kvsname for both the kvs_domain_id and for the id */
/* Hence the implementation of the following three functions */

int PMI_Get_id_length_max( int *length )
{
	if (length == NULL)
		return PMI_ERR_INVALID_ARG;

	*length = PMI_kvsname_max;
	return PMI_SUCCESS;
}

int PMI_Get_id( char id_str[], int length )
{
	strcpy(id_str, PMI_appid);
	return PMI_SUCCESS;
}

int PMI_Get_kvs_domain_id( char id_str[], int length )
{
	return PMI_KVS_Get_my_name( id_str, length );
}

int PMI_KVS_Create( char kvsname[], int length )
{
	return PMI_KVS_Get_my_name( kvsname, length );
}

int PMI_KVS_Destroy( const char kvsname[] )
{
	return PMI_SUCCESS;
}

int PMI_KVS_Put( const char kvsname[], const char key[], const char value[] )
{
	if (strcmp(kvsname, PMI_kvs_my_name) != 0)
		return PMI_ERR_INVALID_ARG;

	strcpy(PMI_kvs_my_key, key);
	strcpy(PMI_kvs_my_value, value);

    return PMI_SUCCESS;
}

int PMI_KVS_Commit( const char kvsname[] )
{
    /* no-op in this implementation */
    return PMI_SUCCESS;
}

int PMI_KVS_Get( const char kvsname[], const char key[], char value[], 
		 int length)
{
    int i;
    
    if (strcmp(kvsname, PMI_kvs_my_name) != 0)
		return PMI_ERR_INVALID_ARG;
    
    for (i = 0; i < kvs_ig.size; i++)
    	if (strcmp(kvs_ig.kvs[i].key, key) == 0)
    	{
    		strcpy(value, kvs_ig.kvs[i].value);
    		return PMI_SUCCESS;
    	}

    return PMI_ERR_INVALID_KEY;
}

int PMI_KVS_Iter_first(const char kvsname[], char key[], int key_len, 
		       char val[], int val_len)
{
	PMI_kvs_iterator = 0;
	
	return PMI_KVS_Iter_next(kvsname, key, key_len, val, val_len);
}

int PMI_KVS_Iter_next(const char kvsname[], char key[], int key_len, 
		      char val[], int val_len)
{
    if (strcmp(kvsname, PMI_kvs_my_name) != 0)
		return PMI_ERR_INVALID_ARG;

	strcpy(key, kvs_ig.kvs[PMI_kvs_iterator].key);
	strcpy(val, kvs_ig.kvs[PMI_kvs_iterator].value);
	PMI_kvs_iterator++;
	
    return PMI_SUCCESS;
}

/*************************** Name Publishing functions **********************/

int PMI_Publish_name( const char service_name[], const char port[] )
{
    return PMI_SUCCESS;
}

int PMI_Unpublish_name( const char service_name[] )
{
    return PMI_SUCCESS;
}

int PMI_Lookup_name( const char service_name[], char port[] )
{
    return PMI_SUCCESS;
}


/************************** Process Creation functions **********************/

int PMI_Spawn_multiple(int count,
                       const char * cmds[],
                       const char ** argvs[],
                       const int maxprocs[],
                       const int info_keyval_sizes[],
                       const PMI_keyval_t * info_keyval_vectors[],
                       int preput_keyval_size,
                       const PMI_keyval_t preput_keyval_vector[],
                       int errors[])
{
    return PMI_SUCCESS;
}

int PMI_Args_to_keyval(int *argcp, char *((*argvp)[]), PMI_keyval_t **keyvalp, 
		       int *size)
{
    return PMI_SUCCESS;
}

int PMI_Parse_option(int num_args, char *args[], int *num_parsed, 
		     PMI_keyval_t **keyvalp, int *size)
{
	if (num_args < 1)
		return PMI_ERR_INVALID_NUM_ARGS;
	if (args == NULL)
  		return PMI_ERR_INVALID_ARGS;
	if (num_parsed == NULL)
		return PMI_ERR_INVALID_NUM_PARSED;
	if (keyvalp == NULL)
		return PMI_ERR_INVALID_KEYVALP;
	if (size == NULL)
		return PMI_ERR_INVALID_SIZE;
	*num_parsed = 0;
	*keyvalp = NULL;
	*size = 0;
	return PMI_SUCCESS;
}

int PMI_Free_keyvals(PMI_keyval_t keyvalp[], int size)
{
	int i;
	if (size < 0)
		return PMI_ERR_INVALID_ARG;
	if (keyvalp == NULL && size > 0)
		return PMI_ERR_INVALID_ARG;
	if (size == 0)
 		return PMI_SUCCESS;
	/* free stuff */
	for (i=0; i<size; i++)
	{
		MPIU_Free(keyvalp[i].key);
		MPIU_Free(keyvalp[i].val);
	}
	MPIU_Free(keyvalp);
	return PMI_SUCCESS;
}
