///////////////////////////////////////////////////////////////////////////////////////
//  file   : tcp_chat.c
//  author : Alain Greiner
//  date   : december 2020
///////////////////////////////////////////////////////////////////////////////////////
//  This file describes a TCP chat application. Depending on the "is_server argument,
//  it can be used to launch the the TCP client, or the TCP server application.
//  The client send the first message. The server wait this first message. 
//  The 4 command line arguments are: is_server, local_ip_addr, remote_ip_addr, port
///////////////////////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <almosmkh.h>
#include <unistd.h>
#include <sys/socket.h>

#define   BUF_SIZE       256

////////////////////////////
void client_chat( int fdid )
{
    char   buffer[BUF_SIZE];   // string buffer (for both send and receive)
    int    size;               // string length (including NUL)
    int    nbytes;             // number of characters actually sent or received

    while( 1 )
    {
        //////// display client prompt to wait local client message ////////
        printf("\n[local client] ");

        // build local client message
        size = get_string( buffer , BUF_SIZE );

        // exit chat function when client message is the "exit" string
        if( strncmp( "exit" , buffer , 4 ) == 0 )
        {
            printf("\n[client_chat] local message is <exit> => return to main\n" );
            return;
        }
 
        // send local client message
        nbytes = send( fdid , buffer , size , 0 );

        if( nbytes != size )
        {
            printf("\n[client_chat error] cannot send => return to main\n");
            return;
        }

        ///////// display server prompt to wait remote server message ///////
        printf("\n[remote server] ");

        // receive remote server message
        nbytes = recv( fdid , buffer , BUF_SIZE , 0 ); 

        if( nbytes < 0 )
        {
            printf("\n\n[client_chat error] cannot receive => return to main\n" );
            return;
        }
        else if( nbytes == 0 )
        {
            printf("\n\n[client_chat] receive EOF => return to main\n" );
            return;
        }
        
        // display remote server message
        printf("%s\n", buffer );
    }
}  // end client_chat()


////////////////////////////
void server_chat( int fdid )
{
    char   buffer[BUF_SIZE];   // string buffer (for send and receive)
    int    size;               // string length (including NUL)
    int    nbytes;             // number of characters actually sent or received

    while( 1 )
    {
        //////// display client prompt to wait remote client message ///////
        printf("\n[remote client] ");

        // receive remote client message
        nbytes = recv( fdid , buffer , BUF_SIZE , 0 ); 

        if( nbytes < 0 )
        {
            printf("\n\n[server_chat error] cannot receive => return to main\n" );
            return;
        }
        else if( nbytes == 0 )
        {
            printf("\n\n[server_chat] receive EOF => return to main\n" );
            return;
        }
        
        // display remote client message
        printf("%s\n", buffer );

        //////// display server prompt to wait local server message //////
        printf("\n[local server] ");

        // build local server message
        size = get_string( buffer , BUF_SIZE );

        // exit chat function when server message is the "exit" string
        if( strncmp( "exit" , buffer , 4 ) == 0 )
        {
            printf("\n[server_chat] local message is <exit> => return to main\n" );
            return;
        }

        // send local server message
        nbytes = send( fdid , buffer , size , 0 ); 

        if( nbytes != size )
        {
            printf("\n[server_chat error] cannot send => return to main\n");
            return;
        }
    } 
}  // end server_chat()

