// E08-ClientServer.cpp -- sends a message from a client to a server over TCP/IP socket

// NOTE: this example demonstrates two technologies: Networking and Threading
//  A TCP/IP server and client are started in two different threads and exchange
//  messages between each other. The communication is run over TCP/IP sockets
//  and can utilize IPv4 or IPv6 protocols.


#include <Platform.h>
using namespace Platform;
using namespace Platform::Containers;
using namespace Platform::Net;


// select IP version by creating a type alias (try changing this to IPv6...)
typedef IPv4 IP;
typedef Server<IP> MyServer;
typedef Client<IP> MyClient;

// use the following port for communication
static const Int    MyPort = 45980;

// define two messages sent by server and client to each other
static const Char*  ServerMessage = appText("world");
static const Char*  ClientMessage = appText("hello");


// ServerThread implements a simple TCP/IP server; runs in own execution thread
//====================================================================================================
struct ServerThread:
    Thread::IRunnable
{
    // the endpoint to which this server will bind
    IP::Endpoint serverEndpoint;

    // simple constructor
    ServerThread(const IP::Endpoint& endpoint):
        serverEndpoint(endpoint)
    {}
    // the server thread code is implemented in run() method
    Void run()
    {
        // print a diagnostic message revealing this server's endpoint and thread id
        Runtime::StdOut::fprintln(appText("running server at endpoint %s in thread [%Lx]"), serverEndpoint.toString().value(), Thread::id());

        // create a TCP/IP server bound to the specified endpoint
        // equivalent to using native calls: socket, bind, listen
        MyServer* server = new MyServer(serverEndpoint);

        // loop until the server receives a message from the client
        String text;
        while(text != ClientMessage)
        {
            // accept incoming connections, blocks until client connects
            MyClient* client = server->accept();

            // read the message from the client's socket
            text = client->receive();

            // print the received message
            Runtime::StdOut::println(appText("server received: ") + text);

            // send confirmation message back to client
            client->send(ServerMessage);

            // we are responsible for destruction of the connected client
            //  deleting the client closes its socket
            delete client;
        }
        // destroy the server closing its socket
        delete server;
    }
};


// ServerThread implements a simple TCP/IP client; runs in own execution thread
//====================================================================================================
struct ClientThread:
    Thread::IRunnable
{
    // the endpoint to which this client will connect
    IP::Endpoint clientEndpoint;

    // simple constructor
    ClientThread(const IP::Endpoint& endpoint):
        clientEndpoint(endpoint)
    {}
    // the client thread code is implemented in run() method
    Void run()
    {
        // print a diagnostic message revealing this client's endpoint and thread id
        Runtime::StdOut::fprintln(appText("running client to endpoint %s in thread [%Lx]"), clientEndpoint.toString().value(), Thread::id());

        // create a TCP/IP client connected to the specified endpoint
        // equivalent to using native calls: socket, connect
        MyClient* client = new MyClient(clientEndpoint);

        // send a message to the server
        client->send(ClientMessage);

        // loop until we receive a confirmation message from the server
        String text;
        while(text != ServerMessage)
        {
            // read a message sent from the server
            text = client->receive();

            // print the received message
            Runtime::StdOut::println(appText("client received: ") + text);
        }
        // destroy the cliend closing its socket
        delete client;
    }
};


// program entry point
//====================================================================================================
AppMain(args)
{
    // print diagnostic messages about localhost's IPv4/IPv6 addresses
    Runtime::StdOut::println(appText("localhost IPv4 addresses: ") + Runtime::machineIPv4Addresses().toString());
    Runtime::StdOut::println(appText("localhost IPv6 addresses: ") + Runtime::machineIPv6Addresses().toString());

    // create an endpoint, which is the TCP/IP address:port for communication between the client and the server
    IP::Endpoint connectionEndpoint(IP::Address::getLocalhost(), MyPort);

    // create the server object which will shortly run in its own thread
    ServerThread server(connectionEndpoint);
    // start the server in a new thread
    Thread::Task serverTask(&server);

    // give the server some time to perform initialization; this is not mandatory but can decrease the
    //  chance of client timeout if the underlying system networking takes time to bootstrap itself
    Thread::sleep(100);

    // create the client object
    ClientThread client(connectionEndpoint);
    // start the client in a new thread
    Thread::Task clientTask(&client);

    // allow the server and client threads to complete before proceeding
    serverTask.join();
    clientTask.join();

    return 0;
}


/* EOF */