#include "buddies.h"

char* bud_expand_fmt(const bud_utrec_t *bud, const char *fmt_string);

static int bud_hash_buddy(void *o){
	char *c=((buddy_t*)o)->name;
	int i, hash=0;
	for(i=0;i<strlen(c);i++){
		hash=hash<<1;
		hash+=((char*)c)[i];
	}
	return hash;
}

static int bud_hash_login(void *o){
	char *c=((bud_utrec_t*)o)->u->ut_line;
	int i,hash=0;

	for(i=0;i<strlen(c);i++){
		hash=hash<<1;
		hash+=((char*)c)[i];
	}
	return hash;
}

static int bud_comp_buddy(void *o1, void *o2){
	return strncmp(((buddy_t*)o1)->name, ((buddy_t*)o2)->name,
				__UT_NAMESIZE);
}

static int bud_comp_login(void *o1, void *o2){
	bud_utrec_t *b1=(bud_utrec_t*)o1, *b2=(bud_utrec_t*)o2;
	return strncmp(b1->u->ut_line, b2->u->ut_line, __UT_LINESIZE);
}

htable_t *bud_init_budtable(void){
	return ht_init(BUDDY_HT_SIZE, bud_hash_buddy, bud_comp_buddy);
}

int bud_load(const char *fn, htable_t *ht){
	char *line_buf=NULL;
	int line_buf_size=4, buf_offset=0, len=0;
	buddy_t *next_buddy=NULL;
	wuzzah_config_t *bconf=NULL;
	FILE *buds=NULL;

	// initialize the hashtable of buddies

	if (g_config.noloadfile) return 0;
	else if(strcmp(fn, "-")==0) buds=stdin;
	else buds=fopen(fn, "r");

	if(!buds){
		fprintf(stderr, "unable to open %s! (%s)\n", fn,
				strerror(errno));
		next_buddy=bud_create_buddy(g_config.whoami);
		fprintf(stderr, "defaulting to just watching yourself...\n");
		ht_insert(next_buddy, ht);
		return 0;
	}

	line_buf=(char*)malloc(sizeof(char)*line_buf_size);
	while(!feof(buds)){
		// first make sure we get a whole line
		if(!fgets(line_buf+buf_offset, line_buf_size-len, buds))break;
		len=strlen(line_buf);
		if(strcspn(line_buf, "\n") == len && !feof(buds)){
			buf_offset=len;
			line_buf_size *= 2;
			line_buf=(char*)realloc(line_buf, line_buf_size);
			continue;
		}
	
		next_buddy=bud_create_buddy(line_buf);
		if(ht_find(next_buddy, ht)){
			fprintf(stderr, 
					"%s: warning: duplicate buddy %s\n",
					g_config.progname, next_buddy->name);
		} else ht_insert(next_buddy, ht);

		// reset some variables
		len=buf_offset=0;
		bconf=NULL;
	}

	return 0;
}

void bud_load_every_user(htable_t *ht){
	struct passwd *pw=NULL;
	buddy_t *next_buddy=NULL;

	setpwent();
	pw=getpwent();
	while(pw){
		next_buddy=bud_create_buddy(pw->pw_name);
		ht_insert(next_buddy, ht);
		pw=getpwent();
	}
	endpwent();
}

// what's going on:
//
// - for each buddy in ht
// 	- for each bud_utrec_t in buddy's login ht
// 		- set the 'verify' field to logged out
//
// - initialize the utmpx records
// - for each record in utmpx
// 	- if it's a USER_PROCESS 
// 		- if ut_user is in buddy ht
// 			- if buddy's login ht already has ut_line
// 				- set verify field to logged in
// 			- else (they've logged in)
// 				- add the record, with verify=logged in
// 
// - for each buddy in ht
// 	- for each bud_utrec_t in buddy's login ht
// 		- if verify == logged out (they've logged out)
// 			- remove from login ht
// 		
// 

