/*
 * Ouroboros - Copyright (C) 2016
 *
 * A simple CBR generator
 *
 *    Dimitri Staessens <dimitri.staessens@intec.ugent.be>
 *    Sander Vrijders   <sander.vrijders@intec.ugent.be>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <ouroboros/dev.h>
#include <ouroboros/time_utils.h>
#include <ouroboros/fcntl.h>

#include <stdbool.h>

#ifdef __FreeBSD__
#define __XSI_VISIBLE 500
#endif

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

#define THREADS_SIZE 10

pthread_t       listen_thread;
pthread_t       threads[THREADS_SIZE];
int             fds[THREADS_SIZE];
int             fds_count = 0;
int             fds_index = 0;
pthread_mutex_t fds_lock;
pthread_cond_t  fds_signal;

static void shutdown_server(int signo, siginfo_t * info, void * c)
{
        (void) info;
        (void) c;

        switch(signo) {
        case SIGINT:
        case SIGTERM:
        case SIGHUP:
                pthread_cancel(listen_thread);
        default:
                return;
        }
}

static void handle_flow(int fd)
{
        int count = 0;
        char buf[BUF_SIZE];

        struct timespec now;
        struct timespec alive;
        struct timespec intv = {server_settings.interval, 0};

        struct timespec iv_start;
        struct timespec iv_end;

        bool stop = false;

        long sdus            = 0;
        long sdus_intv       = 0;
        long bytes_read      = 0;
        long bytes_read_intv = 0;


        clock_gettime(CLOCK_REALTIME, &iv_start);
        alive = iv_start;
        ts_add(&iv_start, &intv, &iv_end);

        flow_set_flags(fd, FLOW_O_NONBLOCK);

        while (!stop) {
                clock_gettime(CLOCK_REALTIME, &now);

                count = flow_read(fd, buf, BUF_SIZE);

                if (count > 0) {
                        clock_gettime(CLOCK_REALTIME, &alive);
                        sdus++;
                        bytes_read += count;
                }

                if (ts_diff_us(&alive, &now)
                    > server_settings.timeout * MILLION) {
                        printf("Test on flow %d timed out\n", fd);
                        stop = true;
                }

                if (stop || ts_diff_ms(&now, &iv_end) < 0) {
                        long us = ts_diff_us(&iv_start, &now);
                        printf("Flow %4d: %9ld SDUs (%12ld bytes) in %9ld ms"
                               " => %9.4f p/s, %9.4f Mb/s\n",
                               fd,
                               sdus - sdus_intv,
                               bytes_read - bytes_read_intv,
                               us / 1000,
                               ((sdus - sdus_intv) / (double) us) * MILLION,
                               8 * ((bytes_read - bytes_read_intv)
                                    / (double)(us)));
                        iv_start = iv_end;
                        sdus_intv = sdus;
                        bytes_read_intv = bytes_read;
                        ts_add(&iv_start, &intv, &iv_end);
                }
        }

        flow_dealloc(fd);
}

static void * worker(void * o)
{
        int cli_fd;

        (void) o;

        while (true) {
                pthread_mutex_lock(&fds_lock);
                pthread_cleanup_push((void(*)(void *)) pthread_mutex_unlock,
                                     (void *) &fds_lock);
                while (fds[fds_index] == -1)
                        pthread_cond_wait(&fds_signal, &fds_lock);

                cli_fd = fds[fds_index];
                fds[fds_index] = -1;

                pthread_cleanup_pop(true);

                handle_flow(cli_fd);

                pthread_mutex_lock(&fds_lock);
                fds_count--;
                pthread_mutex_unlock(&fds_lock);
        }

        return 0;
}

static void * listener(void * o)
{
        int client_fd = 0;
        int response = 0;
        qosspec_t qs;

        (void) o;

        printf("Server started, interval is %ld s, timeout is %ld s.\n",
               server_settings.interval, server_settings.timeout);

        while (true) {
                client_fd = flow_accept(NULL, &qs);
                if (client_fd < 0) {
                        printf("Failed to accept flow.\n");
                        break;
                }

                printf("New flow.\n");

                pthread_mutex_lock(&fds_lock);

                response = (fds_count < THREADS_SIZE) ? 0 : -1;

                if (flow_alloc_resp(client_fd, response)) {
                        printf("Failed to give an allocate response.\n");
                        flow_dealloc(client_fd);
                        pthread_mutex_unlock(&fds_lock);
                        continue;
                }

                if (response) {
                        printf("Can't accept any more flows, denying.\n");
                        continue;
                }

                fds_count++;
                fds_index = (fds_index + 1) % THREADS_SIZE;
                fds[fds_index] = client_fd;

                pthread_mutex_unlock(&fds_lock);
                pthread_cond_signal(&fds_signal);
        }

        return 0;
}

int server_main(void)
{
        struct sigaction sig_act;
        int i;

        memset(&sig_act, 0, sizeof sig_act);
        sig_act.sa_sigaction = &shutdown_server;
        sig_act.sa_flags = 0;

        for (i = 0; i < THREADS_SIZE; i++)
                fds[i] = -1;

        if (sigaction(SIGINT,  &sig_act, NULL) ||
            sigaction(SIGTERM, &sig_act, NULL) ||
            sigaction(SIGHUP,  &sig_act, NULL) ||
            sigaction(SIGPIPE, &sig_act, NULL)) {
                printf("Failed to install sighandler.\n");
                return -1;
        }

        if (pthread_mutex_init(&fds_lock, NULL)) {
                printf("Failed to init mutex.\n");
                exit(EXIT_FAILURE);
        }

        if (pthread_cond_init(&fds_signal, NULL)) {
                printf("Failed to init cond.\n");
                return -1;
        }

        for (i = 0; i < THREADS_SIZE; i++)
                pthread_create(&threads[i], NULL, worker, NULL);

        pthread_create(&listen_thread, NULL, listener, NULL);

        pthread_join(listen_thread, NULL);

        for (i = 0; i < THREADS_SIZE; i++)
                pthread_cancel(threads[i]);

        for (i = 0; i < THREADS_SIZE; i++)
                pthread_join(threads[i], NULL);

        return 0;
}