//
// tcpservice.cpp
//
//  Copyright 2000 - Gianni Mariani <gianni@mariani.ws>
//
//  An example of a simple chatty server using CommonC++.
//
//  This simple application basically operates as a
//  very simple chat system. From a telnet session
//  on localhost:3999 , any messages typed from a telnet
//  client are written to all participating sessions.
//
//  This is free software licensed under the terms of the GNU
//  Public License
//
//  This example:
//
//  This demostrates a simple threaded server, actually,
//  the sessions are not all threaded though they could be
//  if that's what you wanted.	Basically it demonstrates the
//  use of SocketService, SocketPorts and Threads.
//
//  For those familiar with Unix network programming, SocketService
//  basically encapsulates all the work to communicate with
//  the select() or poll() system calls.  SocketPorts are
//  basically encapsulations of sessions or open file descriptors.
//
//  Anyhow, this example is a very simple echo server but
//  it echos to all connected clients.	So it's a poor man's
//  IRC !  You connect via telnet to localhost port 3999 and
//  it will echo to all other connected clients what you type in !
//

#include <cc++/socketport.h>

#include <iostream>

// For starters, we need a thread safe list, we'll make one
// out of the STL list<> template -
//  http://www.sgi.com/Technology/STL/index.html
//
// Thread safe list class
//
#include <list>

#ifdef	CCXX_NAMESPACES
using namespace std;
using namespace ost;
#endif

class ts_list_item;
typedef list<ts_list_item *> ts_list;

// a list head - containing a list and a Mutex.
// It would be really nice to teach stl to do this.

class ts_list_head {
public:

    // No point inheriting, I'd have to implement
    // alot of code. We'll hold off on that exercise.

    // Using the CommonC++ Mutex class.
    Mutex		    linkmutex;
    // And the STL template.
    ts_list		    list_o_items;

    // Not nessasary, but nice to be explicit.
    ts_list_head()
	: linkmutex(), list_o_items()
    {
    }

    // This thing knows how to remove and insert items.
    void RemoveListItem( ts_list_item * li );
    void InsertListItem( ts_list_item * li );

    // And it knows how to notify that it became empty
    // or an element was deleted and it was the last one.
    virtual void ListDepleted()
    {
    }

    virtual ~ts_list_head()
    {
    }
};


// This item knows how to remove itself from the
// list it belongs to.
class ts_list_item {
public:
    ts_list::iterator	   linkpoint;
    ts_list_head	  * listhead;

    virtual ~ts_list_item()
    {
	listhead->RemoveListItem( this );
    }

    ts_list_item( ts_list_head * head )
    {
	listhead = head;
	head->InsertListItem( this );
    }
};

void ts_list_head::RemoveListItem( ts_list_item * li )
{
    bool    is_empty;
    linkmutex.enterMutex();
    list_o_items.erase( li->linkpoint );
    is_empty = list_o_items.empty();
    linkmutex.leaveMutex();

    // There is a slim possibility that at this time
    // we recieve a connection.
    if ( is_empty ) {
	ListDepleted();
    }
}

void ts_list_head::InsertListItem( ts_list_item * li )
{
    linkmutex.enterMutex();
    list_o_items.push_front( li );
    li->linkpoint = list_o_items.begin();
    linkmutex.leaveMutex();
}

// ChatterSession operates on the individual connections
// from clients as are managed by the SocketService
// contained in CCExec.  ChatterThread simply waits in
// a loop to create these, listening forever.
//
// Even though the SocketService contains a list of
// clients it serves, it may actually serve more than
// one type of session so we create our own list by
// inheriting the ts_list_item.
//

