/* global includs and libraries */
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netdb.h>
#include <time.h>
#include <signal.h>

/* global constants and defines */
#define MAX_CLIENT  8192
#define MAX_BUFFER  4096
#define NUM_PORT    0

/*
    On exit procedure.
*/
void exiterr(const char* szMsg)
{
    fprintf(stderr, "%s\n", szMsg);
    exit(-1);
}

/*
    Make the process a daemon.
*/
void Daemonize(void)
{
    pid_t pid, sid;

    /* already a daemon */
    if ( getppid() == 1 ) return;

    /* Fork off the parent process */
    pid = fork();
    if (pid < 0) {
        exit(1);
    }
    /* If we got a good PID, then we can exit the parent process. */
    if (pid > 0) {
        exit(0);
    }

    /* At this point we are executing as the child process */

    /* Change the file mode mask */
    umask(0);

    /* Create a new SID for the child process */
    sid = setsid();
    if (sid < 0) {
        exit(1);
    }

    /* Change the current working directory.  This prevents the current
       directory from being locked; hence not being able to remove it. */
    if ((chdir("/")) < 0) {
        exit(1);
    }

    /* Redirect standard files to /dev/null */
    freopen("/dev/null", "r", stdin);
    freopen("/dev/null", "w", stdout);
    freopen("/dev/null", "w", stderr);
}

/*
    Client Thread. Each client is executed on its own thread.
    Continueously read and serve client until it closes the connection.
*/
void* ServeClientThread(void* pArg)
{
    pthread_detach(pthread_self());

    /* This is the critical section object (statically allocated). */
    static pthread_mutex_t cs_mutex = PTHREAD_MUTEX_INITIALIZER;

    int paddrlen;
    int* pclientsd = (int*) pArg;
    int clientsd = pclientsd[0];
    char buffer[MAX_BUFFER];

    struct sockaddr_in clientsock;
    struct hostent *phe;
    struct in_addr addr;

    paddrlen = sizeof(clientsock);

    // get client port number
    if (getpeername(clientsd, (struct sockaddr*) &clientsock, &paddrlen) < 0)
        return NULL;

    // get client doman name
    if ((phe = gethostbyaddr((char*) &clientsock.sin_addr, sizeof(clientsock.sin_addr), AF_INET)) == NULL)
        return NULL;

    // get client ip address
    addr.s_addr = *(u_long*) phe->h_addr_list[0];
    printf("Client Connected: [%d][%s][%s][%d]\n", clientsd, phe->h_name, inet_ntoa(addr), ntohs(clientsock.sin_port));

    while (1)
    {
        memset(buffer, 0, sizeof(buffer));
        recv(clientsd, buffer, sizeof(buffer), 0);

        if (strlen(buffer) == 0)
            break;

        /* Enter the critical section -- other threads are locked out */
        pthread_mutex_lock( &cs_mutex );

        /* Do some thread-safe processing! */
        printf("%d:%s", clientsd, buffer);

        /*Leave the critical section -- other threads can now pthread_mutex_lock()  */
        pthread_mutex_unlock( &cs_mutex );
    }
    printf("Closed: [%d]\n", clientsd);
    close(clientsd);
    pthread_exit(NULL);
}

/*
    Main Driver Routine
*/
int main(int argc, char* argv[])
{
    int sd, err, clientsd, paddrlen;
    int paramlist[1];
    pthread_t tid;

    struct sockaddr_in localsock;
    struct sockaddr_in remotesock;

    // cancel certain signals
    signal(SIGINT,  SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGTERM, SIG_IGN);

    // setup local socket
    memset(&localsock, 0, sizeof(localsock));
    localsock.sin_family = AF_INET;
    localsock.sin_addr.s_addr = INADDR_ANY;
    localsock.sin_port = htons(NUM_PORT);

    // create a socket
    if ((sd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
        exiterr("Error: socket()");

    // bind to socket
    if (bind(sd, (struct sockaddr*) &localsock, sizeof(localsock)) < 0)
        exiterr("Error: bind()");

    paddrlen = (int) sizeof(remotesock);

    // get socket name
    if (getsockname(sd, (struct sockaddr*) &remotesock, &paddrlen) < 0)
        exiterr("Error: getsockname()");

    // listen on socket
    if (listen(sd, MAX_CLIENT) < 0)
        exiterr("Error: listen()");

    printf("TCP Multi-threaded Server online. Port number: %d\n", ntohs(remotesock.sin_port));

    while (1)
    {
        // acccept incomming client connection
        paramlist[0] = clientsd = accept(sd, (struct sockaddr*) &remotesock, &paddrlen);

        // serve each client with its own thread
        pthread_create(&tid, NULL, (void*) ServeClientThread, (void*) paramlist);

        // pthread_join(tid, NULL);
        // close(clientsd);
    }
    return 0;
}