/*
 * wuzzah.c
 *
 * written and (c) 2001 by Sean Finney
 *
 * This program can be freely copied and redistributed under
 * the terms of the GNU General Public License, v2.  For details
 * of said terms, please see the file COPYING
 *
 */

//	Includes
#include "includes.h"
#include "buddies.h"
#include "hashtable.h"
#include "common.h"

//	Definitions

//	Macros

//	Function Prototypes
void usage(void);
void version(void);
void wuzzah_whoami(void);
void wuzzah_add_extrabuddies(const char *line, htable_t *ht);

//	Global (config) Variables 
//	g_progname:		the name of this program (basename(argv[0]))
//	g_infile:		file to be read in 
//	g_buddy_msg:		message to be sent to buddy
//	g_status_msg:		message to be displayed on local tty
//	g_write_users:		whether or not to send users a message
//	g_sleep_interval:	how long to sleep between polling utmp
//	g_whoami:		local username
//	g_no_newline:		don't hard-handedly add a newline to the mesg
//	g_eventcmd:		a command to run when a buddy logs on/off

wuzzah_config_t g_config;

static const char *g_default_buddy_msg=
	"(wuzzah)  %u says: \"shoutout to my homie %b.\"";
static const char *g_default_status_msg=
	"(%d)  %b %o %l from %h";
static const char *g_default_infile=".wuzzah";
static const int g_default_sleep_interval=1;

struct option program_opts[] = {
	{ "all-users",1,0,'a' },
	{ "exec-command",1,0,'c' },
	{ "help",0,0,'h' },
	{ "buddyfile",1,0,'f' },
	{ "no-buddyfile",0,0,'F'},
	{ "interval",1,0,'i' },
	{ "message",1,0,'m' },
	{ "no-newline",0,0,'n' },
	{ "process-once",0,0,'o' },
	{ "process-current",0,0,'p' },
	{ "silent",0,0,'q' },
	{ "status-message",1,0,'s' },
	{ "users",1,0,'u' },
	{ "version",0,0,'v' },
	{ "write-buddies",0,0,'w' },
	{ NULL, 0, 0, 0}
};

extern char *optarg;
extern int optind;

//	---- MAIN ----
int main(int argc, char *argv[]){
	htable_t *ht=NULL;
	process_args(argc, argv, &g_config);

	ht=bud_init_budtable();
	wuzzah_whoami();
	if(g_config.all_users){
		bud_load_every_user(ht);
	} else {
		if(g_config.extrabuddies)
			wuzzah_add_extrabuddies(g_config.extrabuddies, ht);
		if(!g_config.noloadfile) bud_load(g_config.infile, ht);
	}

	if(ht->size==0){
		fprintf(stderr, 
			"%s: no buddies specified\n", g_config.progname);
		fprintf(stderr, "well, if you don't have any friends for\n");
		fprintf(stderr, "me to watch, I'm going away!\n");
		exit(1);
	}

	// let's do it once first, and not spam everyone when
	// we start up (hence 0).
	if(!g_config.process_current) bud_chk_utmpx(ht, -1);
	else bud_chk_utmpx(ht, g_config.write_users);
	// if we were only supposed to go once
	if(g_config.run_once) return 0;
	// otherwise...
	while(!sleep(g_config.sleep_interval)){
		bud_chk_utmpx(ht, g_config.write_users);
	}

	return 0;
}

//	---- Other Functions ----

