/*/////////////////////////////////////////////////////////////////////////////
Name: Bo Bayles
Assignment: CS 284, Homework 4
File: server.cpp
Compile: g++ -o server server.cpp -lpthread -D_REENTRANT
Description: Server for a small chat room implemented with Berkeley Sockets.
/*/////////////////////////////////////////////////////////////////////////////

/*/////////////////////////////////////////////////////////////////////////////
    Compiler Directives
/////////////////////////////////////////////////////////////////////////////*/
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <iostream>
#include <vector>
using namespace std;


#define SERVER_PORT 9999
#define CLIENT_COUNT 10

/*/////////////////////////////////////////////////////////////////////////////
    Data type definitions
/////////////////////////////////////////////////////////////////////////////*/
struct argPackage
{
    vector<int> fdArray;
    int counter;
    vector<pthread_t*> connectionThread;
    pthread_mutex_t mLock;
};
volatile int socketDescriptor;
argPackage* clientArrayPtr;

/*/////////////////////////////////////////////////////////////////////////////
    Function declarations
/////////////////////////////////////////////////////////////////////////////*/
void* connection(void* arg);
void interruptTrap(int signal);

/*/////////////////////////////////////////////////////////////////////////////
    Function definitions
/////////////////////////////////////////////////////////////////////////////*/
int main()
{
    argPackage clientInfo;
    clientArrayPtr = &clientInfo;
    clientInfo.counter = 0;
    //Set up information to be shared by all the clients

    sockaddr_in serverAddress = { AF_INET, htons(SERVER_PORT) };
    sockaddr_in clientAddress = { AF_INET };
    int client_len = sizeof(clientAddress);
    int clientSocket;
    //Set up connection parameters with Berkeley Sockets API

    pthread_mutex_init(&clientInfo.mLock, NULL);
    //Set up threading environment

    signal(SIGINT, interruptTrap);
    //Register interrup signal handler

    if( ( socketDescriptor = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 )
    {
	    cout << "socket() failed"  << endl
             << "Exiting..." << endl;
	    exit( 1 );
    }
    //Create a stream socket with socket()
    //cout << "Socket " << socketDescriptor << " was created." << endl;

    if( bind( socketDescriptor,
              (struct sockaddr*)&serverAddress,
              sizeof(serverAddress) ) == -1 )
    {
	    cout << "bind() failed"  << endl
             << "Exiting..." << endl;
	    exit( 1 );
    }
    //Bind the socket to an internet port with bind()
    //cout << "bind() was successful." << endl;

    if( listen( socketDescriptor, 1 ) == -1 )
    {
	    cout << "listen() failed"  << endl
             << "Exiting..." << endl;
	    exit( 1 );
    }
    //Listen for clients with listen()
    cout << "Listening for clients..." << endl;


    while( ( clientSocket = accept( socketDescriptor,
                                    (struct sockaddr*)&clientAddress,
                                    (socklen_t*)&client_len ) ) != -1 )
    //Wait for clients to accept
    {
       if( clientInfo.fdArray.size() <  CLIENT_COUNT )
       {
		    pthread_mutex_lock(&clientInfo.mLock);
		    pthread_t temp;
		    pthread_create( &temp,
			                NULL,
			                connection,
                            (void *) &clientInfo );

            clientInfo.fdArray.push_back(clientSocket);
            clientInfo.connectionThread.push_back(&temp);
            clientInfo.counter++; //add 1 to the number of threads
            pthread_mutex_unlock(&clientInfo.mLock);

       }
       else
       {
		   string message = "Sorry, we're full up.";
		   write(clientSocket, message.c_str(), message.size() + 1);
	   }

       pthread_yield();
    }

    for( int i = 0; i < clientInfo.connectionThread.size(); i++ )
    {
        if( !pthread_join(*clientInfo.connectionThread[i], NULL) )
            cout << "Thread " << i << " terminated properly." << endl;
        else
            cout << "Thread " << i << " did not terminate properly." << endl;
    }

    if ( shutdown(socketDescriptor, 2) != -1)
      cout << "Shutting down..." << endl;
    close(socketDescriptor);

    cout << "Exiting...\n" << endl;
    return 0;
}

void* connection(void* arg)
{
    argPackage *clientInfo = (argPackage *) arg;
    int clientId = clientInfo->fdArray.back();
    char buffer[512];
    string message, notify, nick, quitMsg;

    cout << "Client " << clientId << " has joined. Asking nickname..." << endl;

    message = "Enter your nickname: ";
    write( clientId, message.c_str(), message.size()+1 );
    //Send user nickname prompt

    read( clientId, buffer, sizeof(buffer) );
    nick = buffer;
    cout << "Client " << clientId << " is now known as " << nick << endl;
    //Receive and store the user's nickname

    message = "Server: Welcome, " + nick + "\n";
    notify = "Server: User " + nick + " has joined the chat room.\n";

    for(int i = 0; i < clientInfo->fdArray.size(); i++)
    {
    	if(clientInfo->fdArray[i] == clientId)
    	   write( clientInfo->fdArray[i], message.c_str(), message.size()+1);
    	else
    	   write( clientInfo->fdArray[i], notify.c_str(), notify.size()+1);
    }

    //Send user acknowledgement of nickname


    bool firstTime = true;
    bool quit = false;
    quitMsg = "Server: User " + nick + " has disconnected.";
    int k = 0;
    while(1)
    {
        if
        (
            (k = read( clientId, buffer, sizeof(buffer) ))
             == 0
        ) //Go until we... stop.
            break;

        if( !strncmp(buffer, "/quit", 5) ||
            !strncmp(buffer, "/part", 5) ||
            !strncmp(buffer, "/exit", 5) ) //Catch quit signals
           quit = true;

        pthread_mutex_lock(&clientInfo->mLock); //Lock for writing
           if( !quit ) //As long as the user's not quitting...
           {
              if( !firstTime )
              //...and as long as it's not the first response after the
              //  nickname...
              {
                 message = nick + ": " + buffer;
                 //Send to each user connected
                 for(int i = 0;i < clientInfo->fdArray.size(); i++)
                 {
                    write( clientInfo->fdArray[i],
                       message.c_str(),
                       message.size()+1 );
                 }
                 cout << message << endl;
                 //...send the user's input to all clients
              }
           }
           else
           {
              for(int i = 0;i < clientInfo->fdArray.size(); i++)
               {
                  write( clientInfo->fdArray[i],
                     quitMsg.c_str(),
                     quitMsg.size()+1 );
               }
               cout << quitMsg << endl;
               //...otherwise, send and the quit message to everyone
            }
        pthread_mutex_unlock(&clientInfo->mLock);
        
        if ( quit )
            break; //End reading if the user quit
        firstTime = false;
        pthread_yield(); //Let another thread have control
    } //End infinite loop.

		int currentId = 0;
        for(int i = 0; i < clientInfo->fdArray.size();i++)
		{
			if(clientInfo->fdArray[i] == clientId)
			{
				currentId = i;
				break;
			}
		}

    pthread_mutex_lock(&clientInfo->mLock);
        close( clientId ); //Kill the connection
        clientId = -1; //Invalidate the file descriptor
        clientInfo->fdArray.erase(clientInfo->fdArray.begin() + currentId); //remove the file descriptor
    pthread_mutex_unlock(&clientInfo->mLock);

    pthread_exit(arg);
    return arg;
}

void interruptTrap(int signal)
{
   string message = "Server: Shutting down in 10 seconds...\n";
   
   cout << "Interrupt received." << endl;
   pthread_mutex_lock(&clientArrayPtr->mLock);
      for(int i = 0; i < clientArrayPtr->fdArray.size();i++)
      {
         write( clientArrayPtr->fdArray[i],message.c_str(),message.size()+1 );
      }
   pthread_mutex_unlock(&clientArrayPtr->mLock);

   sleep(10);

   message = "TERM\n";

   pthread_mutex_lock(&clientArrayPtr->mLock);
      for(int i = 0; i < clientArrayPtr->fdArray.size();i++)
      {
         write( clientArrayPtr->fdArray[i],message.c_str(),message.size()+1 );
      }
   pthread_mutex_unlock(&clientArrayPtr->mLock);

   if ( shutdown(socketDescriptor, 2) != -1 )
      cout << "Shutdown successful." << endl;
   close(socketDescriptor);

   exit( 0 );

    return;
}

