/*
 *
 *   unixserver.cc
 *
 *   Implements a simple echo server for testing of my::multiplexed::unixsocket class.
 *
 *   Copyright (C) 2003 by Matt Roberts, KK5JY,
 *   All rights reserved.
 *
 *   TODO:
 *      + Add limit controls, so that the number of simultaneous connections
 *        can be controled.
 *      + Add syslog support to make it all very pretty. :)
 *
 *
 */


#include <string>
#include <list>
#include <iostream>
#include <my++/multiplex.h>
#include <my++/muxunixsocket.h>
#include <signal.h>
#include <stdlib.h>

#define LISTEN_ADDR "testsocket"

using namespace std;


void cleanup (void) {
	cerr << "Cleanup.\r\n";
	remove(LISTEN_ADDR);
}

void signal_handler(int sig) {
	cleanup();
}


/*
 *
 *   class session
 *
 *   Represents one proxy session; holds two Unix sockets.
 *
 */

struct session {
	public:
		my::multiplexed::unixsocket client;
		bool cleanup;

	public:
		session (void) : cleanup(false) { /* noop */ };

	private:
		session (const session &s) { /* noop */ };
};



/*
 *
 *   main (...)
 *
 */

int main (int argc, char **argv) {
	my::multiplexed::unixsocket listener;
	my::multiplex mux;
	char buffer [128];
	int count;
	int devs;
	bool do_cleanup, try_again;

	std::list<session*> clients;
	std::list<session*>::iterator ci;

	unlink(LISTEN_ADDR);
	signal(SIGTERM, signal_handler);
	signal(SIGKILL, signal_handler);

	/* attempt to bind to a well-known port */
	try {
		listener.bind (LISTEN_ADDR);
	} catch (my::unixsocket::exception &e) {
		cerr << "Bind failure: " << e.what() << endl;
		return 1;
	}

	/* set that socket to listen mode */
	try {
		listener.listen(5);
	} catch (my::unixsocket::exception &e) {
		cerr << "Listener failure: " << e.what() << endl;
		return 1;
	}

	/* start by configuring the mux to watch for inbound connections on the listener */
	mux.add (listener);

	cout << "Listening to \'" << LISTEN_ADDR << '\'' << endl;

	for (;;) {
		// assume that we will have a clean run
		do_cleanup = false;
		try_again = false;

		try {
			// watch for something to happen
			devs = mux.watch();
			cerr << "There are " << clients.size() << " clients currently connected." << endl;
		} catch (const my::unixsocket::exception &e) {
			cout << "Unix Socket Error: " << e.what() << endl;
			for (ci = clients.begin(); ci != clients.end(); ++ci) {
				if ((&(*ci)->client) == e.object()) {
					clients.erase(ci);
					break;
				}
			}
			try_again = true;
		} catch (const std::exception &e) {
			cout << "Error: " << e.what() << endl;
			try_again = true;
		}

		if (try_again) {
			try_again = false;
			continue;
		}

		// if we can read from the listener socket
		if (listener.inbound()) {
			cout << "Accept new connection: ";
			cout.flush();
			session *new_session = 0;

			// try to accept the client connection request
			try {
				new_session = new session;
				my::multiplexed::unixsocket::address addr;
				std::string disp_str;
				listener.accept (new_session->client, addr);
				cout << addr.to_string(disp_str) << endl;
			} catch (const std::exception &e) {
				cerr << "Could not accept new connection: reason was "
					<< e.what() << endl;
				delete new_session;
				new_session = 0;
			}

			// try to connect to the remote service
			if (new_session != 0) {
				// add the new sockets to the mux
				mux.add (new_session->client);

				// add the new object to the list of active clients
				clients.push_back(new_session);

				my::multiplexed::unixsocket::address a1;
				new_session->client.peer(a1);
				cout << "Added new echo session: " << a1.to_string() << endl;
			}
		}

		for (ci = clients.begin(); ci != clients.end(); ++ci) {
			/* this is our current session */
			session *sptr = *ci;

			try {
				/* if we can read from client end */
				while (sptr->client.ready() > 0) {
					/* read from client, write to server */
					count = sptr->client.get (buffer, sizeof(buffer) - 1);
					if (count > 0) {
						sptr->client.put (buffer, count);
						cout << "Read " << count << " bytes from console." << endl;
					}
				}

				if (sptr->client.disconnected()) {
					my::multiplexed::unixsocket::address client_addr;
					sptr->client.peer(client_addr);
					cout << "Client closed connection." << endl;
					/* disconnect in both directions */
					sptr->client.disconnect();
					/* remove both sockets from mux */
					mux.remove(sptr->client);
					/* flag this object for cleanup */
					do_cleanup = true;
					sptr->cleanup = true;
				}
			} catch (const std::exception &e) {
				sptr->client.disconnect();
				mux.remove(sptr->client);
				do_cleanup = true;
				sptr->cleanup = true;
			}
		}

		/* only do this if some session ended */
		if (do_cleanup) {
			do_cleanup = false;

			/* look for sessions to destroy */
			for (ci = clients.begin(); ci != clients.end(); ++ci) {
				/* if the node is to be destroyed, move it into the 'pending' list */
				if ((*ci)->cleanup) {
					delete (*ci);
					clients.erase(ci);
					/* at this point, 'ci' is undefined, so we just start again at the
					 * beginning of the list 'clients' */
					ci = clients.begin();
				}
			}
		}
	}

	/* should never reach here */
	cleanup();
	return 0;
}
