/*
 * sample yahoo client based on libyahoo2
 *
 * libyahoo2 is available at http://libyahoo2.sourceforge.net/
 *
 * $Revision: 374525 $
 * $Date: 2004-12-31 19:02:39 +0100 (Fr, 31 Dez 2004) $
 * 
 * Copyright (C) 2002, Philip S Tellis <philip . tellis AT gmx . net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#if HAVE_CONFIG_H
# include <config.h>
#endif
#include <netdb.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <termios.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

/* Get these from http://libyahoo2.sourceforge.net/ */
#include <yahoo2.h>
#include <yahoo2_callbacks.h>
#include "yahoo_util.h"

int fileno(FILE * stream);


#define MAX_PREF_LEN 255

static char *local_host = NULL;

static int do_mail_notify = 0;
static int do_yahoo_debug = 0;
static int ignore_system = 0;
static int do_typing_notify = 1;
static int accept_webcam_viewers = 1;
static int send_webcam_images = 0;
static int webcam_direction = YAHOO_WEBCAM_DOWNLOAD;
static time_t curTime = 0;
static time_t pingTimer = 0;
static time_t webcamTimer = 0;
static double webcamStart = 0;

/* id of the webcam connection (needed for uploading) */
static int webcam_id = 0;

static int poll_loop=1;

static void register_callbacks();

typedef struct {
	char yahoo_id[255];
	char password[255];
	int id;
	int fd;
	int status;
	char *msg;
} yahoo_local_account;

typedef struct {
	char yahoo_id[255];
	char name[255];
	int status;
	int away;
	char *msg;
	char group[255];
} yahoo_account;

typedef struct {
	int id;
	char *label;
} yahoo_idlabel;

typedef struct {
	int id;
	char *who;
} yahoo_authorize_data;

yahoo_idlabel yahoo_status_codes[] = {
	{YAHOO_STATUS_AVAILABLE, "Available"},
	{YAHOO_STATUS_BRB, "BRB"},
	{YAHOO_STATUS_BUSY, "Busy"},
	{YAHOO_STATUS_NOTATHOME, "Not Home"},
	{YAHOO_STATUS_NOTATDESK, "Not at Desk"},
	{YAHOO_STATUS_NOTINOFFICE, "Not in Office"},
	{YAHOO_STATUS_ONPHONE, "On Phone"},
	{YAHOO_STATUS_ONVACATION, "On Vacation"},
	{YAHOO_STATUS_OUTTOLUNCH, "Out to Lunch"},
	{YAHOO_STATUS_STEPPEDOUT, "Stepped Out"},
	{YAHOO_STATUS_INVISIBLE, "Invisible"},
	{YAHOO_STATUS_IDLE, "Idle"},
	{YAHOO_STATUS_OFFLINE, "Offline"},
	{YAHOO_STATUS_CUSTOM, "[Custom]"},
	{YAHOO_STATUS_NOTIFY, "Notify"},
	{0, NULL}
};

char * yahoo_status_code(enum yahoo_status s)
{
	int i;
	for(i=0; yahoo_status_codes[i].label; i++)
		if(yahoo_status_codes[i].id == s)
			return yahoo_status_codes[i].label;
	return "Unknown";
}

#define YAHOO_DEBUGLOG ext_yahoo_log

int ext_yahoo_log(char *fmt,...)
{
	va_list ap;

	va_start(ap, fmt);

	vfprintf(stderr, fmt, ap);
	fflush(stderr);
	va_end(ap);
	return 0;
}

#define LOG(x) if(do_yahoo_debug) { YAHOO_DEBUGLOG("%s:%d: ", __FILE__, __LINE__); \
	YAHOO_DEBUGLOG x; \
	YAHOO_DEBUGLOG("\n"); }

#define WARNING(x) if(do_yahoo_debug) { YAHOO_DEBUGLOG("%s:%d: warning: ", __FILE__, __LINE__); \
	YAHOO_DEBUGLOG x; \
	YAHOO_DEBUGLOG("\n"); }

#define print_message(x) { printf x; printf("\n"); }

static yahoo_local_account * ylad = NULL;
static YList * buddies = NULL;

static int expired(time_t timer)
{
	if (timer && curTime >= timer)
		return 1;

	return 0;
}

static void rearm(time_t *timer, int seconds)
{
	time(timer);
	*timer += seconds;
}

FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
int gethostname(char *name, size_t len);

static char * get_local_addresses()
{
	static char addresses[1024];
	char buff[1024];
	char gateway[16];
	char  * c;
	struct hostent * hn;
	int i;
	FILE * f;
	f = popen("netstat -nr", "r");
	if((int)f < 1)
			goto IP_TEST_2;
	while( fgets(buff, sizeof(buff), f)  != NULL ) {
			c = strtok( buff, " " );
			if( (strstr(c, "default") || strstr(c,"0.0.0.0") ) &&
							!strstr(c, "127.0.0" ) )
					break;
	}
	c = strtok( NULL, " " );
	pclose(f);

	strncpy(gateway,c, 16);



	for(i = strlen(gateway); gateway[i] != '.'; i-- )
		gateway[i] = 0;

	gateway[i] = 0;

	for(i = strlen(gateway); gateway[i] != '.'; i-- )
		gateway[i] = 0;

	f = popen("/sbin/ifconfig -a", "r");
	if((int)f < 1)
		goto IP_TEST_2;

	while( fgets(buff, sizeof(buff), f) != NULL ) {
		if( strstr(buff, "inet") && strstr(buff,gateway) )
			break;
	}
	pclose(f);

	c = strtok( buff, " " );
	c = strtok( NULL, " " );

	strncpy ( addresses, c, sizeof(addresses) );
	c = strtok(addresses, ":" );
	strncpy ( buff, c, sizeof(buff) );
	if((c=strtok(NULL, ":")))
		strncpy( buff, c, sizeof(buff) );

	strncpy(addresses, buff, sizeof(addresses));

	return addresses;
		
		
IP_TEST_2:

	gethostname(buff,sizeof(buff));

	hn = gethostbyname(buff);
	if(hn)
		strncpy(addresses, inet_ntoa( *((struct in_addr*)hn->h_addr)), sizeof(addresses) );
	else
		addresses[0] = 0;

	return addresses;
}