void process_args(int ac, char *av[], wuzzah_config_t *conf){
	int c, len;
	char *homedir;

	optind=0;

	memset(conf, 0, sizeof(wuzzah_config_t));
	conf->progname=av[0]=basename(av[0]);	

	while(1){
		c=getopt_long(ac, av, "ac:f:Fhi:m:nopqs:u:vw", 
				program_opts, NULL);
		switch(c){
		case 'a': conf->all_users=1; break;
		case 'c': conf->eventcmd=strdup(optarg); break;
		case 'f': conf->infile=strdup(optarg); break;
		case 'F': conf->noloadfile=1; break;
		case 'h': version(); usage(); exit(0); break;
		case 'i': conf->sleep_interval=atoi(optarg); break;
		case 'm': conf->buddy_msg=strdup(optarg); break;
		case 'n': conf->no_newline=1; break;
		case 'o': conf->run_once=1;
		case 'p': conf->process_current=1; break;
		case 'q': conf->write_users=0; break;
		case 's': conf->status_msg=strdup(optarg); break;
		case 'u': conf->extrabuddies=strdup(optarg); break;
		case 'v': version(); exit(0); break;
		case 'w': conf->write_users=1; break;
		} 
		if(c==-1)break;
	}
	if(!conf->buddy_msg)conf->buddy_msg=g_default_buddy_msg;
	if(!conf->status_msg)conf->status_msg=g_default_status_msg;

	//	if the userfile hasn't been specified, use ${HOME}/.wuzzah
	if(!conf->infile && !conf->noloadfile){
		homedir=getenv("HOME");
		len=strlen(homedir)+1+strlen(g_default_infile); // 1 for '/'
		conf->infile=(char*)malloc(sizeof(char)*(len+1));
		strncpy(conf->infile, homedir, strlen(homedir)+1);
		strncat(conf->infile, "/", 1);
		strncat(conf->infile, g_default_infile,
				strlen(g_default_infile));
	}
	if(!conf->sleep_interval)
		conf->sleep_interval=g_default_sleep_interval;
}

/*
 * gaaaah, my eyes, this is so ugly.  please make me prettier without
 * losing any functionality...
 *
 */
int string_to_argv(const char *str, char **av[]){
	int c=0, i=0, j=0, num_args=1, len=strlen(str), procd_str_pipe[2];
	int arg_len=0, arg_max_len=0;
	short parsing_whitespace=1;
	char **argv=NULL, *tmp=strdup(str);
	FILE *p_write, *p_read;

	if(pipe(procd_str_pipe)) perror("opening pipe");
	p_read=fdopen(procd_str_pipe[0], "r");
	if(!p_read) perror("opening read end of pipe");
	p_write=fdopen(procd_str_pipe[1], "w");
	if(!p_write) perror("opening write end of pipe");

	for(i=0;i<len;i++){
		switch(str[i]){
		case '\\':
			if(parsing_whitespace) {
				num_args++;
				parsing_whitespace=0;
			}
			if(i==len-1) {
				fprintf(stderr, 
					"syntax err: unterminated \\\n");
				return -1;
			}
			fputc(str[i++], p_write);
			fputc(str[i], p_write);
			arg_len+=2;
			break;
		case ' ': case '\n': case '\t':
			if(!parsing_whitespace){
				if(arg_len>arg_max_len)arg_max_len=arg_len;
				arg_len=0;
				fputc('\0', p_write);
				parsing_whitespace=1;
			} 
			break;
		case '\'':
			if(parsing_whitespace) {
				num_args++;
				parsing_whitespace=0;
			}
			while(str[i+1] != '\'' || str[i] == '\\'){
				if(++i > len){
					fprintf(stderr, 
						"error: unterminated '\n");
					return -1;
				}
				if(str[i+1]=='\'' && str[i] == '\\') continue;
				else {
					arg_len++;
					fputc(str[i], p_write);
				}
			}
			i++;
			break;
		case '"':
			if(parsing_whitespace) {
				num_args++;
				parsing_whitespace=0;
			}
			while(str[i+1] != '"' || str[i] == '\\'){
				if(++i > len){
					fprintf(stderr, 
						"error: unterminated \"\n");
					return -1;
				}
				if(str[i+1]=='"' && str[i] == '\\') continue;
				else {
					arg_len++;
					fputc(str[i], p_write);
				}
			}
			i++;
			break;
		default: 
			if(parsing_whitespace) {
				num_args++;
				parsing_whitespace=0;
			}
			arg_len++;
			fputc(str[i], p_write);
			break;
		}
	}

	fclose(p_write);
	argv=(char **)malloc(sizeof(char*)*(num_args+1));
	memset(argv, 0, sizeof(char)*(num_args+1));

	argv[0]=strdup(g_config.progname);

	for(i=1; i<num_args; i++){
		argv[i]=(char*)malloc(sizeof(char)*(arg_max_len+1));
		memset(argv[i], 0, sizeof(char)*(arg_max_len+1));
		c=fgetc(p_read);
		for(j=0; c!='\0' && !feof(p_read) && !ferror(p_read); j++){
			argv[i][j] = c;	
			c=fgetc(p_read);
		}
		argv[i][j]='\0';
	}
	free(tmp);
	argv[num_args]=NULL;
	*(av)=argv;
	return num_args;
}