class ChatterSession :
    public virtual SocketPort,
    public virtual ts_list_item
{
public:

	enum { size_o_buf = 2048 };

    // Nothing special to do here, it's all handled
    // by SocketPort and ts_list_item

    virtual ~ChatterSession()
    {
	cerr << "ChatterSession deleted !\n";
    }

    // When you create a ChatterSession it waits to accept a
    // connection.  This is done by it's own
    ChatterSession(
	TCPSocket      & server,
	SocketService	* svc,
	ts_list_head	* head
    ) :
	SocketPort( NULL, server ),
	ts_list_item( head )
    {
	cerr << "ChatterSession Created\n";

	tpport_t port;
	InetHostAddress ia = getPeer( & port );

	cerr << "connecting from " << ia.getHostname() <<
	":" << port << endl;

	// Set up non-blocking reads
	setCompletion( false );

	// Set yerself to time out in 10 seconds
	setTimer( 100000 );
	attach(svc);
    }

    //
    // This is called by the SocketService thread when it the
    // object has expired.
    //

    virtual void expired()
    {
	// Get outa here - this guy is a LOOSER - type or terminate
	cerr << "ChatterSession Expired\n";
	delete this;
    }

    //
    // This is called by the SocketService thread when it detects
    // that there is somthing to read on this connection.
    //

    virtual void pending()
    {
	// Implement the echo

	cerr << "Pending called\n";

	// reset the timer
	setTimer( 100000 );
	try {
	    int    len;
	    unsigned int total = 0;
	    char    buf[ size_o_buf ];

	    while ( (len = receive(buf, sizeof(buf) )) > 0 ) {
		total += len;
		cerr << "Read '";
		cerr.write( buf, len );
		cerr << "'\n";

		// Send it to all the sessions.
		// We probably don't really want to lock the
		// entire list for the entire time.
		// The best way to do this would be to place the
		// message somewhere and use the service function.
		// But what are examples for ?

		bool sent = false;
		listhead->linkmutex.enterMutex();
		for (
		   ts_list::iterator iter = listhead->list_o_items.begin();
		   iter != listhead->list_o_items.end();
		   iter ++
		) {
		   ChatterSession * sess =
		    dynamic_cast< ChatterSession * >( * iter );
		   if ( sess != this ) {
		    sess->send( buf, len );
		    sent = true;
		   }
		}
		listhead->linkmutex.leaveMutex();

		if ( ! sent ) {
		   send(
		    ( void * ) "No one else listening\n",
		    sizeof( "No one else listening\n" ) - 1
		   );

		   send( buf, len );
		}
	    }
	    if (total == 0)
	    {
		cerr << "Broken connection!\n" << endl;
		delete this;
	    }
	}
	catch ( ... )
	{
	    // somthing wrong happened here !
	    cerr << "Socket port write sent an exception !\n";
	}

    }

    virtual void disconnect()
    {
	// Called by the SocketService thread when the client
	// hangs up.
	cerr << "ChatterSession disconnected!\n";

	delete this;
    }

};

class ChatterThread;

//
// This is the main application object containing all the
// state for the application.  It uses a SocketService object
// (and thread) to do all the work, however, that object could
// theoretically be use by more than one main application.
//
// It creates a ChatterThread to sit and wait for connections
// from clients.

class CCExec : public virtual ts_list_head {
public:

    SocketService	    * service;
    ChatterThread	       * my_Chatter;
    Semaphore		      mainsem[1];

    CCExec():my_Chatter(NULL)
    {
	service = new SocketService( 0 );
    }

    virtual void ListDepleted();

    // These methods defined later.
    virtual ~CCExec();
    int RunApp( char * hn = "localhost" );

};

//
// ChatterThread simply creates ChatterSession all the time until
// it has an error.  I suspect you could create as many of these
// as the OS could take.
//

class ChatterThread : public virtual TCPSocket, public virtual Thread {
public:

    CCExec		* exec;

    void run ()
    {
	while ( 1 ) {
	    try {
		// new does all the work to accept a new connection
		// attach itself to the SocketService AND include
		// itself in the CCExec list of sessions.
		new ChatterSession(
		   * ( TCPSocket * ) this,
		   exec->service,
		   exec
		);
	    }
	    catch ( ... )
	    {
		// Bummer - there was an error.
		cerr << "ChatterSession create failed\n";
		exit();
	    }
	}
    }

    ChatterThread(
	InetHostAddress & machine,
	int	      port,
	CCExec	       * inexec

    ) : TCPSocket( machine, port ),
	Thread(),
	exec( inexec )
    {
	    start();
    }


};

//
// Bug here, this should go ahead and shut down all sessions
// for application.  An exercise left to the reader.

CCExec::~CCExec()
{
    // MUST delete my_Chatter first or it may end up using
    // a deleted service.
    if ( my_Chatter ) delete my_Chatter;

    // Delete whatever is left.
    delete service;
}

//
// Run App would normally read some config file or take some
// parameters about which port to connect to and then
// do that !
int CCExec::RunApp( char * hn )
{
    // which port ?

    InetHostAddress machine( hn );

    if ( machine.isInetAddress() == false ) {
	cerr << "machine is not address" << endl;
    }

    cerr << "machine is " << machine.getHostname() << endl;

    // Start accepting connections - this will bind to the
    // port as well.
    try {
	my_Chatter = new ChatterThread(
	    machine,
	    3999,
	    this
	);
    }
    catch ( ... )
    {
	cerr << "Failed to bind\n";
	return false;
    }
    
    return true;
}

// When there is no one else connected - terminate !
void CCExec::ListDepleted()
{
    mainsem->post();
}


int main( int argc, char ** argv )
{
    CCExec	* server;

    server = new CCExec();

    // take the first command line option as a hostname
    // to listen to.
    if ( argc > 1 ) {
	server->RunApp( argv[ 1 ] );
    } else {
	server->RunApp();
    }

    server->mainsem->wait();

    delete server;

    return 0;
}
