//Example code: A simple server side code, which echos back the received message. 
//Handle multiple socket connections with select and fd_set on Linux  
#include <stdio.h>  
#include <string.h>   //strlen  
#include <stdlib.h>  
#include <errno.h>  
#include <unistd.h>   //close  
#include <arpa/inet.h>    //close  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <sys/time.h> //FD_SET, FD_ISSET, FD_ZERO macros  
#include <iostream> //Including this header may automatically include other headers, such as <ios>, <streambuf>, <istream>, <ostream> and/or <iosfwd>.
#include <string>
#define TRUE   1  
#define FALSE  0  
#define PORT 8080  
#define max_clients 32

using namespace std;

class Client
{
    public:
    int client_socket;
    unsigned long s_addr;
    unsigned short sin_port;
    char client_ip[32];
    char client_agent[512];
    char client_filter[32];
    char client_store[512];


};
Client Clients[32]; //= {0, {0}}
 
int main(int argc , char *argv[])   
{   
    int opt = TRUE;   
    int master_socket , addrlen , new_socket , activity, i , valread , sd;  
    
    int max_sd;   
    struct sockaddr_in address;   
         
    char buffer[1025];  //data buffer of 1K  
         
    //set of socket descriptors  
    fd_set readfds;   
         
    //a message   
    char *message = "ECHO Daemon v1.0 \r\n";   
     
    //initialise all client_socket[] to 0 so not checked  
    for (i = 0; i < max_clients; i++)   
    {   
        Clients[i].client_socket = 0;   
    }   
         
    //create a master socket  
    if( (master_socket = socket(AF_INET , SOCK_STREAM , 0)) == 0)   
    {   
        perror("socket failed");   
        exit(EXIT_FAILURE);   
    }   
     
    //set master socket to allow multiple connections ,  
    //this is just a good habit, it will work without this  
    if( setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt,  
          sizeof(opt)) < 0 )   
    {   
        perror("setsockopt");   
        exit(EXIT_FAILURE);   
    }   
     
    //type of socket created  
    address.sin_family = AF_INET;   
    address.sin_addr.s_addr = INADDR_ANY;   
    address.sin_port = htons( PORT );   
         
    //bind the socket to localhost port 8888  
    if (bind(master_socket, (struct sockaddr *)&address, sizeof(address))<0)   
    {   
        perror("bind failed");   
        exit(EXIT_FAILURE);   
    }   
    printf("Listener on port %d \n", PORT);   
         
    //try to specify maximum of 3 pending connections for the master socket  
    if (listen(master_socket, 3) < 0)   
    {   
        perror("listen");   
        exit(EXIT_FAILURE);   
    }   
         
    //accept the incoming connection  
    addrlen = sizeof(address);   
    puts("Waiting for connections ...");   
         
    while(TRUE)   
    {   
        //clear the socket set  
        FD_ZERO(&readfds);   
     
        //add master socket to set  
        FD_SET(master_socket, &readfds);   
        max_sd = master_socket;   
             
        //add child sockets to set  
        for ( i = 0 ; i < max_clients ; i++)   
        {   
            //socket descriptor  
            sd = Clients[i].client_socket;   
                 
            //if valid socket descriptor then add to read list  
            if(sd > 0)   
                FD_SET( sd , &readfds);   
                 
            //highest file descriptor number, need it for the select function  
            if(sd > max_sd)   
                max_sd = sd;   
        }   
     
        //wait for an activity on one of the sockets , timeout is NULL ,  
        //so wait indefinitely  
        activity = select( max_sd + 1 , &readfds , NULL , NULL , NULL);   
       
        if ((activity < 0) && (errno!=EINTR))   
        {   
            puts("select error");   
        }   
             
        //If something happened on the master socket ,  
        //then its an incoming connection  
        if (FD_ISSET(master_socket, &readfds))   
        {   
            if ((new_socket = accept(master_socket,  
                    (struct sockaddr *)&address, (socklen_t*)&addrlen))<0)   
            {   
                perror("accept");   
                exit(EXIT_FAILURE);   
            }   
             
            //inform user of socket number - used in send and receive commands  
            printf("New connection, socket fd is %d , IP: %s port: %d \n" , new_socket , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));   
           
            //send new connection greeting message  
            if( send(new_socket, message, strlen(message), 0) != strlen(message) )   
            {   
                perror("send");   
            }   
                 
            puts("Welcome message sent successfully");   
                 
            //add new socket to array of sockets  
            for (i = 0; i < max_clients; i++)   
            {   
                //if position is empty  
                if( Clients[i].client_socket == 0 )   
                {   
                    Clients[i].client_socket = new_socket;   
                    printf("Adding to list of sockets as %d\n" , i);   
                    memset((void*)Clients[i].client_filter[0], 32, 0);
                    Clients[i].client_filter[0] = 0x255;    
                    Clients[i].s_addr = address.sin_addr.s_addr;
                    Clients[i].sin_port = address.sin_port;
                    sprintf(Clients[i].client_ip, "%s", inet_ntoa(address.sin_addr));
                    break;   
                }   
            }   
        }   
             
        //else its some IO operation on some other socket 
        for (i = 0; i < max_clients; i++)   
        {   
            sd = Clients[i].client_socket;   
                 
            if (FD_ISSET( sd , &readfds))   
            {   
                //Check if it was for closing , and also read the  
                //incoming message  
                if ((valread = read( sd , buffer, 1024)) == 0)   
                {   
                    //Somebody disconnected , get his details and print  
                    getpeername(sd , (struct sockaddr*)&address , (socklen_t*)&addrlen);   
                    printf("Host disconnected %03d] ip %s:%d \n", i, inet_ntoa(address.sin_addr) , ntohs(address.sin_port));   
                         
                    //Close the socket and mark as 0 in list for reuse  
                    close( sd );   
                    Clients[i].client_socket = 0;   
                }   
                     
                //Echo back the message that came in  
                else 
                {   
                    //set the string terminating NULL byte on the end  
                    //of the data read  
                    buffer[valread] = '\0';   
                    //send(sd , buffer , strlen(buffer) , 0 );
                    
                    if (strncmp(buffer, "-list", 5) == 0)
                    {
                        getpeername(sd , (struct sockaddr*)&address , (socklen_t*)&addrlen);
                        sprintf(buffer, "You are from %s:%d \r\nClient List\r\n" ,  inet_ntoa(address.sin_addr) , ntohs(address.sin_port));                         
                        send(sd , buffer , strlen(buffer) , 0 );
                        
                        for (int a = 0; a < max_clients; a++)   
                        {
                            if (Clients[a].client_socket > 0 && a != i)
                            {
                                getpeername(Clients[a].client_socket, (struct sockaddr*)&address , (socklen_t*)&addrlen);
                                sprintf(buffer, "%03d]%s:%d\r\n" , a, inet_ntoa(address.sin_addr) , ntohs(address.sin_port));                           
                                send(sd , buffer , strlen(buffer) , 0 );
                            }
                        }
                        send(sd , "\r\n" , strlen("\r\n"), 0 ); //Send end of list
                        continue;
                    }
                    
                    if (strncmp(buffer, "-agents", 7) == 0)
                    {
                        getpeername(sd , (struct sockaddr*)&address , (socklen_t*)&addrlen);
                        sprintf(buffer, "\r\nClient Agent List\r\n" ,  inet_ntoa(address.sin_addr) , ntohs(address.sin_port));                          
                        send(sd , buffer , strlen(buffer) , 0 );
                        
                        for (int a = 0; a < max_clients; a++)   
                        {
                            if (Clients[a].client_socket > 0 && a != i)
                            {
                                getpeername(Clients[a].client_socket, (struct sockaddr*)&address , (socklen_t*)&addrlen);
                                sprintf(buffer, "%03d]%s:%d\r\n%s\r\n" , a, inet_ntoa(address.sin_addr) , ntohs(address.sin_port), Clients[a].client_agent);                            
                                send(sd , buffer , strlen(buffer) , 0 );
                            }
                        }
                        send(sd , "\r\n" , strlen("\r\n"), 0 ); //Send end of list
                        continue;
                    }
            
                    if (strncmp(buffer, "-clearfilter", 12) == 0) //By default no one gets data
                    {
                        memset((void*)Clients[i].client_filter[0], 32, 0);
                        Clients[i].client_filter[0] = 0x0;                      
                        strcpy(buffer, "Filter cleared");
                        send(sd , buffer , strlen(buffer) , 0 );
                        continue;
                    }

                    if (strncmp(buffer, "-filter:", 8) == 0)
                    {
                        strncpy(&Clients[i].client_filter[0], &buffer[8], 32);
                        strcpy(buffer, "\r\nI got your filter: ");
                        send(sd , buffer , strlen(buffer) , 0 );
                        send(sd , &Clients[i].client_filter[0] , strlen(&Clients[i].client_filter[0]) , 0 );
                        continue;
                    }

                    if (strncmp(buffer, "-agent:", 7) == 0)
                    {
                        strncpy(&Clients[i].client_agent[0], &buffer[7], 512);
                        sprintf(buffer, "\r\nSetting Agent: %s\r\n", Clients[i].client_agent);
                        send(sd , buffer , strlen(buffer) , 0 );
                        send(sd , &Clients[i].client_agent[0] , strlen(&Clients[i].client_agent[0]) , 0 );
                        continue;
                    }

                    if (strncmp(buffer, "-server:", 8) == 0)
                    {
                        strncpy(&Clients[i].client_filter[0], &buffer[8], 32);
                        printf("%s", &buffer[8]);
                        continue;
                    }

                    if (strncmp(buffer, "-to:", 8) == 0)
                    {
                        strncpy(&Clients[i].client_filter[0], &buffer[8], 32);
                        printf("%s", &buffer[8]);
                        continue;
                    }
                    
                    if (strncmp(buffer, "-invite:", 8) == 0)
                    {
                        char temp[8];
                        strncpy(&temp[0], &buffer[8], 4);
                        printf("inviting %s", temp);
                        int targetclient = atoi(temp);
                        printf("inviting %d", targetclient);
                        char invite[128];
                        sprintf(invite, "INVITE:%s", Clients[i].client_ip);
                        send(Clients[targetclient].client_socket, invite, strlen(invite), 0);
                        continue;
                    }
                    
                    for (int a = 0; a < max_clients; a++)   
                    {
                        //printf("%d - %d\r\n", a, i);
                        if (a != i)
                        {
                            if ((strncmp(buffer, &Clients[a].client_filter[0], strlen(&Clients[a].client_filter[0])) == 0) || Clients[a].client_filter[0] == 0x0)
                            {
                                send(Clients[a].client_socket, buffer , strlen(buffer) , 0 );
                            }
                        }
                    }
                }   
            }   
        }   
    }   
         
    return 0;   
}