/*
 *  socket_handling.h
 *
 */

#include <ctype.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>       // for memset
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>

#define DATA_SIZE  1024               // maximum message size
char outgoing_data[DATA_SIZE];        // data to be sent out along the socket server (e.g., to Python)
char incoming_data[DATA_SIZE];        // data coming in from our socket's client (such as a Python process)

// int server_control is a global in main.cpp

void *start_server(void *not_used)
{
    unsigned int sockfd, newsockfd, clilen;    // network variables
    struct sockaddr_in serv_addr, cli_addr;    // network configuration
    int i, n, portno = 2000;                   // helpful indices and the starting port number to try
    int number_of_ports_to_try = 42;          
    
    memset(outgoing_data,0,DATA_SIZE);         // clear out the outgoing network message buffer
    memset(incoming_data,0,DATA_SIZE);         // clear out the incoming network message buffer
    memset((char *) &serv_addr, 0, 
           sizeof(serv_addr));                 // clear out serv_addr
    
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) 
    {
        perror("ERROR opening socket");
        printf("Server thread quitting.\n");
        pthread_exit(NULL);                    // allows other threads to continue running...
    }
    
    serv_addr.sin_family = AF_INET;            // parameters to bind to port
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);
    
    bool socket_bind_success = false;
    for ( i=0 ; i<number_of_ports_to_try ; ++i) // try different ports until one is found...
    {
        serv_addr.sin_port = htons (portno);
        if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) 
        {
			portno += 1;                      // try the next value...
            printf ("Trying port #%d ", (portno));
            perror ("ERROR!"); 
        }
        else
        {  
            socket_bind_success = true;
            break;
        }
    }
    
    if (!socket_bind_success)                  // failed on every port tried
    {
        printf("No ports succeeded. Server thread quitting.\n");
        pthread_exit(NULL);                    // allows other threads to continue running...
    }
    
    
    printf("Server listening on port %d.\n", portno);
    listen(sockfd,1);          // no need to wait for more than 1 connection!
    clilen = sizeof(cli_addr); // needed for the call to accept
    
    /*
     * this loop allows it to connect to clients repeatedly via the call to accept
     */
    while (true) // in case the client disconnects, we check again!
    {
        
        /*
         * this next call (to accept) blocks
         */
        newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);  // waiting...
        if (newsockfd < 0)
        {
            perror("ERROR on accept");
            break;
        }
        printf("Server accepted a connection... .\n");
        
        /*
         * conversation with this client...
         */
        while (true) // in case the main thread shuts the server down
        {
            /*
             * read in the message
             */
            n = read(newsockfd,incoming_data,DATA_SIZE-1);   // trailing zero
            
            if (n < 0) 
            {
                printf("Error reading from socket, n = %d\n", n);
                break;  // allows a restart of the connection...
            }
            else if (n > DATA_SIZE)
            {
                incoming_data[DATA_SIZE-1] = 0;   // make sure the string-ending zero is present
            }
            else
            {
                incoming_data[n] = 0;   // make sure the string-ending zero is present
            }
            
            // local printing of the client's message...
            printf ("I read the message: %s\n",incoming_data);
            
            // process the client's message
            if (strncmp ("quit", incoming_data, strlen("quit")) == 0)
            {
                break; // allows a restart of the connection...
            }
            else if (strncmp ("quit server", incoming_data, strlen("quit server")) == 0)
            {
                break; // should cleanly quit -- look up how to do this!!
            }
            
            // reply to client's message
            /*
             * this is blocking communication on the client side - make sure to reply once for each message!!
             */
            if (strncmp ("answer", incoming_data, strlen("answer")) == 0)
            {
                snprintf(outgoing_data,DATA_SIZE-100,"The answer is %d.", 42 );
                n = write(newsockfd, outgoing_data, strlen(outgoing_data));
            }
            
            else  // messages not understood get an echo reply
            {
                snprintf(outgoing_data,DATA_SIZE-100,"Echo -> %s.", incoming_data );
                n = write(newsockfd, outgoing_data, strlen(outgoing_data));
            }
            
            // check for network-write errors (e.g., the client dropped the connection)
            if (n < 0) 
            {
                printf("Error writing to socket, n = %d\n", n);
                break;
            }
            // ? clear out old data from the buffer... memset(incoming_data, 0, n or DATA_SIZE);
            
            if (server_control == SERVER_QUIT)
            {
                break; // how to shutdown cleanly?
            }
        }
        printf("Lost that connection.\n");
        
        if (server_control == SERVER_QUIT)
        {
            break; // how to shutdown cleanly?
        }
    }
    
    printf("Server thread quitting.\n");
    pthread_exit(NULL);   // allows other threads to continue running...
}


void server_thread()
{
    pthread_t server_thread_id;       // the identifier for the thread
    int thread_result;                // return value on thread creation

    // create a thread for the server...
    thread_result = pthread_create(&server_thread_id, NULL, start_server, (void *)0 /* not used */ );
    if (thread_result) 
    {
        perror("ERROR in pthread_create.\n");
    }
}

