// Experiment 17: TCP forking (multi-client) program
// SERVER side - handles each client in a separate child process via fork()
// Compile: gcc 17_tcp_fork_multiclient_server.c -o forkserver
// Run    : ./forkserver
// Then run multiple clients (17_tcp_fork_client.c) in different terminals.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 3030
#define BUF  1024

// Reap zombie child processes
void sigchld_handler(int s) {
    (void)s;
    while (waitpid(-1, NULL, WNOHANG) > 0);
}

void handle_client(int client_fd) {
    char buffer[BUF];
    while (1) {
        memset(buffer, 0, BUF);
        int n = read(client_fd, buffer, BUF);
        if (n <= 0) break;

        buffer[strcspn(buffer, "\n")] = '\0';
        printf("[pid %d] Received: %s\n", getpid(), buffer);

        if (strncmp(buffer, "exit", 4) == 0) break;

        // Echo back in uppercase
        char reply[BUF];
        snprintf(reply, BUF, "Server[%d] echo: ", getpid());
        int p = strlen(reply);
        for (int i = 0; buffer[i] && p < BUF - 2; i++, p++) {
            if (buffer[i] >= 'a' && buffer[i] <= 'z')
                reply[p] = buffer[i] - 32;
            else
                reply[p] = buffer[i];
        }
        reply[p++] = '\n';
        reply[p]   = '\0';

        send(client_fd, reply, strlen(reply), 0);
    }
    close(client_fd);
}

int main() {
    int server_fd, client_fd;
    struct sockaddr_in addr;
    socklen_t addrlen = sizeof(addr);

    // Auto-reap children
    struct sigaction sa = {0};
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    sigaction(SIGCHLD, &sa, NULL);

    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) { perror("socket"); exit(1); }

    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    addr.sin_family      = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port        = htons(PORT);

    if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("bind"); exit(1);
    }
    listen(server_fd, 5);
    printf("Forking server listening on port %d (Ctrl+C to stop)...\n", PORT);

    while (1) {
        client_fd = accept(server_fd, (struct sockaddr*)&addr, &addrlen);
        if (client_fd < 0) continue;
        printf("New client connected (fd=%d)\n", client_fd);

        pid_t pid = fork();
        if (pid == 0) {              // child
            close(server_fd);
            handle_client(client_fd);
            printf("[pid %d] client gone, exiting.\n", getpid());
            exit(0);
        }
        // parent
        close(client_fd);
    }

    close(server_fd);
    return 0;
}
