/*
 * function handle_requests_loop(): infinite loop of requests handling
 * algorithm: forever, if there are requests to handle, take the first
 *            and handle it. Then wait on the given condition variable,
 *            and when it is signaled, re-do the loop.
 *            increases number of pending requests by one.
 * input:     id of thread, for printing purposes.
 * output:    none.
 */
void*
handle_requests_loop(void* thread_params)
{
    int rc;	                    /* return code of pthreads functions.  */
    struct request* a_request;      /* pointer to a request.               */
    struct handler_thread_params *data;
 				    /* hadler thread's parameters */

    /* sanity check -make sure data isn't NULL */
    data = (struct handler_thread_params*)thread_params;
    assert(data);

    printf("Starting thread '%d'\n", data->thread_id);
    fflush(stdout);

    /* set my cancel state to 'enabled', and cancel type to 'defered'. */
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);

    /* set thread cleanup handler */
    pthread_cleanup_push(cleanup_free_mutex, (void*)data->request_mutex);

    /* lock the mutex, to access the requests list exclusively. */
    rc = pthread_mutex_lock(data->request_mutex);

#ifdef DEBUG
    printf("thread '%d' after pthread_mutex_lock\n", data->thread_id);
    fflush(stdout);
#endif /* DEBUG */

    /* do forever.... */
    while (1) {
        int num_requests = get_requests_number(data->requests);

#ifdef DEBUG
    	printf("thread '%d', num_requests =  %d\n",
	       data->thread_id, num_requests);
    	fflush(stdout);
#endif /* DEBUG */
	if (num_requests > 0) { /* a request is pending */
	    a_request = get_request(data->requests);
	    if (a_request) { /* got a request - handle it and free it */
    		/* unlock mutex - so other threads would be able to handle */
		/* other reqeusts waiting in the queue paralelly.          */
    		rc = pthread_mutex_unlock(data->request_mutex);
		handle_request(a_request, data->thread_id);
		free(a_request);
    		/* and lock the mutex again. */
    		rc = pthread_mutex_lock(data->request_mutex);
	    }
	}
	else {
	    /* the thread checks the flag before waiting            */
	    /* on the condition variable.                           */
	    /* if no new requests are going to be generated, exit.  */
	    if (done_creating_requests) {
                pthread_mutex_unlock(data->request_mutex);
		printf("thread '%d' exiting\n", data->thread_id);
		fflush(stdout);
		pthread_exit(NULL);
	    }
	    /* wait for a request to arrive. note the mutex will be */
	    /* unlocked here, thus allowing other threads access to */
	    /* requests list.                                       */
#ifdef DEBUG
    	    printf("thread '%d' before pthread_cond_wait\n", data->thread_id);
    	    fflush(stdout);
#endif /* DEBUG */
	    rc = pthread_cond_wait(data->got_request, data->request_mutex);
	    /* and after we return from pthread_cond_wait, the mutex  */
	    /* is locked again, so we don't need to lock it ourselves */
#ifdef DEBUG
    	    printf("thread '%d' after pthread_cond_wait\n", data->thread_id);
    	    fflush(stdout);
#endif /* DEBUG */
	}
    }

    /* remove thread cleanup handler. never reached, but we must use */
    /* it here, according to pthread_cleanup_push's manual page.     */
    pthread_cleanup_pop(0);
}
/* like any C program, program's execution begins in main */
int
main(int argc, char* argv[])
{
    int        i;                                /* loop counter          */
    struct timespec delay;			 /* used for wasting time */
    struct requests_queue* requests = NULL;  /* pointer to requests queue */
    struct handler_threads_pool* handler_threads = NULL;
					       /* list of handler threads */

    /* create the requests queue */
    requests = init_requests_queue(&request_mutex, &got_request);
    assert(requests);

    /* create the handler threads list */
    handler_threads =
	init_handler_threads_pool(&request_mutex, &got_request, requests);
    assert(handler_threads);

    /* create the request-handling threads */
    for (i=0; i<NUM_HANDLER_THREADS; i++) {
	add_handler_thread(handler_threads);
    }

    /* run a loop that generates requests */
    for (i=0; i<600; i++) {
	int num_requests; // number of requests waiting to be handled.
	int num_threads;  // number of active handler threads.

	add_request(requests, i);

	num_requests = get_requests_number(requests);
	num_threads = get_handler_threads_number(handler_threads);

	/* if there are too many requests on the queue, spawn new threads */
	/* if there are few requests and too many handler threads, cancel */
	/* a handler thread.          					  */
	if (num_requests > HIGH_REQUESTS_WATERMARK &&
	    num_threads < MAX_NUM_HANDLER_THREADS) {
		printf("main: adding thread: '%d' requests, '%d' threads\n",
		       num_requests, num_threads);
		add_handler_thread(handler_threads);
	}
	if (num_requests < LOW_REQUESTS_WATERMARK &&
		 num_threads > NUM_HANDLER_THREADS) {
	    printf("main: deleting thread: '%d' requests, '%d' threads\n",
		   num_requests, num_threads);
	    delete_handler_thread(handler_threads);
	}

	/* pause execution for a little bit, to allow      */
	/* other threads to run and handle some requests.  */
	if (rand() > 3*(RAND_MAX/4)) { /* this is done about 25% of the time */
	    delay.tv_sec = 0;
	    delay.tv_nsec = 1;
	    nanosleep(&delay, NULL);
	}
    }
    /* modify the flag to tell the handler threads no   */
    /* new requests will be generated.                  */
    {
        int rc;

        rc = pthread_mutex_lock(&request_mutex);
        done_creating_requests = 1;
        rc = pthread_cond_broadcast(&got_request);
        rc = pthread_mutex_unlock(&request_mutex);
    }

    /* cleanup */
    delete_handler_threads_pool(handler_threads);
    delete_requests_queue(requests);
    
    printf("Glory,  we are done.\n");

    return 0;
}