/*
 * $Id: pkthreads.c,v 1.12 2000/12/04 03:37:03 alderman Exp $
 *
  This code uses threads and lets the system schedule them as best
  it can.  The result is a maximum of 14 concurrent streams with
  SCHED_RR and PTHREAD_SCOPE_USER, 13 with SCHED_FIFO, 12 with
  PTHREAD_SCOPE_SYSTEM
 */

#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>
#include <time.h>
#include <math.h>

#include "global.h"
#include "options.h"
#include "timer.h"


struct streamdata {             /* data structure for each stream / thread */
        pthread_t t_tid;        /* the thread id */
        int fileno;             /* The filenumber for this stream */
        int fd;                 /* The file's file descriptor */
        char buf[NUMBYTES+1];   /* The thread's buffer. */
        int errorcnt;           /* The number of errors from this stream */
} streamdata[MAX_STREAMS];

/* Size of a file in bytes, From stat.  Assumes all files are the same size. */
int how_big;

/* A count of the number of errors in each round.  */
int round_errors[MAX_NUM_ROUNDS];

pthread_cond_t sched_go;
pthread_cond_t sched_alldone;
pthread_mutex_t sched_mutex;

/* TODO: create mutex for timer.c */

/* The following variables are used for stream synchronization. */
int current_round = -1; 
/* The number of streams completed in the current round */
int num_completed = 0;          

pthread_t sched_tid;

void *readStream(void *fnp);
void *scheduler(void *idp);
int readMeg(char *buf, int streamno);

struct timeval start_tv;

int main(int argc, char *argv[]) {
        int i = 0;
	int rv, sum;

        pthread_attr_t attr;

        struct stat sbuf;

        /* see options.c */
        extern int num_streams, block_size;

        /* see options.c */
        parseArgs(argc, argv);
        if(validateParams() != VALIDATION_SUCCESS) {
                printUsage();
                exit(1);
        }

	/* Initialization */
        rv = stat("bf/00", &sbuf);
	how_big = sbuf.st_size / block_size;

        /* Initialize the per round error counter. */
        for(i = 0; i < MAX_NUM_ROUNDS; i++) {
                round_errors[i] = 0;
        }

	rv = pthread_attr_init(&attr);
        if(rv) {
                perror("Can't init pthread attributes.");
                exit(1);
        }

	/* TODO: is this supported on Solaris8? */
	rv = pthread_attr_setschedpolicy(&attr, SCHED_RR); 
	// rv = pthread_attr_setschedpolicy(&attr, SCHED_FIFO); 
	if(rv) {
		perror("Can't set SCHED_RR policy");
	}

 	rv = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
	if(rv) {
                perror("Can't set thread scope to system-wide scope.");
                exit(1);
        }
		
        pthread_cond_init(&sched_go, NULL);
        pthread_mutex_init(&sched_mutex, NULL);
        pthread_cond_init(&sched_alldone, NULL);

        for (i = 0; i < num_streams; i++) {
                streamdata[i].fileno = i;
                // streamdata[i].done = 0;
#ifdef DEBUGVERBOSE
                printf("Creating thread: %d\n",i);
#endif
                rv = pthread_create(&(streamdata[i].t_tid), &attr, readStream, 
                                        (void *)&(streamdata[i].fileno));
                if(rv != 0) {
			switch(rv) {
			case NULL:
				printf("Not sane.\n");
				break;
			case ENOMEM:
				printf("Not enough memory.\n");
				break;
			case EINVAL:
				printf("Invalid attr.\n");
				break;
			case EPERM:
				printf("Invalid permissions.\n");
				break;
			default:
				printf("Other: %d.\n", rv);
			}
                        perror("Can't create thread");
                        exit(1);
                }
        }
	
        rv = pthread_create(&sched_tid, &attr, scheduler, NULL);
	if(rv != 0) {
		perror("Can't create scheduler thread");
		exit(1);
	}

        for(i = 0; i < num_streams; i++) {
                printf("\nWaiting for thread: %d\n",i);
                pthread_join(streamdata[i].t_tid, NULL);
        }

	pthread_join(sched_tid, NULL);

        /* Process error information */
        sum = 0;
        for (i = 0; i < how_big; i++) {
                printf("Errors in round %d: %d\n", i, round_errors[i]);
                sum += round_errors[i];
        }
        printf("Total round errors: %d\n", sum);
        sum = 0;
        for (i = 0; i < num_streams; i++) {
                printf("Errors in stream %d: %d\n", i, streamdata[i].errorcnt);
                sum += streamdata[i].errorcnt;
        }
        printf("Total round errors: %d\n", sum);
                
	exit(0);
}

