/*
	nirc: a minimal irc client (by nad)
	..just because nothing else would compile quick enough on my MacLC III..

	8-27-04 - first update in 2 years or so
	added support for pong parameters and channel keys

*/

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>
#include <netinet/in.h>

#define QUITMSG "i'm t0 lame to write d0x"
#define BUFSIZE 1024

/* prototypes */
void die(char *, ...);
int irc_connect(char *);
void do_irc(void);
void parse(char *);
void do_cmd(char *);
void do_send(void);
int msgargs(int, char *);
int getargs(char *);
struct handler *find_handler(char *);
char *resolve(char *);
int is_ip(char *);
int in_channel(char *);
void add_channel(char *);

/* handlers */
void do_msg(void);
void do_quit(void);
void do_nick(void);
void do_join(void);
void do_part(void);
void do_whois(void);
void do_who(void);
void do_whowas(void);
void do_mode(void);
void do_topic(void);
void do_names(void);
void do_list(void);
void do_invite(void);
void do_kick(void);
void do_pong(void);


/* globals */
int s;
char channel[1024], in[1024], out[1024], args[32][1024];
char nick[16];
char *user = "guh\0";
char *host = "guh\0";
char *serv = "guh\0";
char *real = "guh\0";


/* handler table */
struct handler {

	char *name;
	void (*func)(void);

};

struct handler handlers[] = {
	{"msg",		do_msg},
	{"quit",	do_quit},
	{"nick",	do_nick},
	{"join",	do_join},
	{"part",	do_part},
	{"whois",	do_whois},
	{"who",		do_who},
	{"whowas",	do_whowas},
	{"mode",	do_mode},
	{"topic",	do_topic},
	{"names",	do_names},
	{"list",	do_list},
	{"invite",	do_invite},
	{"kick",	do_kick},
	{"pong",	do_pong},
	{ NULL }
};

struct channel_t {
	char name[32];
	struct channel_t *next;
};

struct channel_t *channels=NULL, *channels_tail=NULL;

/* the c0dez */
int main(int argc, char **argv) {

	if (argc != 3)
		die("nirc <nick> <server>\n");

	strncpy(nick, argv[1], 16);

	if (irc_connect(argv[2]) == -1)
		die("unable to connect to \"%s\" as \"%s\"\n", argv[2], nick);

	do_irc();

	die("DONE\n");

	return 0;

}

void do_irc(void) {

	fd_set rset;

	printf("connected.\n");

	*channel = '\0';

	FD_ZERO(&rset);
	FD_SET(0, &rset);
	FD_SET(s, &rset);

	snprintf(out, 1024, "NICK %s\nUSER %s %s %s %s\n", nick, user, host, serv, real);
	do_send();

	while(1) {

		FD_ZERO(&rset);
		FD_SET(0, &rset);
		FD_SET(s, &rset);

		if (select(s+1, &rset, NULL, NULL, 0) > 0) {

			if (FD_ISSET(0, &rset)) {

				fgets(in, BUFSIZE, stdin);
				do_cmd(in);

			} else if (FD_ISSET(s, &rset)) {
				if (recv(s, in, BUFSIZE, 0) > 0)
					parse(in);
				else
					die("DONE(recv)\n");
			}

			memset(in, 0, BUFSIZE);

		}
	}
}


/* special arg function for n args and the rest in one (topic, msg, privmsg)*/
int msgargs(int n, char *buf) {

	char *tok;
	int i=0;

	tok = strtok(buf, " ");
	while (tok) {
		strncpy(args[i], tok, 1024);
		i++;
		if (i == n)
			break;
		tok = strtok(NULL, " ");
	}
	tok = strtok(NULL, "\0");
	if (tok) {
		strncpy(args[i], tok, 1024);
		i++;
	}
	return i;
}

int getargs(char *buf) {

	char *tok;
	int i=0;

	tok = strtok(buf, " ");
	while (tok) {
		strncpy(args[i], tok, 1024);
		i++;
		tok = strtok(NULL, " ");
	}

	return i;
}


void chomp(char *buf) {

	if (strlen(buf) > 0)
		buf[strlen(buf)-1] = '\0';

}

void do_cmd(char *buf) {

	int i;
	struct handler *h;

	chomp(buf);

	if (strncmp(buf, "/", 1) == 0) {

		if ((h = find_handler(buf)) == NULL)
			printf("unknown command (%s)\n", buf);
		else
			h->func();

	} else {
		if (strlen(channel) > 0) {
			sprintf(out, "PRIVMSG %s :%s\n", channel, buf);
			do_send();
		} else
			printf("not in a channel bitch...\n");
	}

}

struct handler *find_handler(char *buf) {

	struct handler *h;
	char *s, *tok;

	s = strdup(buf);
	tok = strtok(s, " ");

	tok++;

	for(h=handlers; h->name!=NULL; h++)
		if (strcmp(h->name, tok) == 0) {
			free(s);
			return h;
		}

	free(s);
	return NULL;

}

void parse(char *buf) {

	printf("%s", buf);

	if (strncmp(buf, "PING :", 6) == 0) {

		if (getargs(buf) == 2)
			printf("got 2 pong args: %s and %s\n", args[0], args[1]);

		do_pong();

	}

	fflush(0);

}

