/* 
 * 2016 Mc128k
 */

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>


#define DEFAULT_PORT 1880
#define MAX_BUF 256
#define MAX_CHILD 10
#define MAX_TIMEWAIT 5

// Used to disable signal actions with child processes
int sig_active = 0;

// Active socket, needed by handler too
int tmp_sock;

// Child processes pids
pid_t chld_pids[MAX_CHILD];

// Signal handler
void sighandler(int signal) {
    printf("Killing all subprocesses\n");

    // Kill all subprocesses
    if (sig_active) {

        // Notify client
        char buf[] = "Processi figli terminati\n";
        write(tmp_sock, buf, sizeof(buf));
        close(tmp_sock);
        int i;
        for (i = 0; chld_pids[i] > 0; i++) {
            kill(chld_pids[i], SIGTERM);
        }
    }
}


int main(int argc, const char **argv) {
    int sock, port;
    struct sockaddr_in srv_addr, cli_addr;
    socklen_t cli_len;
    char buf[MAX_BUF];

    port = DEFAULT_PORT;

    // Signal handler for child processes
    sigset_t mask;
    struct sigaction act;
    sigemptyset(&act.sa_mask);
    act.sa_handler = sighandler;
    act.sa_flags = 0;         
    if (sigaction(SIGUSR2, &act, NULL) < 0) {
        perror("sigaction");
        return -2;
    }

    // Create socket
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket");
        return -1;
    }
    int opt = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // Bind
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_addr.s_addr = INADDR_ANY;
    srv_addr.sin_port = htons(port);
    if (bind(sock, (struct sockaddr *) &srv_addr, sizeof(srv_addr)) < 0) {
        perror("bind");
        return -2;
    }

    // Listen
    listen(sock, 5);
    printf("Listening on port %d\n", port);

    while (1) {
        /************/
        /** SERVER **/
        /************/
        tmp_sock = accept(sock, (struct sockaddr *) &cli_addr, &cli_len);
        if (tmp_sock < 0) {
            perror("accept");
            return -2;
        }
        
        if (fork() == 0) {
            /************/
            /** WORKER **/
            /************/
            
            // If SIGUSR2 is received, kill child proc
            sig_active = 1;

            printf("Worker %d spawned\n", getpid());
            close(sock);
            
            // Read number
            if (read(tmp_sock, buf, sizeof(buf)) < 0) {
                return 1;
            }
            int max = atoi(buf);
            printf("Received %d\n", max);
            if (max < 2 || max > MAX_CHILD) {
                // Invalid input
                printf("Invalid input received\n");
                char err[] = "Invalid input\n";
                write(tmp_sock, err, sizeof(err));
                close(tmp_sock);
                return 0;
            }

            // Create child processes
            int i;
            for (i = 0; i < max; i++) {
                pid_t pid;
                if ((pid = fork()) == 0) {
                    // CHILD
                    sig_active = 0;

                    printf("Child %d spawned\n", getpid());

                    // Wait random time
                    srandom(time(NULL) + getpid());
                    int t = random();
                    sleep(t % MAX_TIMEWAIT + 1);
    
                    return 0;
                }
                chld_pids[i] = pid;
            }
            // Terminate
            chld_pids[i] = 0;

            // Activate signal handler
            sig_active = 1;

            // Wait first child, return pid
            int ret;
            int chld_pid = wait(&ret);
            if (chld_pid > 0) {
                snprintf(buf, MAX_BUF, "First child terminated: %d\n", chld_pid);
            } else {
                snprintf(buf, MAX_BUF, "Internal error\n");
            }
            write(tmp_sock, buf, strlen(buf));
            close(tmp_sock);

            // Wait for other child proc
            while (wait(&ret) > 0) {}
            printf("All child processes terminated\n");

            return 0;
        }
        
        // Wait previous child to remove zombie
        close(tmp_sock);
        int status;
        waitpid(-1, &status, WNOHANG);
    }
    return 0;
}