void bud_chk_utmpx(htable_t *buddy_table, short write_users){
	ht_list_t *budlist_root,*budlist, *utlist_root,*utlist;
	STRUCT_UTMPX *u;
	// below are for quick lookups in the respective ht's
	// (all i need to do is fill in the fields that are used 
	// for the hash and comp functions)
	buddy_t *budptr=NULL, tmpbud;
	bud_utrec_t *recptr=NULL, tmprec;

	budlist_root=ht_iter(buddy_table);
	budlist=budlist_root;
	while(budlist){
		utlist_root=ht_iter(((buddy_t*)budlist->info)->logins);
		utlist=utlist_root;
		while(utlist){
			((bud_utrec_t*)utlist->info)->verified=B_LOGGED_OUT;
			utlist=utlist->lptr;
		}
		ht_free_iter(utlist_root);
		budlist=budlist->lptr;
	}
	ht_free_iter(budlist_root);

	SETUTXENT();
	u=GETUTXENT();
	while(u){
		if(IS_USER_PROCESS(u)){
			tmpbud.name=u->ut_user;
			budptr=ht_find(&tmpbud, buddy_table);
			if(budptr){
				tmprec.u=u;
				recptr=NULL;
				recptr=ht_find(&tmprec, budptr->logins);
				if(recptr){
					recptr->verified=B_UNCHANGED;
				} else {
					recptr=MSTRUCT(bud_utrec_t);
					recptr->u=MSTRUCT(STRUCT_UTMPX);
					memcpy(recptr->u, u,
						sizeof(STRUCT_UTMPX));
					recptr->verified=B_LOGGED_IN;
					ht_insert(recptr, budptr->logins);
				}
			}
		}
		u=GETUTXENT();
	}
	ENDUTXENT();

	budlist_root=ht_iter(buddy_table);
	budlist=budlist_root;
	while(budlist){
		budptr=(buddy_t*)budlist->info;
		utlist_root=ht_iter(budptr->logins);
		utlist=utlist_root;
		while(utlist){
			recptr=(bud_utrec_t*)utlist->info;

			event(budptr, recptr, write_users);

			if(recptr->verified==B_LOGGED_OUT)
				ht_remove(recptr, budptr->logins);

			utlist=utlist->lptr;
		}
		ht_free_iter(utlist_root);
		budlist=budlist->lptr;
	}
	ht_free_iter(budlist_root);

	return;
}

char* bud_expand_fmt(const bud_utrec_t *bud, const char *fmt_string){
	int buf_fd[2], buf_size=0, amt=0, i, h_len=0;
	char *hname=NULL, *out_str=NULL;
	time_t tp;
	struct tm *tm_p;
	FILE *buf;
	struct hostent *hent;
	struct in_addr in;

	if(!bud || !fmt_string) return NULL;
	if(pipe(buf_fd)!=0){
		perror("unable to alloc pipe in bud_expand_fmt");
		return NULL;
	}
	buf=fdopen(buf_fd[1], "w");
	if(!buf){
		perror("unable to attach file ptr in bud_expand_fmt");
		return NULL;
	}

	tp=time(NULL);
	tm_p=localtime(&tp);

	for(i=0;i<strlen(fmt_string);i++){
		if(fmt_string[i]!='%') amt=fprintf(buf, "%c", fmt_string[i]);
		else if(i < strlen(fmt_string) - 1){
			i++;
			switch(fmt_string[i]){
			case 'a': amt=fprintf(buf, "\a"); break;
			case 'b': 
				amt=fprintf(buf, "%s", bud->u->ut_user); 
				break;
			case 'd':
				amt=fprintf(buf, "%02d:%02d:%02d", 
						tm_p->tm_hour, 
						tm_p->tm_min, tm_p->tm_sec);
				break;
			case 'h': 
				h_len=UTMPX_HOSTLEN(bud->u);
				if(h_len == 0){
					amt=fprintf(buf, "localhost");
				} else {
					hname=(char*)malloc(sizeof(char*)*(h_len+1));
					memcpy(hname, 
						UTMPX_HOSTNAME(bud->u), h_len);
					hname[h_len] = '\0';
					amt=fprintf(buf, "%s", hname);
					free(hname);
				}
				break;
			case 'H': 
				h_len=UTMPX_HOSTLEN(bud->u);
				if(h_len == 0){
					amt=fprintf(buf, "127.0.0.1");
				} else {
					hname=(char*)malloc(sizeof(char*)*(h_len+1));
					memcpy(hname, 
						UTMPX_HOSTNAME(bud->u), h_len);
					hname[h_len] = '\0';
					hent=gethostbyname(hname);
					if(!hent) amt=fprintf(buf, "n/a");
					else {
						memcpy(&in, 
							hent->h_addr_list[0],
							sizeof(struct in_addr));
						hname=inet_ntoa(in);
						amt=fprintf(buf, "%s", hname);
					}
					free(hname);
				}
				break;
			case 'l': 
				amt=fprintf(buf, "%-8s", bud->u->ut_line); 
				break;
			case 'm': 
				if(g_config.write_users) amt=fprintf(buf, "messaging");
				else amt=fprintf(buf, "not messaging");
				break;
			case 'n': amt=fprintf(buf, "\n"); break;
			case 'o': 
				if(bud->verified==B_LOGGED_IN)
					amt=fprintf(buf, "logged on ");
				else if(bud->verified==B_LOGGED_OUT)
					amt=fprintf(buf, "logged off");
				else amt=fprintf(buf, "bummin'");
				break;
			case 'u': amt=fprintf(buf, "%s", g_config.whoami); break;
			default: 
				amt=fprintf(buf, "%c", fmt_string[i]); 
				break;
			}
		}
		if(amt>0) buf_size+=amt;
		else if(amt==-1) perror("error writing to pipe");
	}
	if(!g_config.no_newline) {
		amt=fprintf(buf, "\n");
		if(amt>0) buf_size+=amt;
		else if(amt==-1) perror("error writing to pipe");
	}
	if(fclose(buf)!=0) perror("error closing pipe");

	out_str=(char*)malloc(sizeof(char)*(buf_size+2));
	amt=read(buf_fd[0], out_str, sizeof(char)*(buf_size));
	out_str[amt]='\0';
	if(close(buf_fd[0])!=0) perror("error closing pipe");
	return out_str;
}