int irc_connect(char *server) {

	char *ip;
	struct sockaddr_in serv;

	if ((s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
		die("unable to open socket\n");

	ip = resolve(server);

	serv.sin_family = AF_INET;
	serv.sin_addr.s_addr = inet_addr(ip);
	serv.sin_port = htons(6667);

	return connect(s, (struct sockaddr *) &serv, sizeof(struct sockaddr_in));

}

char *resolve(char *name) {

	struct hostent *h;

	if (is_ip(name))
		return name;

	if ((h = gethostbyname(name)) == NULL)
		die("unable to resolve host\n");

	return (char *)inet_ntoa(*((struct in_addr *) h->h_addr));
}	

int is_ip(char *s) {
	if (!*s)
		return 0;
	while (*s)
		if (isdigit((int)*s) || *s == '.')
			s++;
		else
			return 0;
	return 1;

}

void die(char *fmt, ...) {
	va_list ap;
	close(s);
	va_start(ap, fmt);
	vprintf(fmt, ap);
	exit(-1);
}

void do_send() {
	if (send(s, out, strlen(out), 0) == -1)
		die("Fatal Error on send()\n");
	memset(out, 0, 1024);	
}


/* handlers */

void do_msg() {
	int argc;
	argc = msgargs(2, in);
	if (argc == 3) {
		sprintf(out, "PRIVMSG %s :%s\n", args[1], args[2]);
		do_send();
	} else
		printf("/msg <nick> <stuff>\n");
}

void do_quit() {
	int argc;
	argc = msgargs(1, in);
	if (argc == 2) 
		sprintf(out, "QUIT :%s\n", args[1]);
	else
		sprintf(out, "QUIT :%s\n", QUITMSG);
	do_send();
}

void do_nick() {
	if (getargs(in) == 2) {
		sprintf(out, ":%s NICK %s\n", nick, args[1]);
		do_send();
	} else
		printf("/nick <nick> (duh)\n");
}

void do_join() {
	int numargs = getargs(in);

	if (numargs == 2) {

		strncpy(channel, args[1], 1024);
		if (!in_channel(args[1])) {
			printf("not in channel %s\n", args[1]);
			add_channel(channel);
			sprintf(out, "JOIN %s\n", args[1]);
			do_send();
		} else {
			strncpy(channel, args[1], 32);
			printf("now talking in %s\n", channel);
		}

	} else if (numargs == 3) {

		// join with key
		strncpy(channel, args[1], 1024);
		if (!in_channel(args[1])) {
			printf("not in channel %s, joining with key %s\n", args[1], args[2]);
			add_channel(channel);
			sprintf(out, "JOIN %s %s\n", args[1], args[2]);
			do_send();
		} else {
			strncpy(channel, args[1], 32);
			printf("now talking in %s\n", channel);
		}

	} else
		printf("/join <channel>\n");
}

int in_channel(char *ch) {
	struct channel_t *c;

	printf("looking for %s\n", ch);
	for(c=channels; c!=NULL; c=c->next) {
		printf("comparing %s with %s\n", c->name, ch);
		if (strcmp(c->name, ch) == 0)
			return 1;
	}
	printf("no find %s\n", ch);
	return 0;
}

void add_channel(char *ch) {
	struct channel_t *c;
	printf("adding channel %s\n", ch);

	c = (struct channel_t *) malloc(sizeof(struct channel_t));
	c->next = NULL;
	strncpy(c->name, ch, 32);

	printf("strncpy of %s into %s\n", ch, c->name);
	if (channels == NULL) {
		printf("adding %s as first node\n", ch);
		channels = channels_tail = c;
	} else {

		printf("adding %s as tail\n", ch);
		channels_tail->next = c;
		channels_tail = c;

	}
	printf("channel tail = %s\n", channels_tail->name);
}

void do_part() {
	if (getargs(in) == 2) {
		*channel = '\0';
		sprintf(out, "PART %s\n", args[1]);
		do_send();
	} else
		printf("/part <channel>\n");
}

void do_whois() {
	if (getargs(in) == 2) {
		sprintf(out, "WHOIS %s\n", args[1]);
		do_send();
	} else
		printf("/whois <nick>\n");
}

void do_who() {
	if (getargs(in) == 2) {
		sprintf(out, "WHO %s\n", args[1]);
		do_send();
	} else
		printf("/who <channel>\n");
}

void do_whowas() {
	if (getargs(in) == 2) {
		sprintf(out, "WHOWAS %s\n", args[1]);
		do_send();
	} else
		printf("/whowas <nick>\n");
}

void do_mode() {
	printf("/mode unsupported\n");
}

void do_topic() {

	int argc;
	argc = msgargs(2, in);

	switch(argc) {
	case 2:
		sprintf(out, "TOPIC %s\n", args[1]);
		break;
	case 3:
		sprintf(out, "TOPIC %s :%s\n", args[1], args[2]);
		break;
	default:
		printf("/topic <channel> <topic>\n");
		return;
	}
	do_send();
}

void do_names() {
	if (getargs(in) == 2) {
		sprintf(out, "NAMES %s\n", args[1]);
		do_send();
	} else 
		printf("/names <channel>\n");
}

void do_list() {
	printf("/list unsupported\n");
}

void do_invite() {
	if (getargs(in) == 3) {
		sprintf(out, "INVITE %s %s\n", args[1], args[2]);
		do_send();
	} else
		printf("/invite <channel> <nick>\n");
}

void do_kick() {
	if (getargs(in) == 3) {
		sprintf(out, "KICK %s %s\n", args[1], args[2]);
		do_send();
	} else
		printf("/kick <channel> <nick>\n");
}

void do_pong(void) {

	printf("PONG! (%s) \n", args[1]);
	sprintf(out, "PONG %s\n", args[1]);
	do_send();
}



