/** @file threads.h
 *  CVS: $Id$
 *  @author ??
 */

#ifndef PAPI_THREADS_H
#define PAPI_THREADS_H

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#ifdef HAVE_THREAD_LOCAL_STORAGE
#define THREAD_LOCAL_STORAGE_KEYWORD HAVE_THREAD_LOCAL_STORAGE
#else
#define THREAD_LOCAL_STORAGE_KEYWORD
#endif

#if defined(ANY_THREAD_GETS_SIGNAL) && !defined(_AIX)
#error "lookup_and_set_thread_symbols and _papi_hwi_broadcast_signal have only been tested on AIX"
#endif

typedef struct _ThreadInfo
{
	unsigned long int tid;
	unsigned long int allocator_tid;
	struct _ThreadInfo *next;
	hwd_context_t **context;
	void *thread_storage[PAPI_MAX_TLS];
	EventSetInfo_t **running_eventset;
	EventSetInfo_t *from_esi;          /* ESI used for last update this control state */
	int wants_signal;
} ThreadInfo_t;

/** The list of threads, gets initialized to master process with TID of getpid() 
 *	@internal */

extern volatile ThreadInfo_t *_papi_hwi_thread_head;

/* If we have TLS, this variable ALWAYS points to our thread descriptor. It's like magic! */

#if defined(HAVE_THREAD_LOCAL_STORAGE)
extern THREAD_LOCAL_STORAGE_KEYWORD ThreadInfo_t *_papi_hwi_my_thread;
#endif

/** Function that returns an unsigned long int thread identifier 
 *	@internal */

extern unsigned long int ( *_papi_hwi_thread_id_fn ) ( void );

/** Function that sends a signal to other threads 
 *	@internal */

extern int ( *_papi_hwi_thread_kill_fn ) ( int, int );

extern int _papi_hwi_initialize_thread( ThreadInfo_t ** dest, int tid );
extern int _papi_hwi_init_global_threads( void );
extern int _papi_hwi_shutdown_thread( ThreadInfo_t * thread, int force );
extern int _papi_hwi_shutdown_global_threads( void );
extern int _papi_hwi_broadcast_signal( unsigned int mytid );
extern int _papi_hwi_set_thread_id_fn( unsigned long int ( *id_fn ) ( void ) );

inline_static int
_papi_hwi_lock( int lck )
{
	if ( _papi_hwi_thread_id_fn ) {
		_papi_hwd_lock( lck );
		THRDBG( "Lock %d\n", lck );
	} else {
		( void ) lck;		 /* unused if !defined(DEBUG) */
		THRDBG( "Skipped lock %d\n", lck );
	}

	return ( PAPI_OK );
}

inline_static int
_papi_hwi_unlock( int lck )
{
	if ( _papi_hwi_thread_id_fn ) {
		_papi_hwd_unlock( lck );
		THRDBG( "Unlock %d\n", lck );
	} else {
		( void ) lck;		 /* unused if !defined(DEBUG) */
		THRDBG( "Skipped unlock %d\n", lck );
	}

	return ( PAPI_OK );
}

inline_static ThreadInfo_t *
_papi_hwi_lookup_thread( int custom_tid )
{

	unsigned long int tid;
	ThreadInfo_t *tmp;


	if (custom_tid==0) {
#ifdef HAVE_THREAD_LOCAL_STORAGE
	   THRDBG( "TLS returning %p\n", _papi_hwi_my_thread );
	   return ( _papi_hwi_my_thread );
#else
	   if ( _papi_hwi_thread_id_fn == NULL ) {
	      THRDBG( "Threads not initialized, returning master thread at %p\n",
				_papi_hwi_thread_head );
	      return ( ( ThreadInfo_t * ) _papi_hwi_thread_head );
	   }

	   tid = ( *_papi_hwi_thread_id_fn ) (  );
#endif
	}
	else {
	  tid=custom_tid;
	}
	THRDBG( "Threads initialized, looking for thread %#lx\n", tid );

	_papi_hwi_lock( THREADS_LOCK );

	tmp = ( ThreadInfo_t * ) _papi_hwi_thread_head;
	while ( tmp != NULL ) {
		THRDBG( "Examining thread tid %#lx at %p\n", tmp->tid, tmp );
		if ( tmp->tid == tid )
			break;
		tmp = tmp->next;
		if ( tmp == _papi_hwi_thread_head ) {
			tmp = NULL;
			break;
		}
	}

	if ( tmp ) {
		_papi_hwi_thread_head = tmp;
		THRDBG( "Found thread %ld at %p\n", tid, tmp );
	} else {
		THRDBG( "Did not find tid %ld\n", tid );
	}

	_papi_hwi_unlock( THREADS_LOCK );
	return ( tmp );

}

inline_static int
_papi_hwi_lookup_or_create_thread( ThreadInfo_t ** here, int tid )
{
	ThreadInfo_t *tmp = _papi_hwi_lookup_thread( tid );
	int retval = PAPI_OK;

	if ( tmp == NULL )
	  retval = _papi_hwi_initialize_thread( &tmp, tid );

	if ( retval == PAPI_OK )
		*here = tmp;

	return ( retval );
}

/* Prototypes */
void _papi_hwi_shutdown_the_thread_list( void );
void _papi_hwi_cleanup_thread_list( void );
int _papi_hwi_insert_in_thread_list( ThreadInfo_t * ptr );
ThreadInfo_t *_papi_hwi_lookup_in_thread_list(  );
void _papi_hwi_shutdown_the_thread_list( void );
int _papi_hwi_get_thr_context( void ** );
int _papi_hwi_gather_all_thrspec_data( int tag, PAPI_all_thr_spec_t * where );

#endif