void wuzzah_whoami(void){
	struct passwd *pwd=getpwuid(getuid());	
	// get the user's name as gracefully as we can :)
	if(!pwd){
		fprintf(stderr, 
			"uh, your uid (%d) wasn't found..\n", (int)getuid());
		fprintf(stderr, "so I'm setting your name to \"dumbass\".\n");
		g_config.whoami=(char*)malloc(sizeof(char)*8);
		strncpy(g_config.whoami, "dumbass", 8);
	} else {
		g_config.whoami=(char*)malloc(sizeof(char)*(strlen(pwd->pw_name)+1));
		strncpy(g_config.whoami, pwd->pw_name, (strlen(pwd->pw_name)+1));
	}
}

void wuzzah_add_extrabuddies(const char *line, htable_t *ht){
	int i=0, len=strlen(line);
	const char *head=line;
	char *buf=NULL;

	buf=(char*)malloc(sizeof(char)*(strlen(line)+1));
	while((int)(head-line)<len){
		i=strcspn(head, " \t\n,:");
		if(i>0) {
			strncpy(buf, head, i);
			buf[i]='\0';
		} else {
			strncpy(buf, head, strlen(head));
			buf[strlen(head)]='\0';
		}
		ht_insert(bud_create_buddy(buf), ht);
		head=&head[i+1];
	}
	free(buf);
}

void version(void){
	fprintf(stderr, "%s v%s\t-\twuuzzzaaah?\n", g_config.progname, 
			PACKAGE_VERSION);
	fprintf(stderr, "copyright 2002 sean finney <seanius@seanius.net>\n");
}

void usage(void){
        fprintf(stderr, "\nusage:\t");
        fprintf(stderr, "%s [OPTIONS] ...\n", g_config.progname);
        fprintf(stderr, ""
"  -h, --help                   this informative usage summary :)\n"
"  -v, --version                the current version and copyright\n\n"
"  -a, --all-users              load and watch for all users\n"
"  -c, --exec-cmd=CMD           evaluate/execute CMD with every login\n"
"  -f, --buddyfile=FILE         use FILE as buddyfile, can be '-' for\n"
"                               signifying to use stdin.\n"
"  -F, --no-buddyfile           do not attempt to load the file which\n"
"                               contains the list of buddies to watch\n"
"  -i, --interval=NUM           sleep NUM seconds between each polling\n"
"  -m, --message=STRING         use STRING as a message template to greet\n"
"                               logged-in buddies. RTFM for more information\n"
"  -n, --no-newline             don't end the various messages in newlines\n"
"  -o, --process-once           exit after having scanned once through the\n"
"                               records.\n"
"  -p, --process-current        override wuzzah's default behavior and\n"
"                               message/write/exec-cmd the users already\n"
"                               logged in.\n"
"  -q, --silent                 don't message buddies when they log in\n"
"  -s, --status-message=STRING  use STRING as a template for displaying the\n"
"                               status of people logging in and out\n"
"  -u, --users=LIST             adds LIST to the list of buddies for which\n"
"                               to watch.\n"
"  -w, --write-budies           be obnoxious and write your buddy every time\n"
"                               you see him/her log in.\n"
        );
}

void bail(char *reason, int exitval){
	if(reason) fprintf(stderr, "%s: bailing: %s\n", 
			g_config.progname, reason);
	exit(exitval);
}
