/**
 * threadpool.c
 *
 * This file will contain your implementation of a threadpool.
 */

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

#include "threadpool.h"

// _threadpool is the internal threadpool structure that is
// cast to type "threadpool" before it given out to callers
typedef struct _threadpool_st 
{
  // The pointer to the array of pthread(s)
  pthread_t t_Pool[MAXT_IN_POOL];
  // The mutex that prevents multiple copies of dispatch and destroy_threadpool
  // from being called at one time.
  pthread_mutex_t oneAtATime;
  // This will be the index of the next position in which to create a thread. 
  int nextThreadToCreate;
  // The maximum number of threads in the pool
  int maxThreads;
  // you should fill in this structure with whatever you need
} _threadpool;

typedef struct func_and_arg
{
  void (*func)(void *);
  void * arg;
} func_and_arg;

void dispatchfunc(void * arg);

threadpool create_threadpool(int num_threads_in_pool)
{
  _threadpool *pool;

  // sanity check the argument
  if ((num_threads_in_pool <= 0) || (num_threads_in_pool > MAXT_IN_POOL))
    return NULL;

  pool = (_threadpool *) malloc(sizeof(_threadpool));
  if (pool == NULL) {
    fprintf(stderr, "Out of memory creating a new threadpool!\n");
    return NULL;
  }
  // add your code here to initialize the newly created threadpool
  //pool->t_Pool = (pthread_t **) malloc(sizeof(pthread_t *)*num_threads_in_pool);
  // a bit of a hack; sets all pointers to NULL (0x00000000)
  memset(pool->t_Pool,0,(sizeof(pthread_t *)*MAXT_IN_POOL));//num_threads_in_pool));
  
  pool->nextThreadToCreate=0;
  pthread_mutex_init(&pool->oneAtATime, NULL);
  pool->maxThreads=num_threads_in_pool;

  return (threadpool) pool;
}
//    char   *hostname;
//    char   *servicename;


void dispatch(threadpool from_me, dispatch_fn dispatch_to_here,
	      void *arg) 
{
  _threadpool *pool = (_threadpool *) from_me;
  int oldtype;
  func_and_arg * funcstr;

  pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
  pthread_cleanup_push(pthread_mutex_unlock, (void *) &pool->oneAtATime);

  pthread_mutex_lock(&pool->oneAtATime);
  //fprintf(stderr,"Mutext gotten\n");
  if(pool->t_Pool[pool->nextThreadToCreate] != 0)
    {
      //fprintf(stderr,"thread joining");
      pthread_join((pool->t_Pool[pool->nextThreadToCreate]),NULL);
      //fprintf(stderr,"threadjoind\n");
    }
  //fprintf(stderr,"Creating thread\n");
  funcstr = (func_and_arg *)malloc(sizeof(func_and_arg));
  funcstr->func = dispatch_to_here;
  funcstr->arg  = arg;
  if (pthread_create(&pool->t_Pool[pool->nextThreadToCreate], NULL, 
		     (void *) dispatchfunc, (void *)funcstr) != 0) 
    {
      perror("Threadpool error");
      exit(10);
    }
  //fprintf(stderr,"Thread created\n");
  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
  pool->nextThreadToCreate = (pool->nextThreadToCreate+1) % pool->maxThreads;


  //  pthread_mutex_unlock(&pool->oneAtATime);
  // This is taken care of by the cleanup below
  pthread_cleanup_pop(1);
  pthread_setcanceltype(oldtype, NULL);
}

void destroy_threadpool(threadpool destroyme) 
{
  _threadpool *pool = (_threadpool *) destroyme;
  int oldtype;

  pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
  pthread_cleanup_push(pthread_mutex_unlock, (void *) &pool->oneAtATime);

  //pthread_mutex_t * mutexForNow=&pool->oneAtATime;
  pthread_mutex_lock(&pool->oneAtATime);
  
  // add your code here to kill a threadpool
  if(pool->t_Pool[pool->nextThreadToCreate] != 0)
    {
      int i,curindex;
      for(i=0,curindex=pool->nextThreadToCreate;
	  i<pool->maxThreads;
	  i++,curindex=curindex+1%pool->maxThreads)
	{
	  pthread_cancel(pool->t_Pool[curindex]);
	  pthread_join(pool->t_Pool[curindex],NULL);
	}
    }
  else
    {
      int curindex;
      for(curindex=0;
	  curindex <pool->nextThreadToCreate;
	  curindex++)
	{
	  pthread_cancel(pool->t_Pool[curindex]);
	  pthread_join(pool->t_Pool[curindex],NULL); 
	}
    }
  //free(pool->t_Pool);
  //pool->t_Pool = NULL;
  free(pool);
  pool = NULL;

  //  pthread_mutex_unlock(mutexForNow);
  //  mutex unlock handled by below function automatically
  pthread_cleanup_pop(1);
  pthread_setcanceltype(oldtype, NULL);
}

void dispatchfunc(void * arg)
{
  func_and_arg * funcstr = (func_and_arg *) arg;
  //fprintf(stderr,"Dispatching...\n");
  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
  (*(funcstr->func))
      (funcstr->arg);
  free(funcstr);
}
