#include <stdio.h>
#include <errno.h>
#include <sys/time.h>

#include <sys/types.h>
#include <sys/wait.h>

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>

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

#define FICHERO   "/tmp"

void reusePort(int sock);
static void sig_usr(int);

int AcumuladorServe(int psd);
int MonitorServe(int psd);

int    SHM_ID = 0;
key_t  llave;
int    *tv_options;

int main (int argc, char *argv[]) {

    if (argc != 3) {
        if ( (argc == 2) && (strcmp(argv[1], "--help") == 0) ) {
            printf("Sintaxis:\n");
            printf("\%s <puerto_votacion> <puerto_monitor>\n\n", argv[0]);
            printf("Parametros:\n");
            printf("<puerto_votacion>     Puerto en que recibe conexiones de televisores\n");
            printf("<puerto_monitor>      Puerto en que recibe conexiones de monitores\n");
        }
        else {
            printf("Usage: acumulador <puerto_votacion> <puerto_monitor>\n");
            printf("Try 'acumulador --help' for more information\n");
        }
        exit(1);
    }

    int s, ss, pss;
    struct sockaddr_in server, monitor;
    struct sockaddr_in from;
    int fromlen;
    int length;
    pid_t pid;
    int status;

    if ( signal(SIGINT, sig_usr) == SIG_ERR ) {
        perror("can't catch SIGINT\n");
        exit(-1);
    }

    // Crear y enlazar zona de memoria compartida
    if ( (llave = ftok(FICHERO, getpid())) == (key_t) -1) {
        perror("ftok error");
        exit(-1);
    }

    if ( (SHM_ID = shmget(llave, 6*sizeof(int), IPC_CREAT | IPC_EXCL | 0777)) == -1) {
        perror("shmget error");
        exit(-1);
    }

    tv_options = (int *) shmat(SHM_ID, 0, 0);

    fd_set readfds, readfdsCopy;
    int n, i;
    int sockList[64], lastSock = 0;

    // Configuracion del servidor de televisores
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    server.sin_port = htons(atoi(argv[1]));
    
    s = socket(AF_INET, SOCK_STREAM, 0);
    reusePort(s);

    if ( bind(s, (struct sockaddr *) &server, sizeof(server)) ) {
        close(s);
        perror("binding name to stream socket");
        exit(-1);
    }

    length = sizeof(server);
    if ( getsockname(s, (struct sockaddr *) &server, &length) ) {
        perror("getting socket name");
        exit(0);
    }

    // Configuracion del servidor de monitores
    monitor.sin_family = AF_INET;
    monitor.sin_addr.s_addr = htonl(INADDR_ANY);
    monitor.sin_port = htons(atoi(argv[2]));

    ss = socket(AF_INET, SOCK_STREAM, 0);

    if ( bind(ss, (struct sockaddr *) &monitor, sizeof(monitor)) ) {
        close(ss);
        perror("binding name to stream socket");
        exit(-1);
    }

    length = sizeof(monitor);
    if ( getsockname(ss, (struct sockaddr *) &monitor, &length) ) {
        perror("getting socket name");
        exit(0);
    }

    printf("Server acumulador iniciado\n");
    printf("Puerto de votacion: %d\n", ntohs(server.sin_port));
    printf("Server monitor iniciado\n");
    printf("Puerto de monitoreo: %d\n", ntohs(monitor.sin_port));

    // Creacion del proceso hijo
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }

    // Proceso hijo
    if (pid == 0) {
        listen(ss,5);
        fromlen = sizeof(from);

        // Servidor monitor concurrente escuchando por conexiones entrantes
        for (;;) {
            pss = accept(ss, (struct sockaddr *) &from, &fromlen);
            if ( pss < 0 )
                perror("accepting new conection");
            MonitorServe(pss);
            close(pss);
        }
    }
    else {
        // Proceso padre
        listen(s,5);
        fromlen = sizeof(from);
        FD_ZERO(&readfdsCopy);
        FD_SET(s, &readfdsCopy);

        // Servidor televisor concurrente escuchando por conexiones entrantes
        for (;;) {
            memcpy(&readfds, &readfdsCopy, sizeof(fd_set));
            n = select(FD_SETSIZE, &readfds, (fd_set *) 0, (fd_set *) 0, NULL);

            if (n > 0) {
                if (FD_ISSET(s, &readfds)) {
                    printf("Aceptando nueva conexion de televisor...\n");
                    sockList[lastSock++] = accept(s, (struct sockaddr *) &from, &fromlen);
                    FD_SET(sockList[lastSock-1], &readfdsCopy);
                }
                for (i = 0; i < lastSock; i++) {
                    if (FD_ISSET(sockList[i], &readfds))
                        if (AcumuladorServe(sockList[i]) < 0) {
                            FD_CLR(sockList[i], &readfdsCopy);
                            close(sockList[i]);
                            sockList[i] = sockList[--lastSock];
                            i--;
                        }
                }
            }
        }
    }

    waitpid(pid, &status, 0);
}

// Funcion que va almacenando las opciones recibidas de cada televisor

int AcumuladorServe (int psd) {
    char buf[32];
    int rc;

    for (;;) {
        if ( (rc = read(psd, buf, sizeof(buf))) < 0 )
            perror("receiving stream message");
        if ( rc > 0 ) {
            buf[rc] = '\0';
            printf("Opcion marcada por el televidente: %s\n", buf);
            tv_options[atoi(buf)]++;
            return(1);
        }
        else {
            printf("Desconectando...\n");
            return(-1);
        }
    }
}

// Funcion que envia los datos de las opciones acumuladas en el servidor

int MonitorServe (int psd) {
    char buf[32];

    sprintf(buf, "%d %d %d %d %d %d", tv_options[0], tv_options[1], tv_options[2], tv_options[3], 
tv_options[4], tv_options[5]);

    printf("... Monitor conectado...\n");
    printf("Enviando: %s\n", buf);

    if ( send(psd, buf, sizeof(buf), 0) < 0 )
        perror("sending stream message");
    return(1);
}

void reusePort(int sock) {
    int one = 1;

    if ( setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)) == -1 ) {
        printf("error in setsockopt, SO_REUSEPORT \n");
        exit(-1);
    }
}

// Funcion para liberar la zona de memoria compartida al "matar" el programa

static void sig_usr(int signal) {
    if ( signal == SIGINT ) {
        if ( (shmctl(SHM_ID, IPC_RMID, NULL)) == -1 ) {
            perror("shmctl error");
            exit(-1);
        }

        kill(0,9);
    }
}