static double get_time()
{
	struct timeval ct;
	gettimeofday(&ct, 0);

	/* return time in milliseconds */
	return (ct.tv_sec * 1E3 + ct.tv_usec / 1E3);
}

static int yahoo_ping_timeout_callback()
{
	print_message(("Sending a keep alive message"));
	yahoo_keepalive(ylad->id);
	rearm(&pingTimer, 600);
	return 1;
}

static int yahoo_webcam_timeout_callback(int id)
{
	static unsigned image_num = 0;
	unsigned char *image = NULL;
	unsigned int length = 0;
	unsigned int timestamp = get_time() - webcamStart;
	char fname[1024];
	FILE *f_image = NULL;
	struct stat s_image;

	if (send_webcam_images)
	{
		sprintf(fname, "images/image_%.3d.jpc", image_num++);
		if (image_num > 999) image_num = 0;
		if (stat(fname, &s_image) == -1)
			return -1;
		length = s_image.st_size;
		image = y_new0(unsigned char, length);

		if ((f_image = fopen(fname, "r")) != NULL) {
			fread(image, length, 1, f_image);
			fclose(f_image);
		} else {
			printf("Error reading from %s\n", fname);
		}
	}

	print_message(("Sending a webcam image (%d bytes)", length));
	yahoo_webcam_send_image(id, image, length, timestamp);
	FREE(image);
	rearm(&webcamTimer, 2);
	return 1;
}

YList * conferences = NULL;
typedef struct {
	int id;
	char * room_name;
	char * host;
	YList * members;
	int joined;
} conf_room;

static char * get_buddy_name(char * yid)
{
	YList * b;
	for (b = buddies; b; b = b->next) {
		yahoo_account * ya = b->data;
		if(!strcmp(yid, ya->yahoo_id))
			return ya->name&&*ya->name?ya->name:ya->yahoo_id;
	}

	return yid;
}

static conf_room * find_conf_room_by_name_and_id(int id, const char * name)
{
	YList * l;
	for(l = conferences; l; l=l->next) {
		conf_room * cr = l->data;
		if(cr->id == id && !strcmp(name, cr->room_name)) {
			return cr;
		}
	}

	return NULL;
}

void ext_yahoo_got_conf_invite(int id, char *who, char *room, char *msg, YList *members)
{
	conf_room * cr = y_new0(conf_room, 1);
	cr->room_name = strdup(room);
	cr->host = strdup(who);
	cr->members = members;
	cr->id = id;

	conferences = y_list_append(conferences, cr);

	print_message(("%s has invited you to a conference: %s\n"
				"with the message: %s",
				who, room, msg));

	for(; members; members=members->next)
		print_message(("\t%s", (char *)members->data));
	
}
void ext_yahoo_conf_userdecline(int id, char *who, char *room, char *msg)
{
	YList * l;
	conf_room * cr = find_conf_room_by_name_and_id(id, room);
	
	if(cr)
	for(l = cr->members; l; l=l->next) {
		char * w = l->data;
		if(!strcmp(w, who)) {
			FREE(l->data);
			cr->members = y_list_remove_link(cr->members, l);
			y_list_free_1(l);
			break;
		}
	}

	print_message(("%s declined the invitation to %s\n"
				"with the message: %s", who, room, msg));
}
void ext_yahoo_conf_userjoin(int id, char *who, char *room)
{
	conf_room * cr = find_conf_room_by_name_and_id(id, room);
	if(cr) {
	YList * l = NULL;
	for(l = cr->members; l; l=l->next) {
		char * w = l->data;
		if(!strcmp(w, who))
			break;
	}
	if(!l)
		cr->members = y_list_append(cr->members, strdup(who));
	}

	print_message(("%s joined the conference %s", who, room));

}
void ext_yahoo_conf_userleave(int id, char *who, char *room)
{
	YList * l;
	conf_room * cr = find_conf_room_by_name_and_id(id, room);
	
	if(cr)
	for(l = cr->members; l; l=l->next) {
		char * w = l->data;
		if(!strcmp(w, who)) {
			FREE(l->data);
			cr->members = y_list_remove_link(cr->members, l);
			y_list_free_1(l);
			break;
		}
	}

	print_message(("%s left the conference %s", who, room));
}
void ext_yahoo_conf_message(int id, char *who, char *room, char *msg, int utf8)
{
	char * umsg = msg;

	if(utf8)
		umsg = y_utf8_to_str(msg);

	who = get_buddy_name(who);

	print_message(("%s (in %s): %s", who, room, umsg));

	if(utf8)
		FREE(umsg);
}

static void print_chat_member(struct yahoo_chat_member *ycm) 
{
	printf("%s (%s) ", ycm->id, ycm->alias);
	printf(" Age: %d Sex: ", ycm->age);
	if (ycm->attribs & YAHOO_CHAT_MALE) {
		printf("Male");
	} else if (ycm->attribs & YAHOO_CHAT_FEMALE) {
		printf("Female");
	} else {
		printf("Unknown");
	}
	if (ycm->attribs & YAHOO_CHAT_WEBCAM) {
		printf(" with webcam");
	}

	printf("  Location: %s", ycm->location);
}

void ext_yahoo_chat_cat_xml(int id, char *xml) 
{
	print_message(("%s", xml));
}