int argvify(int *cmd_argc, char **cmd_argv[], const char *cmdline){
	int i=0, num_args=1, cmd_len=strlen(cmdline);
	char **argv=NULL, *next_arg=NULL, *cmd_copy=strdup(cmdline);

	if(strtok(cmd_copy, " \t\n")) num_args++;
	else { 
		fprintf(stderr, "argvify: unable to parse string!\n");
		return -1;
	}
	while(strtok(NULL, " \t\n")) num_args++;

	argv=(char **)malloc(sizeof(char*)*(num_args+1));
	if(!argv) { perror("argvify"); return -1; }
	else memset(argv, 0, sizeof(char*)*(num_args+1));

	argv[0]=strdup(g_config.progname);
	next_arg=cmd_copy;
	for(i=1; i<num_args; i++){
		while(next_arg[0]=='\0') next_arg+=sizeof(char);
		argv[i] = strdup(next_arg);
		next_arg=memchr(next_arg, '\0',cmd_len-(next_arg-cmd_copy));
	}
	argv[num_args]=NULL;
	*(cmd_argv)=argv;
	*(cmd_argc)=num_args-1;
	free(cmd_copy);
	return 0;
}

buddy_t* bud_create_buddy(const char *line){
	char *line_buf=strdup(line);
	buddy_t *next_buddy=MSTRUCT(buddy_t);
	short colon=0, len=0;
	int ac, i;
	char **av=NULL;
	wuzzah_config_t *bconf=NULL;

	// now check for a per-user config 
	len=strlen(line_buf);
	colon=strcspn(line_buf, ":");
	if(colon<len-1) {
		line_buf[colon]='\0';
		bconf=MSTRUCT(wuzzah_config_t);
		ac=string_to_argv(&line_buf[colon+1], &av);
		if(ac == -1) bail("error parsing config file\n", 1);
		else if(ac){
			process_args(ac, av, bconf);
			for(i=0;i<ac;i++){
				free(av[i]);
			}
			free(av);
		}
	}

	// now fill in all the data and insert into the ht
	if(line_buf[len-1]=='\n') line_buf[len-1]='\0';
	next_buddy->name=strdup(line_buf);
	next_buddy->conf=bconf;
	next_buddy->logins=ht_init(BUDDY_HT_SIZE, 
			bud_hash_login, bud_comp_login);

	free(line_buf);
	return next_buddy;
}
