/*
 * Copyright (C) 2007 Moritz Orbach <gnu@apfelboymchen.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program. If not, see http://www.gnu.org/licenses/
 *
 * compile with
 * gcc $CFLAGS -Wall -lpthread hellothreads.c             -o hellothreads
 *
 * or with
 * gcc -CFLAGS -Wall -lpthread hellothreads.c -g -DDEBUG  -o hellothreads-debug
 * for nice debug output
 *
 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>

/*
 * -DDEBUG to see debug output
 */
#ifdef DEBUG
#include <time.h>

char *that_was_quick    = "\033[32m";
char *mildly_annoying   = "\033[1;33m";
char *that_took_a_while = "\033[31m";
char *color_default     = "\033[0m";
#endif

/*
 * helper struct
 * the worker needs to know the semaphore *and* the character to print
 */
typedef struct s_hello {
        sem_t *signal;
        char *c;
} hello_t;


#ifdef DEBUG
char *get_color(unsigned int seconds)
{
        if (seconds < 5)
                return that_was_quick;
        else if (seconds < 8)
                return mildly_annoying;
        else
                return that_took_a_while;
}
#endif

/*
 * the thread
 */
void *hello_worker(void *arg)
{
        hello_t *helloctrl;
        int delay;

#ifdef DEBUG
        unsigned int time_start;
        time_start = time(NULL);
#endif

        helloctrl = (hello_t *)arg;
        delay = 1 + (int) (10.0 * (rand() / (RAND_MAX + 1.0)));

        /* dream about that nice character we are about to printf */
        sleep(delay);

        /*
         * sem_wait(3)
         *
         * sem_wait()  decrements  (locks) the semaphore pointed to by sem.  If
         * the semaphore's value is greater than zero, then the decrement pro-
         * ceeds, and the function returns, immediately.  If the semaphore
         * currently has the value zero, then  the  call  blocks  until  either  
         * it becomes possible to perform the decrement (i.e., the semaphore value
         * rises above zero), or a signal handler interrupts the call.
         *
         */
        /*
         * wait for the signal
         * - the character must be printed in the correct order
         * - printf is not thread safe
         */
        if (sem_wait(helloctrl->signal) != 0) {
                perror("sem_wait");
                pthread_exit(NULL);
        }

        /* work */
        printf("%c", *helloctrl->c);

#ifdef DEBUG
        unsigned int time_offset = time(NULL) - time_start;
        unsigned int time_idle   = time_offset - delay;
        char *color_delay  = get_color(delay);
        char *color_offset = get_color(time_offset);
        char *color_idle   = get_color(time_idle);
        fprintf(stderr, "\t(delay: %s%3d%ss\toffset %s%3d%ss\tidle: %s%2d%s)",
                        color_delay, delay, color_default,
                        color_offset, time_offset, color_default,
                        color_idle, time_idle, color_default
                        );
#endif

        pthread_exit(NULL);
}


int main(int argc, char *argv[])
{
        int r; /* result */
        char *message = "Hello, World!\n";

        unsigned int msglen = strlen(message);
        sem_t fifo[msglen];
        pthread_t hello_threads[msglen];
        hello_t helloctrl[msglen];

#ifdef DEBUG
        /* just to separate it from the compiler output */
        fprintf(stderr, "\n");
#endif

        /* don't buffer output */
        setvbuf(stdout, (char *)NULL, _IONBF, 0);

        /* initialize random number generator with time */
        srand(time (0));

        /* let's begin */
        int i;
        for (i = 0; i < msglen; i++) {

                /* create semaphore */
                r = sem_init(&fifo[i], 0, 0);
                if (r != 0) {
                        perror("Semaphore initialization failed");
                        exit(EXIT_FAILURE);
                }

                helloctrl[i].signal = &fifo[i];
                helloctrl[i].c      = &message[i];

                /* wie fork */
                r = pthread_create(&hello_threads[i], 0, hello_worker, (void *)&helloctrl[i]);
                if (r != 0) {
                        perror("Thread creation failed");
                        exit(EXIT_FAILURE);
                }

        }
        int numthreads = i;

        for (i = 0; i < numthreads; i++) {


                /*
                 * sem_post(3)
                 *
                 * sem_post() increments (unlocks) the semaphore pointed to. If
                 * the semaphore's value consequently becomes greater than
                 * zero, then another process or thread blocked in a
                 * sem_wait(3) call will be woken up and proceed to lock the
                 * semaphore.
                 */
                sem_post(helloctrl[i].signal);
                r = pthread_join(hello_threads[i], NULL);
                if (r != 0) {
                        perror("Thread join failed");
                        exit(EXIT_FAILURE);
                }
#ifdef DEBUG
                fprintf(stderr, "\thappy reunion with %d (%ld)\n", i, hello_threads[i]);
#endif
                sem_destroy(helloctrl[i].signal);
        }


        exit(EXIT_SUCCESS);
}