#include <stdlib.h>
#include <stralloc.h>
#include <afblib/hostport.h>
#include <afblib/mpx_session.h>
#include <afblib/outbuf.h>
#include <afblib/outbuf_printf.h>
#include <afblib/strhash.h>

struct chat_session {
   stralloc name;
   session* s;
   stralloc line;
   struct chat_session* next;
   struct chat_session* prev;
};

struct chat_service {
   struct chat_session* head;
   struct chat_session* tail;
   strhash users;
};

void open_handler(session* s) {
   struct chat_session* sp = calloc(1, sizeof(struct chat_session));
   if (!sp) {
      close_session(s); return;
   }
   /* link it back and forth */
   s->handle = sp; sp->s = s;
   /* greeting */
   mpx_session_printf(s, "Your nick name, please.\r\n");
}

void read_handler(session* s) {
   struct chat_service* gs = s->global_handle;
   struct chat_session* sp = s->handle;
   if (mpx_session_scan(s, &sp->line) != 1) {
      close_session(s); return;
   }
   if (sp->line.len == 0) {
      /* ignore empty input lines */
      mpx_session_printf(s, "Empty are your words...\r\n");
   } else if (sp->name.len == 0) {
      /* interpret input as name */
      stralloc_0(&sp->line);
      if (strhash_add(&gs->users, sp->line.s, 0)) {
	 --sp->line.len; /* remove 0-byte */
	 stralloc_copy(&sp->name, &sp->line);
	 /* append sp to the linear list of chat sessions */
	 sp->prev = gs->tail;
	 if (gs->tail) {
	    gs->tail->next = sp;
	 } else {
	    gs->head = sp;
	 }
	 gs->tail = sp;
	 mpx_session_printf(s, "%.*s, you are welcome!\r\n",
	    sp->name.len, sp->name.s);
      } else {
	 mpx_session_printf(s,
	    "Your nick name %s has already been taken; please try again!\r\n",
	       sp->line.s);
      }
   } else {
      /* chatting */
      for (struct chat_session* other = gs->head; other; other = other->next) {
	 if (other != sp) {
	    mpx_session_printf(other->s,
	       "%.*s: %.*s\r\n", sp->name.len, sp->name.s,
		  sp->line.len, sp->line.s);
	 }
      }
   }
}

void hangup_handler(session* s) {
   struct chat_service* gs = s->global_handle;
   struct chat_session* sp = s->handle;
   if (sp->name.len > 0) {
      if (sp->prev) {
	 sp->prev->next = sp->next;
      } else {
	 gs->head = sp->next;
      }
      if (sp->next) {
	 sp->next->prev = sp->prev;
      } else {
	 gs->tail = sp->prev;
      }
      stralloc_free(&sp->name);
   }
   stralloc_free(&sp->line);
   free(sp);
}

int main(int argc, char** argv) {
   char* cmdname = *argv++; --argc;
   outbuf err = {2};
   if (argc != 1) {
      outbuf_printf(&err, "Usage: %s hostport\n", cmdname);
      outbuf_flush(&err);
      exit(1);
   }
   char* hostport_string = *argv++; --argc;
   hostport hp;
   if (!parse_hostport(hostport_string, &hp, 33033)) {
      outbuf_printf(&err, "%s: hostport in conformance to RFC 2396 expected\n",
         cmdname);
      exit(1);
   }
   struct chat_service chat = {0}; strhash_alloc(&chat.users, 16);
   run_mpx_service(&hp, "(.*?)\r?\n",
      open_handler, read_handler, hangup_handler, &chat);
}
