/*
 * --  thread.c.1
 *     Tests the thread-specific data API.
 */
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include "utils.h"

#define ITERATIONS ((int)1000000)
#define NUM_THREADS ((int)1)
#define MAX_NAME_SIZE ((size_t) 24)

static void
destroy_data( void *data );

typedef struct FULL_NAME
{
   char first[MAX_NAME_SIZE];
   char last[MAX_NAME_SIZE];
} full_name_t;

static pthread_t *th;
static pthread_attr_t attr_h;
static pthread_key_t name_key, address_key;
static char *address = "40 Elm St.";

void
thread_proc( full_name_t *thread_name )
{
   long sum = 0;
   int st, i;
   full_name_t *name = NULL;
   char *addr;

   for(i = 0; i < ITERATIONS; i++ )
       sum += 1;

   /*
    *  --  Set the name and address of the thread.
    */
   st = pthread_setspecific( name_key, thread_name );
   CHECK(st, "pthread_setspecific()");
   st = pthread_setspecific( address_key, address );
   CHECK(st, "pthread_setspecific()");

   for(i = 0; i < ITERATIONS * 2; i++ )
       sum += 1;

   st = pthread_getspecific( name_key, (void **) &name );
   CHECK(st, "pthread_getspecific()");
   st = pthread_getspecific( address_key, (void **) &addr );
   CHECK(st, "pthread_getspecific()");

   for(i = 0; i < ITERATIONS; i++ )
       sum += 1;

   printf_r("%s %s\n", name->first, name->last );
   printf_r("%s\n", addr );

   pthread_exit( (void *) SUCCESS );
}

static pthread_once_t key_once = PTHREAD_ONCE_INIT;

static void 
init_key( void )
{
   int st;

   st = pthread_key_create( &name_key, destroy_data );
   CHECK(st, "pthread_key_create()");

   st = pthread_key_create( &address_key, NULL );
   CHECK(st, "pthread_key_create()");
}

int 
main( int argc, char *argv[] )
{
   int i, st, exit_val = 0, thread_count = NUM_THREADS;
   full_name_t *full_name;

   if( argc == 2 )
       thread_count = atoi( argv[1] );

   st = pthread_once( &key_once, init_key ); 
   CHECK(st, "pthread_once()");

   /*
    *  --  Since threads are created, by default, as detached, then
    *      we must create them with an attributes object initialized
    *      with the PTHREAD_CREATE_JOINABLE attribute.
    */
   st = pthread_attr_init( &attr_h );
   CHECK(st, "pthread_attr_init()");
   st = pthread_attr_setdetachstate( &attr_h, PTHREAD_CREATE_JOINABLE );
   CHECK(st, "pthread_attr_setdetachstate()");

   /*
    *  --  Create and join a bunch of threads.
    */
   th = malloc_r( thread_count * sizeof( pthread_t ));
   for(i = 0; i < thread_count; i++ )
   {
       full_name = malloc_r( sizeof( full_name_t ));
       pthread_lock_global_np();
       sprintf( full_name->first, "%s_%d", "bob", i );
       sprintf( full_name->last, "%s_%d", "smith", i );
       pthread_unlock_global_np();

       st = pthread_create( &th[i], 
                            &attr_h, 
                            (thread_proc_t) thread_proc,
                            (void *) full_name );

       CHECK(st, "pthread_create()");
   }

   /*
    *  --  Join with each one.  The pthread_join() function automatically
    *      detaches the thread once the join is complete.  This means that
    *      a thread may be joined only once.
    */
   for(i = 0; i < thread_count; i++ )
   {
       st = pthread_join( th[i], (void **) &exit_val );
       CHECK(st, "pthread_join()");

       if( exit_val != SUCCESS )
           fprintf_r( stderr, "Failed to join with th[%d]!\n", i);
   }

   st = pthread_attr_destroy( &attr_h );
   CHECK(st, "pthread_attr_destry()");

   free_r( th );

   for(i = 0; i < ITERATIONS; i++ )
       ; 

   print_system_counters();
   return( EXIT_SUCCESS );
}

static void
destroy_data( void *data )
{
   free_r( data );
}