///////////////////////
int main( int     argc,
          char ** argv )
{
    int                  pid;          // process identifier
    int                  fdid;         // file index of local socket
    int                  new_fdid;     // file index for new derver socket
    int                  error;

    sockaddr_in_t        local_sin;    // local socket internet address
    sockaddr_in_t        remote_sin;   // remote socket internet address

    int                  addr_length = sizeof(sockaddr_t);

    unsigned long long   start_cycle;

    // get  start cycle
    get_cycle( &start_cycle );

    // get PID
    pid = getpid();

    // get arguments
    if( argc != 4 )
    {
        printf("\n  usage : tcp_chat is_server local_addr remote_addr port\n");
        exit( 0 );
    }

    int is_server    = atoi( argv[0] );
    int local_addr   = atoi( argv[1] );
    int remote_addr  = atoi( argv[2] );
    int port         = atoi( argv[3] );
 
    if( is_server )
    printf("\n[%s] SERVER / pid %x / cycle %d / local %x  / remote %x / port %x\n",
    __FUNCTION__, pid, (unsigned int)start_cycle, local_addr,  remote_addr, port );
    else           
    printf("\n[%s] CLIENT / pid %x / cycle %d / local %x  / remote %x / port %x\n",
    __FUNCTION__, pid, (unsigned int)start_cycle, local_addr,  remote_addr, port );

    // initialize local_sin
    local_sin.sin_domain = HTONS( AF_INET );
    local_sin.sin_addr   = HTONL( local_addr );
    local_sin.sin_port   = HTONS( port );

    // initialize remote_sin
    remote_sin.sin_domain = HTONS( AF_INET );
    remote_sin.sin_addr   = HTONL( remote_addr );
    remote_sin.sin_port   = HTONS( port );

    // create local TCP socket
    fdid = socket( AF_INET,
                   SOCK_STREAM,
                   0 );

    if( fdid < 0 )
    {
        printf("\n[error] in %s : cannot create socket\n", __FUNCTION__ );
        exit( 0 );
    }
    else
    {
        printf("\n[%s] created socket[%x,%d]\n", __FUNCTION__, pid, fdid );
    }

    // bind local socket
    error = bind( fdid,
                  (sockaddr_t *)(&local_sin),
                  sizeof(sockaddr_t) );
    if( error )
    {
        printf("\n[error] in %s : cannot bind socket[%x,%d]\n", __FUNCTION__, pid, fdid );
        exit( 0 );
    }
    else
    {
        printf("\n[%s] client socket[%x,%d] bound\n", __FUNCTION__, pid, fdid );
    }

    ///////////////
    if( is_server )
    {
        // Listen client connection request
        error = listen( fdid , 1 );
                     
        if( error )
        {
            printf("\n[error] in %s : socket[%x,%d] cannot listen\n",
            __FUNCTION__, pid, fdid );
            exit( 0 );
        }
        else
        {
            printf("\n[%s] listening socket[%x,%d] wait client connection\n",
            __FUNCTION__ , pid , fdid );
        }

        // get new chat socket fdid
        new_fdid = accept( fdid,
                           (sockaddr_t *)&remote_sin,
                           &addr_length );

        if( new_fdid < 0 )
        {
            printf("\n[error] in %s : listening socket[%x,%d] cannot accept\n",
            __FUNCTION__, pid, fdid );
            exit( 0 );
        }
        else
        {
            printf("\n[%s] new socket[%x,%d] connected to client\n",
            __FUNCTION__, pid, new_fdid  );
        }

        // call server_chat function
        server_chat( new_fdid ); 
 
        // 6 close both listen and chat sockets
        close( fdid );
        close( new_fdid );

        printf("\n[tcp_server] closed both <listen> & <chat> sockets\n" );
    }
    //////////////////
    else  // is_client
    {
        // connect to remote socket 
        error = connect( fdid,
                         (sockaddr_t *)(&remote_sin),
                         sizeof(sockaddr_t) );
        if( error )
        {
            printf("\n[error] in %s : socket[%x,%d] cannot connect\n",
            __FUNCTION__ , pid , fdid );
            exit( 0 );
        }
        else
        {
            printf("\n[%s] socket[%x,%d] connected to server\n",
            __FUNCTION__ , pid, fdid );
        }

        // call client_chat function
        client_chat( fdid );

        // close local socket
        close( fdid );

        printf("\n[%s] client closed socket[%x,%d]\n",
        __FUNCTION__, pid, fdid );
    }

    exit(0);

    return 0;
}