void ext_yahoo_chat_join(int id, char *room, char * topic, YList *members)
{
	print_message(("You have joined the chatroom %s with topic %s", room, topic));

	while(members) {
		YList *n = members->next;

		printf("\t");
		print_chat_member(members->data);
		printf("\n");
		FREE(((struct yahoo_chat_member *)members->data)->id);
		FREE(((struct yahoo_chat_member *)members->data)->alias);
		FREE(((struct yahoo_chat_member *)members->data)->location);
		FREE(members->data);
		FREE(members);
		members=n;
	}
}
void ext_yahoo_chat_userjoin(int id, char *room, struct yahoo_chat_member *who)
{
	print_chat_member(who);
	print_message((" joined the chatroom %s", room));
	FREE(who->id);
	FREE(who->alias);
	FREE(who->location);
	FREE(who);
}
void ext_yahoo_chat_userleave(int id, char *room, char *who)
{
	print_message(("%s left the chatroom %s", who, room));
}
void ext_yahoo_chat_message(int id, char *who, char *room, char *msg, int msgtype, int utf8)
{
	char * umsg = msg;
	char * charpos;

	if(utf8)
		umsg = y_utf8_to_str(msg);
	/* Remove escapes */
	charpos = umsg;
	while(*charpos) {
		if (*charpos == 0x1b) {
			*charpos = ' ';
		}
		charpos++;
	}

	if (msgtype == 2) {
		print_message(("(in %s) %s %s", room, who, umsg));
	} else {
		print_message(("(in %s) %s: %s", room, who, umsg));
	}

	if(utf8)
		FREE(umsg);
}

void ext_yahoo_status_changed(int id, char *who, int stat, char *msg, int away)
{
	yahoo_account * ya=NULL;
	YList * b;
	for(b = buddies; b; b = b->next) {
		if(!strcmp(((yahoo_account *)b->data)->yahoo_id, who)) {
			ya = b->data;
			break;
		}
	}
	
	if(msg)
		print_message(("%s (%s) is now %s", ya?ya->name:who, who, msg))
	else if(stat == YAHOO_STATUS_IDLE)
		print_message(("%s (%s) idle for %d:%02d:%02d", ya?ya->name:who, who, 
					away/3600, (away/60)%60, away%60))
	else
		print_message(("%s (%s) is now %s", ya?ya->name:who, who, 
					yahoo_status_code(stat)))

	if(ya) {
		ya->status = stat;
		ya->away = away;
		if(msg) {
			FREE(ya->msg);
			ya->msg = strdup(msg);
		}
	}
}

void ext_yahoo_got_buddies(int id, YList * buds)
{
	while(buddies) {
		FREE(buddies->data);
		buddies = buddies->next;
		if(buddies)
			FREE(buddies->prev);
	}
	for(; buds; buds = buds->next) {
		yahoo_account *ya = y_new0(yahoo_account, 1);
		struct yahoo_buddy *bud = buds->data;
		strncpy(ya->yahoo_id, bud->id, 255);
		if(bud->real_name)
			strncpy(ya->name, bud->real_name, 255);
		strncpy(ya->group, bud->group, 255);
		ya->status = YAHOO_STATUS_OFFLINE;
		buddies = y_list_append(buddies, ya);

/*		print_message(("%s is %s", bud->id, bud->real_name));*/
	}
}

void ext_yahoo_got_ignore(int id, YList * igns)
{
}

void ext_yahoo_got_im(int id, char *who, char *msg, long tm, int stat, int utf8)
{
	char *umsg = msg;

	if(stat == 2) {
		LOG(("Error sending message to %s", who));
		return;
	}

	if(!msg)
		return;

	if(utf8)
		umsg = y_utf8_to_str(msg);
	
	who = get_buddy_name(who);

	if(tm) {
		char timestr[255];

		strncpy(timestr, ctime(&tm), sizeof(timestr));
		timestr[strlen(timestr) - 1] = '\0';

		print_message(("[Offline message at %s from %s]: %s", 
				timestr, who, umsg))
	} else {
		if(!strcmp(umsg, "<ding>")) 
			printf("\a");
		print_message(("%s: %s", who, umsg))
	}

	if(utf8)
		FREE(umsg);
}

void ext_yahoo_rejected(int id, char *who, char *msg)
{
	print_message(("%s has rejected you%s%s", who, 
				(msg?" with the message:\n":"."), 
				(msg?msg:"")));
}

void ext_yahoo_contact_added(int id, char *myid, char *who, char *msg)
{
	char buff[1024];

	snprintf(buff, sizeof(buff), "%s, the yahoo user %s has added you to their contact list", myid, who);
	if(msg) {
		strcat(buff, " with the following message:\n");
		strcat(buff, msg);
		strcat(buff, "\n");
	} else {
		strcat(buff, ".  ");
	}
	strcat(buff, "Do you want to allow this [Y/N]?");

/*	print_message((buff));
	scanf("%c", &choice);
	if(choice != 'y' && choice != 'Y')
		yahoo_reject_buddy(id, who, "Thanks, but no thanks.");
*/
}

void ext_yahoo_typing_notify(int id, char *who, int stat)
{
	if(stat && do_typing_notify)
		print_message(("%s is typing...", who));
}

void ext_yahoo_game_notify(int id, char *who, int stat)
{
}

void ext_yahoo_mail_notify(int id, char *from, char *subj, int cnt)
{
	char buff[1024] = {0};
	
	if(!do_mail_notify)
		return;

	if(from && subj)
		snprintf(buff, sizeof(buff), 
				"You have new mail from %s about %s\n", 
				from, subj);
	if(cnt) {
		char buff2[100];
		snprintf(buff2, sizeof(buff2), 
				"You have %d message%s\n", 
				cnt, cnt==1?"":"s");
		strcat(buff, buff2);
	}

	if(buff[0])
		print_message((buff));
}

