/*
 * NMSTL, the Networking, Messaging, Servers, and Threading Library for C++
 * Copyright (c) 2002 Massachusetts Institute of Technology
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <nmstl/netioevent>
#include <nmstl/terminal>
#include <nmstl/serial>

#include <fcntl.h>
#include <cerrno>

#include <set>

#include "chatproto.h"

using namespace nmstl;
using namespace std;


// N.B.: Read through chatclient.cc first!


// The event loop that will dispatch all network and terminal I/O events.
io_event_loop loop;

// A handler for terminal input and output.
term_handler term(loop, "chatserver> ");

// The password that clients need to know to log in to
// the server.
const char *the_password = "foobar";



/*
 * A handler for a single connection to a chat client.
 *
 * In this application, chat_server clients are created
 * by the tcp_acceptor<chat_server> handler (see the main()
 * function below).
 *
 */
class chat_server : public msg_handler<chat_header> {
    // A set of *all* the chat_server objects with authenticated
    // users.  Static, so it's shared between all chat_server objects.
    static set<chat_server*> chats;

    // The username of the connected user, if he's authenticated
    // (otherwise, blank).
    string user;

  public:
    chat_server(io_event_loop& loop, iohandle ioh) :
	msg_handler<chat_header>(loop, ioh)
    {
	term << "Accepted connected from " << get_socket().getpeername() <<
	    " on " << get_socket().getsockname() << endl;
    }

    ~chat_server()
    {
	if (user.empty()) {
	    term << "Unauthenticated user has logged out." << endl;
        } else {
            chats.erase(this);
	    term << "User " << user << " has logged out." << endl;
	    shout("system", user + " has left the building.");
	}
    }

    // Shout: write a message to all authenticated users.
    // user is the name of the user *sending* the message.
    void shout(string user, string msg) {
	for (set<chat_server*>::iterator i = chats.begin(); i != chats.end(); ++i)
	    (*i)->write(SHOUT, omessage() << user << msg);
    }

  protected:
    // Handle an incoming message.
    void incoming_message(const chat_header& header, constbuf data) {
        // The only message we allow from a non-logged-in user
        // is LOGIN.
	if (user.empty() && header.type != LOGIN)
	    return;

	switch (header.type) {
	  case LOGIN:
	    {
                // Already authenticated!  Skip it.
		if (!user.empty()) break;

		string try_user;
                string try_password;

                // Read in the user and password provided by the client.
		imessage(data) >> try_user >> try_password;

		if (!try_user.empty() && try_password == the_password) {
                    // Non-empty username and matching password.
                    // Let 'em in!
                    user = try_user;
                    chats.insert(this);

                    // Print a status message to the terminal.
		    term << "User " << user << " has logged in" << endl;

                    // Write a positive acknowledgement to the remote
                    // user.
		    write(chat_header(AUTH_ACK));

		    shout("system", "Everyone say hello to " + user);
		} else {
                    // Nope!  Get lost.
		    term << "Failed login attempt from user " << try_user << endl;
		    write(chat_header(AUTH_NAK));
		}

		return;
	    }

	  case SHOUT:
	    {
		string msg;
		if (!(imessage(data) >> msg)) goto bad_message;

		term << "User " << user << " shouts: \"" << msg << "\"" << endl;
		shout(user, msg);

		return;
	    }

	  case WHISPER:
	    {
		string target;
		string msg;
		if (!(imessage(data) >> target >> msg)) goto bad_message;

		term << "User " << user << " whispers to " << target << ": \"" <<
                    msg << "\"" << endl;

		for (set<chat_server*>::iterator i = chats.begin(); i != chats.end(); ++i)
		    if ((*i)->user == target) {
			(*i)->write(WHISPER, omessage() << user << msg);
			return;
		    }

		// No such user!
		write(WHISPER, omessage() << "system" << target + " is not logged in.");

		return;
	    }
	}

	return;

      bad_message:
	term << " - ill-formed message" << endl;
    }
};
set<chat_server*> chat_server::chats;



int main()
{
    // Listen for connections on port 8002.  Whenever a connection
    // is accepted, tcp_acceptor basically does
    //
    //    new chat_server(loop, some_iohandle);
    //
    // (The chat_server object will be automatically deleted when
    // the connection closes or the event loop terminates.)
    tcp_acceptor< chat_server > server(loop, 8002);

    term << "Welcome.  Listening on port 8002.  Type \"help\" for help." << endl;

    // Run the event loop.  Returns only when
    // io_event_loop::terminate is invoked (because the
    // user types "quit" or "exit" or control-D).
    loop.run();
}