void *readStream(void *fnp) {
        int fd, i, rv;
        char *filename;
        char *buf;
	int ready = 0;

	extern int num_streams;

        int fn = *(int *)fnp;

        buf = streamdata[fn].buf;

#ifdef DEBUGVERBOSE
        printf("Reading stream: %d\n", fn);
#endif

        memset(buf, '\0', NUMBYTES);

        filename = malloc(256);
        if(filename == NULL) {
                fprintf(stderr, "Can't malloc for filename.\n");
                exit(1);
        }

        rv = snprintf(filename, 255, "bf/%2.2d",fn);
        if(rv != 5) {
                fprintf(stderr, "Can't snprintf for filename.\n");
        }

        fd = open(filename, O_RDONLY, NULL);
        if(fd < 1) {
                printf("error: fd %d on %s\n",fd, filename);
        }
	streamdata[fn].fd=fd;

        streamdata[fn].errorcnt = 0;

        for (i = 0; i < how_big; i++) {
                pthread_mutex_lock(&sched_mutex);
		ready = (i <= current_round);
		pthread_mutex_unlock(&sched_mutex);
                while(!ready) {
			pthread_mutex_lock(&sched_mutex);
                        pthread_cond_wait(&sched_go,&sched_mutex);
			ready = (i <= current_round);
			pthread_mutex_unlock(&sched_mutex);
                }
#ifdef DEBUGVERBOSE
                printf("Reading chunk %d in stream %d.\n",i, fn);
#endif
                if(readMeg(buf, fn) < 0) {
                        fprintf(stderr, "Exiting with i = %d.\n", i);
                        exit(1);
                }
#ifdef DEBUGVERBOSE
                fprintf (stderr, "Stream %d block %d completed.\n", fn, i);
#endif
                printf(".");
	
		pthread_mutex_lock(&sched_mutex);
		/* If i != current_round, we're behind. */
		if(i == current_round){
			++num_completed;
		} else {
                        streamdata[fn].errorcnt++;
			printf("\nError: stream %d, round %d.\n", fn, i);
		}
                pthread_mutex_unlock(&sched_mutex);
        }
        pthread_mutex_lock(&sched_mutex);
	if(current_round == how_big - 1 && num_completed == num_streams) {
		pthread_cond_signal(&sched_alldone);
	}
        pthread_mutex_unlock(&sched_mutex);
	return(0);
}

void *scheduler(void *idp) {
        // int *my_id = idp;
        int i, errctr = 0;
	int sleep_time = 0;
	// struct timespec done_time;
	// int tmp_ntime, tmp_time;

        extern int block_size, num_streams, bit_rate;

        init_timer(block_size, bit_rate);

        for(i = 0; i < how_big; i++) {
#ifdef DEBUGVERBOSE
		fprintf(stderr,"Starting loop.\n");
#endif
                pthread_mutex_lock(&sched_mutex);
#ifdef DEBUGVERBOSE
		fprintf(stderr,"Got lock.\n");
#endif
		++current_round; /* Should == i */
                num_completed = 0;
		pthread_cond_broadcast(&sched_go);
#ifdef DEBUGVERBOSE
		fprintf(stderr,"Sent broadcast.\n");
#endif
		pthread_mutex_unlock(&sched_mutex);
#ifdef DEBUGVERBOSE
		fprintf(stderr,"Unlocked mutex, about to sleep.\n");
#endif
		sleep_time = sleep_end_quantum_increment();
		pthread_mutex_lock(&sched_mutex);
		/* TODO: fix done_time 
		clock_gettime(CLOCK_REALTIME, &done_time);
#ifdef DEBUG
		printf("Now: %ld %ld\n",done_time.tv_sec,done_time.tv_nsec);
#endif

		tmp_time = floor(sleep_time/1000);
		tmp_ntime = (sleep_time - tmp_time*1000)*1000000;

		done_time.tv_sec += tmp_time;
		done_time.tv_nsec += tmp_ntime;

		if(tmp_ntime > 1000000000) {
			done_time.tv_sec++;
			done_time.tv_nsec -= 1000000000;
		}
#ifdef DEBUG
		printf("Waiting til: %ld %ld\n",done_time.tv_sec,done_time.tv_nsec);
#endif
		rv = pthread_cond_timedwait(&sched_alldone, &sched_mutex, 
			&done_time);
#ifdef DEBUG
		printf("Exited pthread_cond_timedwait with %d: %d completed.\n",
			rv, num_completed);
#endif
		*/
		if(num_completed < num_streams) {
			++errctr;
                        round_errors[i] = num_streams - num_completed;
		}
                printf("Period %d, %d errors\n", i, errctr);
                pthread_mutex_unlock(&sched_mutex);
        }
        return(0);
}

int readMeg(char *buf, int streamno) {
        int curByte = 0;
        int rv;
        int fd;

	extern int block_size;

        fd = streamdata[streamno].fd;

        while(1) {
#ifdef DEBUGVERBOSE
		fprintf(stderr, "Starting read, %d\n",streamno);
#endif
                rv = read(fd, buf, block_size - curByte);
#ifdef DEBUGVERBOSE
		fprintf(stderr, "Finished read, %d\n", streamno);
#endif
                curByte += rv;
                if(rv > 0) {
#ifdef DEBUGVERBOSE
                        fprintf(stderr, "Read %d bytes.\n",rv);
#endif
                        if (curByte < block_size) {
                                continue;
                        } else {
                                return curByte;
                        }
                } else if (rv == 0) {
                        fprintf(stderr, "Read Zero bytes.\n");
                        return rv;
                } else {
                        fprintf(stderr, "Read returned negative: %d.\n", rv);
                        return rv;
                }
        }
        return rv;
}