void ext_yahoo_got_webcam_image(int id, const char *who,
		unsigned char *image, unsigned int image_size, unsigned int real_size,
		unsigned int timestamp)
{
	static unsigned char *cur_image = NULL;
	static unsigned int cur_image_len = 0;
	static unsigned int image_num = 0;
	FILE* f_image;
	char fname[1024];

	/* copy image part to cur_image */
	if (real_size)
	{
		if (!cur_image) cur_image = y_new0(unsigned char, image_size);
		memcpy(cur_image + cur_image_len, image, real_size);
		cur_image_len += real_size;
	}

	if (image_size == cur_image_len)
	{
		print_message(("Received a image update at %d (%d bytes)",
			 timestamp, image_size));

		/* if we recieved an image then write it to file */
		if (image_size)
		{
			sprintf(fname, "images/%s_%.3d.jpc", who, image_num++);

			if ((f_image = fopen(fname, "w")) != NULL) {
				fwrite(cur_image, image_size, 1, f_image);
				fclose(f_image);
			} else {
				printf("Error writing to %s\n", fname);
			}
			FREE(cur_image);
			cur_image_len = 0;
			if (image_num > 999) image_num = 0;
		}
	}
}

void ext_yahoo_webcam_viewer(int id, char *who, int connect)
{
	switch (connect)
	{
		case 0:
			print_message(("%s has stopped viewing your webcam", who));
			break;
		case 1:
			print_message(("%s has started viewing your webcam", who));
			break;
		case 2:
			print_message(("%s is trying to view your webcam", who));
			yahoo_webcam_accept_viewer(id, who, accept_webcam_viewers);
			break;
	}
}

void ext_yahoo_webcam_closed(int id, char *who, int reason)
{
	switch(reason)
	{
		case 1:
			print_message(("%s stopped broadcasting", who));
			break;
		case 2:
			print_message(("%s cancelled viewing permission", who));
			break;
		case 3:
			print_message(("%s declines permission to view his/her webcam", who));
			break;
		case 4:
			print_message(("%s does not have his/her webcam online", who));
			break;
	}
}

void ext_yahoo_webcam_data_request(int id, int send)
{
	webcam_id = id;

	if (send) {
		print_message(("Got request to start sending images"));
		if (!webcamTimer)
			rearm(&webcamTimer, 2);
	} else {
		print_message(("Got request to stop sending images"));
	}
	send_webcam_images = send;
}

void ext_yahoo_webcam_invite(int id, char *from)
{
	print_message(("Got a webcam invitation from %s", from));
}

void ext_yahoo_webcam_invite_reply(int id, char *from, int accept)
{
	if(accept) {
		print_message(("%s accepted webcam invitation...", from));
	} else {
		print_message(("%s declined webcam invitation...", from));
	}
}

void ext_yahoo_system_message(int id, char *msg)
{
	if(ignore_system)
		return;

	print_message(("Yahoo System Message: %s", msg));
}

void yahoo_logout()
{
	if (ylad->id <= 0) {
		return;
	}

	pingTimer=0;

	while(conferences) {
		YList * n = conferences->next;
		conf_room * cr = conferences->data;
		if(cr->joined)
			yahoo_conference_logoff(ylad->id, NULL, cr->members, cr->room_name);
		FREE(cr->room_name);
		FREE(cr->host);
		while(cr->members) {
			YList *n = cr->members->next;
			FREE(cr->members->data);
			FREE(cr->members);
			cr->members=n;
		}
		FREE(cr);
		FREE(conferences);
		conferences = n;
	}
	
	yahoo_logoff(ylad->id);
	yahoo_close(ylad->id);

	ylad->status = YAHOO_STATUS_OFFLINE;
	ylad->id = 0;

	poll_loop=0;

	print_message(("logged_out"));
}

void ext_yahoo_login(yahoo_local_account * ylad, int login_mode)
{
	LOG(("ext_yahoo_login"));

	ylad->id = yahoo_init_with_attributes(ylad->yahoo_id, ylad->password, 
			"local_host", local_host,
			NULL);
	ylad->status = YAHOO_STATUS_OFFLINE;
	yahoo_login(ylad->id, login_mode);

/*	if (ylad->id <= 0) {
		print_message(("Could not connect to Yahoo server.  Please verify that you are connected to the net and the pager host and port are correctly entered."));
		return;
	}
*/
	rearm(&pingTimer, 600);
}

void ext_yahoo_got_cookies(int id)
{
	/*yahoo_get_yab(id);*/
}

void ext_yahoo_login_response(int id, int succ, char *url)
{
	char buff[1024];

	if(succ == YAHOO_LOGIN_OK) {
		ylad->status = yahoo_current_status(id);
		print_message(("logged in"));
		return;
		
	} else if(succ == YAHOO_LOGIN_PASSWD) {

		snprintf(buff, sizeof(buff), "Could not log into Yahoo service.  Please verify that your username and password are correctly typed.");

	} else if(succ == YAHOO_LOGIN_LOCK) {
		
		snprintf(buff, sizeof(buff), "Could not log into Yahoo service.  Your account has been locked.\nVisit %s to reactivate it.", url);

	} else if(succ == YAHOO_LOGIN_DUPL) {
		snprintf(buff, sizeof(buff), "You have been logged out of the yahoo service, possibly due to a duplicate login.");
	}

	ylad->status = YAHOO_STATUS_OFFLINE;
	print_message((buff));
	yahoo_logout();
	poll_loop=0;
}

void ext_yahoo_error(int id, char *err, int fatal)
{
	fprintf(stdout, "Yahoo Error: ");
	fprintf(stdout, "%s", err);
	/*
	switch(num) {
		case E_CONFNOTAVAIL:
			fprintf(stdout, "%s is not available for the conference", err);
			break;
		case E_IGNOREDUP:
			fprintf(stdout, "%s is already ignored", err);
			break;
		case E_IGNORENONE:
			fprintf(stdout, "%s is not in the ignore list", err);
			break;
		case E_IGNORECONF:
			fprintf(stdout, "%s is in buddy list - cannot ignore ", err);
			break;
	}
	*/
	fprintf(stdout, "\n");
	if(fatal)
		yahoo_logout();
}

