// C4800: 'int' : forcing value to bool 'true' or 'false' (performance warning)
#pragma warning(disable : 4800)
// Win32 templates generate hundreds of "overly-long identifier" warnings
#pragma warning(disable : 4786)
#pragma warning(disable : 4503)

#include "tibrvcpp.h"
#include <stdio.h>
#include <stdlib.h>

#ifdef WIN32
#  include <windows.h>
#  define strcasecmp stricmp
#  define strncasecmp strnicmp
#endif

#include <string>
#include <map>

using namespace std;

const char *g_identity;
const char *g_helpstr = "commands:\n   /join <chan>\n   /leave\n   /quit\n   <msg>\n";

class Channel {
    friend class CB;

    static map<string, Channel *> __all_channels;

    string _name;
    string _fullname;
    TibrvListener _listener;

    struct CB : public TibrvMsgCallback {
	void onMsg(TibrvListener* listener, TibrvMsg& msg) {
	    ((Channel*)listener->getClosure())->gotMsg(msg);
	}
    } cb;

    TibrvTransport *_transport;

public:
    Channel(const char *name, TibrvTransport *transport) {
	_name = name;
	_transport = transport;

	_fullname = "CHATCHANNEL.";
	_fullname += name;

	_listener.create(Tibrv::defaultQueue(), &cb, transport,
			 _fullname.c_str(), this);
	__all_channels[_name] = this;
    }

    ~Channel() {
	__all_channels.erase(_name);
    }

    const char *getName() { return _name.c_str(); }

    void gotMsg(TibrvMsg &msg) {
	const char *who;
	const char *data;
	msg.getString("WHO", who);
	msg.getString("MSG", data);
	printf("<%s@%s> %s\n", who, _name.c_str(), data);
	fflush(stdout);
    }

    void send(const char *message) {
	TibrvMsg msg;
	if (msg.setSendSubject(_fullname.c_str()) != TIBRV_OK) {
	    printf("!!! could not set send subject\n"); fflush(stdout);
	    return;
	}

	msg.addString("MSG", message);
	msg.addString("WHO", g_identity);

	if (_transport->send(msg) != TIBRV_OK) {
	    printf("!!! could not send msg\n"); fflush(stdout);
	    return;
	}
    }

    static Channel *GetChannel(const char *name, TibrvTransport *trans) {
	if (__all_channels.find(name) == __all_channels.end())
	    return new Channel(name, trans);
	return __all_channels[name];
    }
    static Channel *GetAnyChannel() {
	if (__all_channels.empty()) return NULL;
	return __all_channels.begin()->second;
    }
};

class STDIN_Handler {
    TibrvTransport *_transport;

public:
    STDIN_Handler(TibrvTransport *transport) {
	_transport = transport;
    }

    void go() {
	char buf[8192];
	Channel *currchan = NULL;
	while (fgets(buf, sizeof(buf), stdin)) {
	    int i = strlen(buf);
	    while (i > 0) {
		i--;
		if (buf[i] == '\r' || buf[i] == '\n')
		    buf[i] = 0;
	    }
	    i = strlen(buf);
	    if (!i) continue;

	    if (strncasecmp(buf, "/join ", 6) == 0) {
		currchan = Channel::GetChannel(buf+6, _transport);
		printf("*** joined channel %s\n", buf+6);
		fflush(stdout);

	    } else if (strncasecmp(buf, "/help", 5) == 0) {
		printf("%s\n", g_helpstr);
		fflush(stdout);

	    } else if (strncasecmp(buf, "/quit", 5) == 0) {
		return;

	    } else if (strncasecmp(buf, "/leave", 6) == 0) {
		if (currchan) {
		    printf("*** left channel %s\n", currchan->getName());
		    delete currchan;
		    currchan = Channel::GetAnyChannel();
		    if (currchan) {
			printf("*** current channel is %s\n", currchan->getName());
		    } else {
			printf("*** not in a channel anymore\n");
		    }
		} else {
		    printf("*** not in a channel\n");
		}
		fflush(stdout);

	    } else {
		if (currchan) {
		    currchan->send(buf);
		} else {
		    printf("*** not in any channel\n"); fflush(stdout);
		}
	    }
	}
    }
};



//
// --[ MAIN ]--------------------
//

int
main(int argc, char *argv[])
{
    if (argc != 2) {
	fprintf(stderr, "usage: %s <nick>\n", argv[0]);
	fflush(stderr);
	return 1;
    }

    g_identity = argv[1];

    Tibrv::open();

    TibrvNetTransport transport;
    if (transport.create() != TIBRV_OK) {
	printf("!!! err opening transport\n"); fflush(stdout);
	exit(1);
    }
    transport.setDescription("chatter");

    // create dispatcher thread
    (new TibrvDispatcher())->create(Tibrv::defaultQueue());

    // handle cmds from stdin and send appropriatly
    (new STDIN_Handler(&transport))->go();

    Tibrv::close();
    return 0;
}

// static class members
map<string, Channel *> Channel::__all_channels;