void yahoo_set_current_state(int yahoo_state)
{
	if (ylad->status == YAHOO_STATUS_OFFLINE && yahoo_state != YAHOO_STATUS_OFFLINE) {
		ext_yahoo_login(ylad, yahoo_state);
		return;
	} else if (ylad->status != YAHOO_STATUS_OFFLINE && yahoo_state == YAHOO_STATUS_OFFLINE) {
		yahoo_logout();
		return;
	}

	ylad->status = yahoo_state;
	if(yahoo_state == YAHOO_STATUS_CUSTOM) {
		if(ylad->msg)
			yahoo_set_away(ylad->id, yahoo_state, ylad->msg, 1);
		else
			yahoo_set_away(ylad->id, yahoo_state, "delta p * delta x too large", 1);
	} else
		yahoo_set_away(ylad->id, yahoo_state, NULL, 1);
}

int ext_yahoo_connect(char *host, int port)
{
	struct sockaddr_in serv_addr;
	static struct hostent *server;
	static char last_host[256];
	int servfd;
	char **p;

	if(last_host[0] || strcasecmp(last_host, host)!=0) {
		if(!(server = gethostbyname(host))) {
			WARNING(("failed to look up server (%s:%d)\n%d: %s", 
						host, port,
						h_errno, strerror(h_errno)));
			return -1;
		}
		strncpy(last_host, host, 255);
	}

	if((servfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		WARNING(("Socket create error (%d): %s", errno, strerror(errno)));
		return -1;
	}

	LOG(("connecting to %s:%d", host, port));

	for (p = server->h_addr_list; *p; p++)
	{
		memset(&serv_addr, 0, sizeof(serv_addr));
		serv_addr.sin_family = AF_INET;
		memcpy(&serv_addr.sin_addr.s_addr, *p, server->h_length);
		serv_addr.sin_port = htons(port);

		LOG(("trying %s", inet_ntoa(serv_addr.sin_addr)));
		if(connect(servfd, (struct sockaddr *) &serv_addr, 
					sizeof(serv_addr)) == -1) {
			if(errno!=ECONNREFUSED && errno!=ETIMEDOUT && 
					errno!=ENETUNREACH) {
				break;
			}
		} else {
			LOG(("connected"));
			return servfd;
		}
	}

	WARNING(("Could not connect to %s:%d\n%d:%s", host, port, errno, 
				strerror(errno)));
	close(servfd);
	return -1;
}

/*************************************
 * Callback handling code starts here
 */
YList *connections = NULL;
struct _conn {
	int id;
	int fd;
	yahoo_input_condition cond;
	void *data;
	int remove;
};

void ext_yahoo_add_handler(int id, int fd, yahoo_input_condition cond, void *data)
{
	struct _conn *c = y_new0(struct _conn, 1);
	c->id = id;
	c->fd = fd;
	c->cond = cond;
	c->data = data;

	LOG(("Add %d for %d", fd, id));

	connections = y_list_prepend(connections, c);
}

void ext_yahoo_remove_handler(int id, int fd)
{
	YList *l;
	for(l = connections; l; l = y_list_next(l)) {
		struct _conn *c = l->data;
		if(c->fd == fd) {
			/* don't actually remove it, just mark it for removal */
			/* we'll remove when we start the next poll cycle */
			LOG(("Marking id:%d fd:%d for removal", id, fd));
			c->remove = 1;
			return;
		}
	}
}

struct connect_callback_data {
	yahoo_connect_callback callback;
	void * callback_data;
	int id;
};

static void connect_complete(void *data, int source, yahoo_input_condition condition)
{
	struct connect_callback_data *ccd = data;
	int error, err_size = sizeof(error);

	ext_yahoo_remove_handler(-1, source);
	getsockopt(source, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&err_size);

	if(error) {
		close(source);
		source = -1;
	}

	LOG(("Connected fd: %d, error: %d", source, error));

	ccd->callback(source, error, ccd->callback_data);
	FREE(ccd);
}

void yahoo_callback(struct _conn *c, yahoo_input_condition cond)
{
	int ret=1;
	char buff[1024]={0};

	if(c->id < 0) {
		connect_complete(c->data, c->fd, cond);
	} else {
		if(cond & YAHOO_INPUT_READ)
			ret = yahoo_read_ready(c->id, c->fd, c->data);
		if(cond & YAHOO_INPUT_WRITE)
			ret = yahoo_write_ready(c->id, c->fd, c->data);

		if(ret == -1)
			snprintf(buff, sizeof(buff), 
				"Yahoo read error (%d): %s", errno, strerror(errno));
		else if(ret == 0)
			snprintf(buff, sizeof(buff), 
				"Yahoo read error: Server closed socket");

		if(buff[0])
			print_message((buff));
	}
}

int ext_yahoo_connect_async(int id, char *host, int port, 
		yahoo_connect_callback callback, void *data)
{
	struct sockaddr_in serv_addr;
	static struct hostent *server;
	int servfd;
	struct connect_callback_data * ccd;
	int error;

	if(!(server = gethostbyname(host))) {
		errno=h_errno;
		return -1;
	}

	if((servfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		return -1;
	}

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	memcpy(&serv_addr.sin_addr.s_addr, *server->h_addr_list, server->h_length);
	serv_addr.sin_port = htons(port);

	error = connect(servfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));

	LOG(("Trying to connect: fd:%d error:%d", servfd, error));
	if(!error) {
		callback(servfd, 0, data);
		return 0;
	} else if(error == -1 && errno == EINPROGRESS) {
		ccd = calloc(1, sizeof(struct connect_callback_data));
		ccd->callback = callback;
		ccd->callback_data = data;
		ccd->id = id;

		ext_yahoo_add_handler(-1, servfd, YAHOO_INPUT_WRITE, ccd);
		return 1;
	} else {
		close(servfd);
		return -1;
	}
}
/*
 * Callback handling code ends here
 ***********************************/

static void process_commands(char *line)
{
	char *cmd, *to, *msg;

	char *tmp, *start;
	char *copy = strdup(line);

	enum yahoo_status state;

	start = cmd = copy;
	tmp = strchr(copy, ' ');
	if(tmp) {
		*tmp = '\0';
		copy = tmp+1;
	} else {
		copy = NULL;
	}

	if(!strncasecmp(cmd, "MSG", strlen("MSG"))) {
		/* send a message */
		to = copy;
		tmp = strchr(copy, ' ');
		if(tmp) {
			*tmp = '\0';
			copy = tmp+1;
		}
		msg = copy;
		if(to && msg) {
			if(!strcmp(msg, "\a"))
				yahoo_send_im(ylad->id, NULL, to, "<ding>", 0);
			else {
				msg = y_str_to_utf8(msg);
				yahoo_send_im(ylad->id, NULL, to, msg, 1);
				FREE(msg);
			}
		}
	} else if(!strncasecmp(cmd, "CMS", strlen("CMS"))) {
		/* send a message */
		conf_room * cr;
		to = copy;
		tmp = strchr(copy, ' ');
		if(tmp) {
			*tmp = '\0';
			copy = tmp+1;
		}
		msg = copy;
		cr = find_conf_room_by_name_and_id(ylad->id, to);
		if(!cr) {
			print_message(("no such room: %s", copy));
			goto end_parse;
		}
		if(msg)
			yahoo_conference_message(ylad->id, NULL, cr->members, to, msg, 0);
	} else if(!strncasecmp(cmd, "CLS", strlen("CLS"))) {
		YList * l;
		if(copy) {
			conf_room * cr = find_conf_room_by_name_and_id(ylad->id, copy);
			if(!cr) {
				print_message(("no such room: %s", copy));
				goto end_parse;
			}
			print_message(("Room: %s", copy));
			for(l = cr->members; l; l=l->next) {
				print_message(("%s", (char *)l->data))
			}
		} else {
			print_message(("All Rooms:"));
			for(l = conferences; l; l=l->next) {
				conf_room * cr = l->data;
				print_message(("%s", cr->room_name));
			}
		}

	} else if(!strncasecmp(cmd, "CCR", strlen("CCR"))) {
		conf_room * cr = y_new0(conf_room, 1);
		while((tmp = strchr(copy, ' ')) != NULL) {
			*tmp = '\0';
			if(!cr->room_name)
				cr->room_name = strdup(copy);
			else
				cr->members = y_list_append(cr->members,
						strdup(copy));
			copy = tmp+1;
		}
		cr->members = y_list_append(cr->members, strdup(copy));

		if(!cr->room_name || !cr->members) {
			FREE(cr);
		} else {
			cr->id = ylad->id;
			cr->joined = 1;
			conferences = y_list_append(conferences, cr);
			yahoo_conference_invite(ylad->id, NULL, cr->members, cr->room_name, "Join my conference");
			cr->members = y_list_append(cr->members,strdup(ylad->yahoo_id));
		}
	} else if(!strncasecmp(cmd, "CIN", strlen("CIN"))) {
		conf_room * cr;
		char * room=copy;
		YList * l1, *l = NULL;

		while((tmp = strchr(copy, ' ')) != NULL) {
			*tmp = '\0';
			copy = tmp+1;
			l = y_list_append(l, copy);
		}

		cr = find_conf_room_by_name_and_id(ylad->id, room);
		if(!cr) {
			print_message(("no such room: %s", room));
			y_list_free(l);
			goto end_parse;
		}

		for(l1 = l; l1; l1=l1->next) {
			char * w = l1->data;
			yahoo_conference_addinvite(ylad->id, NULL, w, room, cr->members, "Join my conference");
			cr->members = y_list_append(cr->members, strdup(w));
		}
		y_list_free(l);

	} else if(!strncasecmp(cmd, "CLN", strlen("CLN"))) {
		conf_room * cr = find_conf_room_by_name_and_id(ylad->id, copy);
		YList * l;
		if(!cr) {
			print_message(("no such room: %s", copy));
			goto end_parse;
		}

		cr->joined = 1;
		for(l = cr->members; l; l=l->next) {
			char * w = l->data;
			if(!strcmp(w, ylad->yahoo_id))
				break;
		}
		if(!l)
			cr->members = y_list_append(cr->members, strdup(ylad->yahoo_id));
		yahoo_conference_logon(ylad->id, NULL, cr->members, copy);

	} else if(!strncasecmp(cmd, "CLF", strlen("CLF"))) {
		conf_room * cr = find_conf_room_by_name_and_id(ylad->id, copy);
		
		if(!cr) {
			print_message(("no such room: %s", copy));
			goto end_parse;
		}

		yahoo_conference_logoff(ylad->id, NULL, cr->members, copy);

		conferences = y_list_remove(conferences, cr);
		FREE(cr->room_name);
		FREE(cr->host);
		while(cr->members) {
			YList *n = cr->members->next;
			FREE(cr->members->data);
			FREE(cr->members);
			cr->members=n;
		}
		FREE(cr);

	} else if(!strncasecmp(cmd, "CDC", strlen("CDC"))) {
		conf_room * cr;
		char * room = copy;
		tmp = strchr(copy, ' ');
		if(tmp) {
			*tmp = '\0';
			copy = tmp+1;
			msg = copy;
		} else {
			msg = "Thanks, but no thanks!";
		}
		
		cr = find_conf_room_by_name_and_id(ylad->id, room);
		if(!cr) {
			print_message(("no such room: %s", room));
			goto end_parse;
		}

		yahoo_conference_decline(ylad->id, NULL, cr->members, room,msg);

		conferences = y_list_remove(conferences, cr);
		FREE(cr->room_name);
		FREE(cr->host);
		while(cr->members) {
			YList *n = cr->members->next;
			FREE(cr->members->data);
			FREE(cr->members);
			cr->members=n;
		}
		FREE(cr);


	} else if(!strncasecmp(cmd, "CHL", strlen("CHL"))) {
		int roomid;
		roomid = atoi(copy);
		yahoo_get_chatrooms(ylad->id, roomid);
	} else if(!strncasecmp(cmd, "CHJ", strlen("CHJ"))) {
		char *roomid, *roomname;
/* Linux, FreeBSD, Solaris:1 */
/* 1600326591 */
		roomid = copy;
		tmp = strchr(copy, ' ');
		if(tmp) {
			*tmp = '\0';
			copy = tmp+1;
		}
		roomname = copy;
		if(roomid && roomname) {
			yahoo_chat_logon(ylad->id, NULL, roomname, roomid);
		}

	} else if(!strncasecmp(cmd, "CHM", strlen("CHM"))) {
		char *msg, *roomname;
		roomname = copy;
		tmp = strstr(copy, "  ");
		if(tmp) {
			*tmp = '\0';
			copy = tmp+2;
		}
		msg = copy;
		if(roomname && msg) {
			yahoo_chat_message(ylad->id, NULL, roomname, msg, 1, 0);
		}

	} else if(!strncasecmp(cmd, "CHX", strlen("CHX"))) {
		yahoo_chat_logoff(ylad->id, NULL);
	} else if(!strncasecmp(cmd, "STA", strlen("STA"))) {
		if(isdigit(copy[0])) {
			state = (enum yahoo_status)atoi(copy);
			copy = strchr(copy, ' ');
			if(state == 99) {
				if(copy)
					msg = copy;
				else
					msg = "delta x * delta p too large";
			} else
				msg = NULL;
		} else {
			state = YAHOO_STATUS_CUSTOM;
			msg = copy;
		}

		yahoo_set_away(ylad->id, state, msg, 1);

	} else if(!strncasecmp(cmd, "OFF", strlen("OFF"))) {
		/* go offline */
		printf("Going offline\n");
		poll_loop=0;
	} else if(!strncasecmp(cmd, "IDS", strlen("IDS"))) {
		/* print identities */
		const YList * ids = yahoo_get_identities(ylad->id);
		printf("Identities: ");
		for(; ids; ids = ids->next)
			printf("%s, ", (char *)ids->data);
		printf("\n");
	} else if(!strncasecmp(cmd, "AID", strlen("AID"))) {
		/* activate identity */
		yahoo_set_identity_status(ylad->id, copy, 1);
	} else if(!strncasecmp(cmd, "DID", strlen("DID"))) {
		/* deactivate identity */
		yahoo_set_identity_status(ylad->id, copy, 0);
	} else if(!strncasecmp(cmd, "LST", strlen("LST"))) {
		YList * b = buddies;
		for(; b; b=b->next) {
			yahoo_account * ya = b->data;
			if(ya->status == YAHOO_STATUS_OFFLINE)
				continue;
			if(ya->msg)
				print_message(("%s (%s) is now %s", ya->name, ya->yahoo_id, 
							ya->msg))
			else
				print_message(("%s (%s) is now %s", ya->name, ya->yahoo_id, 
						yahoo_status_code(ya->status)))
		}
	} else if(!strncasecmp(cmd, "NAM", strlen("NAM"))) {
		struct yab * yab;
		
		to = copy;
		tmp = strchr(copy, ' ');
		if(tmp) {
			*tmp = '\0';
			copy = tmp+1;
		}
		msg = copy;

		if(to && msg) {
			yab = y_new0(struct yab, 1);
			yab->id = to;
			yab->fname = msg;
			yahoo_set_yab(ylad->id, yab);
			FREE(yab);
		}
	} else if(!strncasecmp(cmd, "WCAM", strlen("WCAM"))) {
		if (copy)
		{
			printf("Viewing webcam (%s)\n", copy);
			webcam_direction = YAHOO_WEBCAM_DOWNLOAD;
			yahoo_webcam_get_feed(ylad->id, copy);
		} else {
			printf("Starting webcam\n");
			webcam_direction = YAHOO_WEBCAM_UPLOAD;
			yahoo_webcam_get_feed(ylad->id, NULL);
		}
	} else if(!strncasecmp(cmd, "WINV", strlen("WINV"))) {
		printf("Inviting %s to view webcam\n", copy);
		yahoo_webcam_invite(ylad->id, copy);
	} else {
		fprintf(stderr, "Unknown command: %s\n", cmd);
	}

end_parse:
	FREE(start);
}

static void local_input_callback(int source)
{
	char line[1024] = {0};
	int i;
	char c;
	i=0; c=0;
	do {
		if(read(source, &c, 1) <= 0)
			c='\0';
		if(c == '\r')
			continue;
		if(c == '\n')
			break;
		if(c == '\b') {
			if(!i)
				continue;
			c = '\0';
			i--;
		}
		if(c) {
			line[i++] = c;
			line[i]='\0';
		}
	} while(i<1023 && c != '\n');

	if(line[0])
		process_commands(line);
}

int main(int argc, char * argv[])
{
	int status;
	int log_level;
	int lfd=0;

	fd_set inp, outp;
	struct timeval tv;


	int fd_stdin = fileno(stdin);
	YList *l=connections;

	ylad = y_new0(yahoo_local_account, 1);

	local_host = strdup(get_local_addresses());

	printf("Yahoo Id: ");
	scanf("%s", ylad->yahoo_id);
	printf("Password: ");
	{
		tcflag_t oflags;
		struct termios term;
		tcgetattr(fd_stdin, &term);
		oflags = term.c_lflag;
		term.c_lflag = oflags & ~(ECHO | ECHOK | ICANON);
		term.c_cc[VTIME] = 1;
		tcsetattr(fd_stdin, TCSANOW, &term);
		
		scanf("%s", ylad->password);

		term.c_lflag = oflags;
		term.c_cc[VTIME] = 0;
		tcsetattr(fd_stdin, TCSANOW, &term);
	}
	printf("\n");

	printf("Initial Status: ");
	scanf("%d", &status);

	printf("Log Level: ");
	scanf("%d", &log_level);
	do_yahoo_debug=log_level;

	register_callbacks();
	yahoo_set_log_level(log_level);

	ext_yahoo_login(ylad, status);

	while(poll_loop) {
		FD_ZERO(&inp);
		FD_ZERO(&outp);
		FD_SET(fd_stdin, &inp);
		tv.tv_sec=1;
		tv.tv_usec=0;
		lfd=0;

		for(l=connections; l; ) {
			struct _conn *c = l->data;
			if(c->remove) {
				YList *n = y_list_next(l);
				LOG(("Removing id:%d fd:%d", c->id, c->fd));
				connections = y_list_remove_link(connections, l);
				y_list_free_1(l);
				free(c);
				l=n;
			} else {
				if(c->cond & YAHOO_INPUT_READ)
					FD_SET(c->fd, &inp);
				if(c->cond & YAHOO_INPUT_WRITE)
					FD_SET(c->fd, &outp);
				if(lfd < c->fd)
					lfd = c->fd;
				l = y_list_next(l);
			}
		}

		select(lfd + 1, &inp, &outp, NULL, &tv);
		time(&curTime);

		if(FD_ISSET(fd_stdin, &inp))	local_input_callback(0);

		for(l = connections; l; l = y_list_next(l)) {
			struct _conn *c = l->data;
			if(c->remove)
				continue;
			if(FD_ISSET(c->fd, &inp))
				yahoo_callback(c, YAHOO_INPUT_READ);
			if(FD_ISSET(c->fd, &outp))
				yahoo_callback(c, YAHOO_INPUT_WRITE);
		}

		if(expired(pingTimer))		yahoo_ping_timeout_callback();
		if(expired(webcamTimer))	yahoo_webcam_timeout_callback(webcam_id);
	}
	LOG(("Exited loop"));

	while(connections) {
		YList *tmp = connections;
		struct _conn * c = connections->data;
		close(c->fd);
		FREE(c);
		connections = y_list_remove_link(connections, connections);
		y_list_free_1(tmp);
	}

	yahoo_logout();

	FREE(ylad);

	return 0;
}

void ext_yahoo_got_file(int id, char *who, char *url, long expires, char *msg, char *fname, unsigned long fesize)
{
}

void ext_yahoo_got_identities(int id, YList * ids)
{
}

static void register_callbacks()
{
#ifdef USE_STRUCT_CALLBACKS
	static struct yahoo_callbacks yc;

	yc.ext_yahoo_login_response = ext_yahoo_login_response;
	yc.ext_yahoo_got_buddies = ext_yahoo_got_buddies;
	yc.ext_yahoo_got_ignore = ext_yahoo_got_ignore;
	yc.ext_yahoo_got_identities = ext_yahoo_got_identities;
	yc.ext_yahoo_got_cookies = ext_yahoo_got_cookies;
	yc.ext_yahoo_status_changed = ext_yahoo_status_changed;
	yc.ext_yahoo_got_im = ext_yahoo_got_im;
	yc.ext_yahoo_got_conf_invite = ext_yahoo_got_conf_invite;
	yc.ext_yahoo_conf_userdecline = ext_yahoo_conf_userdecline;
	yc.ext_yahoo_conf_userjoin = ext_yahoo_conf_userjoin;
	yc.ext_yahoo_conf_userleave = ext_yahoo_conf_userleave;
	yc.ext_yahoo_conf_message = ext_yahoo_conf_message;
	yc.ext_yahoo_chat_cat_xml = ext_yahoo_chat_cat_xml;
	yc.ext_yahoo_chat_join = ext_yahoo_chat_join;
	yc.ext_yahoo_chat_userjoin = ext_yahoo_chat_userjoin;
	yc.ext_yahoo_chat_userleave = ext_yahoo_chat_userleave;
	yc.ext_yahoo_chat_message = ext_yahoo_chat_message;
	yc.ext_yahoo_got_webcam_image = ext_yahoo_got_webcam_image;
	yc.ext_yahoo_webcam_invite = ext_yahoo_webcam_invite;
	yc.ext_yahoo_webcam_invite_reply = ext_yahoo_webcam_invite_reply;
	yc.ext_yahoo_webcam_closed = ext_yahoo_webcam_closed;
	yc.ext_yahoo_webcam_viewer = ext_yahoo_webcam_viewer;
	yc.ext_yahoo_webcam_data_request = ext_yahoo_webcam_data_request;
	yc.ext_yahoo_got_file = ext_yahoo_got_file;
	yc.ext_yahoo_contact_added = ext_yahoo_contact_added;
	yc.ext_yahoo_rejected = ext_yahoo_rejected;
	yc.ext_yahoo_typing_notify = ext_yahoo_typing_notify;
	yc.ext_yahoo_game_notify = ext_yahoo_game_notify;
	yc.ext_yahoo_mail_notify = ext_yahoo_mail_notify;
	yc.ext_yahoo_system_message = ext_yahoo_system_message;
	yc.ext_yahoo_error = ext_yahoo_error;
	yc.ext_yahoo_log = ext_yahoo_log;
	yc.ext_yahoo_add_handler = ext_yahoo_add_handler;
	yc.ext_yahoo_remove_handler = ext_yahoo_remove_handler;
	yc.ext_yahoo_connect = ext_yahoo_connect;
	yc.ext_yahoo_connect_async = ext_yahoo_connect_async;

	yahoo_register_callbacks(&yc);
	
#endif
}

