/*
 * GTK ASTerisk MANager
 * Copyright (C) 2002, Digium
 *
 * Written by Mark Spencer <markster@digium.com>
 *
 * Distributed under GNU GPL 
 *
 * All Rights Reserved
 */

#ifndef _WIN32
#ifdef __FreeBSD__
#include <sys/types.h>
#endif
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/select.h>
#else
#include <winsock.h>
#include "win32dep.h"
#endif

#include <stdio.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <ctype.h>
#include <glib.h>
#include <time.h>
#include "gastman.h"

#include <asterisk/manager.h>
#include <asterisk/md5.h>

#define MAX_HEADERS 256
#define MAX_LEN 1024

#define CHAN_MAGIC 0xc649
#define EXTEN_MAGIC 0x3539
#define TRASH_MAGIC 0x7945
#define QUEUE_MAGIC 0x053b
#define QUEUENAME_MAGIC 0x67a4

#define MAX_HIST 1000

static char *history[MAX_HIST];
static int maxhist;
static int curhistpos;

static void shrink_hist(void)
{
	int x;
	if (history[0])
		free(history[0]);
	for (x=0;x<maxhist - 1;x++)
		history[x] = history[x + 1];
	history[x] = NULL;
	maxhist--;
	if (curhistpos > maxhist)
		maxhist = curhistpos;
}

static void hist_append(const char *s)
{
	if (maxhist >= MAX_HIST - 1)
		shrink_hist();
	curhistpos = maxhist;
	history[curhistpos] = strdup(s);
	maxhist++;
	curhistpos = maxhist;
}

static struct mansession {
	struct sockaddr_in sin;
	int fd;
	char inbuf[MAX_LEN];
	int inlen;
} session;

struct message {
	int hdrcount;
	int gettingdata;
	char headers[MAX_HEADERS][MAX_LEN];
} message;

static struct ast_chan {
	int magic;
	char name[80];
	char exten[20];
	char context[20];
	char priority[20];
	char callerid[40];
	char icon[80];
	char state[10];
	char ident[80];
	char link[80];
	struct gui_object *obj;
	struct gui_object *eobj;
	struct ast_chan *next;
} *chans;

static struct ast_exten {
	int magic;
	char name[256];
	char extens[256];
	char chans[256];
	char icon[80];
	char ident[80];
	char label[80];
	int activechan;
	struct gui_object *obj;
	struct ast_exten *next;
} *extens;

static struct ast_queue {
	int magic;
	char name[256];
	char icon[80];
	char callerid[256];
	char queue[256];
	int position;
	time_t start;
	struct gui_queue *obj;
	struct ast_queue *next;
} *queues;

static struct ast_queuename {
	int magic;
	char name[256];
	int current;
	int max;
	struct gui_queue *obj;
	struct ast_queuename *next;
} *queuenames;

static struct ast_wastebasket {
	int magic;
} wastebasket = { TRASH_MAGIC };

static struct ast_chan *current = NULL;
static struct ast_queue *currentq = NULL;

static char curhost[256];

static int debug = 0;

static int chan_match(char *real, char *pat)
{
	char copy[80];
	char tech[80];
	char *s, *t;
	strncpy(copy, real, sizeof(copy));
	strncpy(tech, real, sizeof(tech));
	s = strchr(tech, '/');
	if (s)
		*s = '\0';
	if (!strcasecmp(tech, "Zap")) {
		/* Zap Channel, match before - */
		s = strchr(copy, '-');
		if (s)
			*s = '\0';
		if (!strcmp(pat, copy))
			return 1;
	} else if (!strcasecmp(tech, "Asyncgoto")) {
	 	/* Not going to match for sure */
		return 0;
	} else if (!strcasecmp(tech, "Agent")) {
		if (!strcasecmp(pat, copy))
			return 1;
		return 0;
	} else if (!strcasecmp(tech, "Phone")) {
		if (!strcasecmp(pat, copy))
			return 1;
		return 0;
	} else if (!strcasecmp(tech, "Console")) {
		if (!strcasecmp(pat, copy))
			return 1;
		return 0;
	} else if (!strcasecmp(tech, "OSS")) {
		if (!strcasecmp(pat, copy))
			return 1;
		return 0;
	} else if (!strncasecmp(tech, "IAX2", 4)) {
		/* HEAD */
		s = strrchr(copy, '-');
		/* 1_0 */
		t = strrchr(copy, '/');

		if (s && s > t) {
			*s = '\0';
		} else if (t) {
			*t = '\0';
		}

		/* If the pattern doesn't contain an @, then match only against the host portion */
		if ((s=strchr(copy, '@')) && (strchr(pat,'@') == NULL)) {
			char newcopy[80] = "IAX2/";
			strncat(newcopy, s + 1, sizeof(newcopy) - 5);
			strncpy(copy, newcopy, sizeof(copy));
		}

		if (!strcasecmp(pat, copy)) {
			return 1;
		}
	} else if (!strncasecmp(tech, "IAX[", 4)) {
		s = strchr(copy, '/');
		if (s)
			*s = '\0';
		if (!strcasecmp(pat, copy)) {
			return 1;
		}
	} else if (!strncasecmp(tech, "IAX", 3)) {
		s = strchr(copy, '/');
		if (s)
			*s = '\0';
		if (!strcasecmp(pat, copy)) {
			return 1;
		}
	} else if (!strncasecmp(tech, "SIP", 3)) {
		/* SIP Channel, match before - */
		s = strchr(copy, '-');
		if (s)
			*s = '\0';
		if (!strcmp(pat, copy))
			return 1;
	} else if (!strncasecmp(tech, "MGCP", 4)) {
		/* MGCP Channel, match before - */
		s = strchr(copy, '-');
		if (s)
			*s = '\0';
		if (!strcmp(pat, copy))
			return 1;
	} else if (!strncasecmp(tech, "Local", 5)) {
		/* Local Channel, match before - */
		s = strchr(copy, '-');
		if (s)
			*s = '\0';
		if (!strcmp(pat, copy))
			return 1;
	} else if (!strncasecmp(tech, "H323", 4)) {
		/* H323 Channel, match before : */
		s = strchr(copy, ':');
		if (s)
			*s = '\0';
		if (!strncmp(pat, copy, strlen(pat)))
			return 1;
	} else {
		fprintf(stderr, "Dunno how to tell if %s is %s\n", real, pat);
	}
	return 0;
}

static int ast_extension_match(char *pattern, char *data)
{
	int match;
	/* If they're the same return */
	if (!strcasecmp(pattern, data))
		return 1;
	/* All patterns begin with _ */
	if (pattern[0] != '_') 
		return 0;
	/* Start optimistic */
	match=1;
	pattern++;
	while(match && *data && *pattern && (*pattern != '/')) {
		switch(toupper(*pattern)) {
		case 'N':
			if ((*data < '2') || (*data > '9'))
				match=0;
			break;
		case 'X':
			if ((*data < '0') || (*data > '9'))
				match = 0;
			break;
		case '.':
			/* Must match */
			return 1;
		case ' ':
		case '-':
			/* Ignore these characters */
			data--;
			break;
		default:
			if (*data != *pattern)
				match =0;
		}
		data++;
		pattern++;
	}
	/* Must be at the end of both */
	if (*data || (*pattern && (*pattern != '/')))
		match = 0;
	return match;
}

static struct ast_exten *exten_match(char *exten, char *context)
{
	struct ast_exten *cur;
	char tmp[256], *con, *cure;
	cur = extens;
	while(cur) {
		strncpy(tmp, cur->extens, sizeof(tmp) - 1);
		cure = strtok(tmp, ",");
		while (cure) {
			while((*cure < 33) && *cure) cure++;
			con = strchr(cure, '@');
			if (con) {
				*con = '\0';
				con++;
			}
			if (ast_extension_match(cure, exten) && (!con || !strcasecmp(context, con)))
				return cur;
			cure = strtok(NULL, ",");
		}
		cur = cur->next;
	}
	return NULL;
}

static char *find_icon_by_channel(char *name)
{
	struct ast_exten *cur;
	char tmp[256], *c;
	cur = extens;
	while(cur) {
		strncpy(tmp, cur->chans, sizeof(tmp) - 1);
		c = strtok(tmp, ",");
		while(c) {
			while(*c && (*c < 33))
				c++;
			if (chan_match(name, c))
				return cur->icon;
			c = strtok(NULL, ",");
		}
		cur = cur->next;
	}
	if (rand() & 1024)
		return "manonphone";
	else
		return "womanonphone";
}

static void check_extens(void)
{
	/* XXX This should be done much more efficiently */
	struct ast_chan *curchan;
	struct ast_exten *curext;
	char tmp[256], *c;
	curext = extens;
	while(curext) {
		curext->activechan = 0;
		curext = curext->next;
	}
	curext = extens;
	while(curext) {
		curchan = chans;
		strncpy(tmp, curext->chans, sizeof(tmp) - 1);
		c = strtok(tmp, ",");
		while(c) {
			while(*c && (*c < 33))
				c++;
			curchan = chans;
			while(curchan) {
				if (chan_match(curchan->name, c)) {
					curext->activechan = 1;
					break;
				}
				curchan = curchan->next;
			}
			if (curext->activechan)
				break;
			c = strtok(NULL, ",");
		}
		curext = curext->next;
	}
	curext = extens;
	while(curext) {
		gui_object_set_led(curext->obj, curext->activechan);
		curext = curext->next;
	}
}

static struct ast_queuename *find_queuename(char *name);

static struct ast_queue *find_queue(char *name, char *queuen)
{
	struct ast_queue *prev = NULL, *queue = queues;
	struct ast_queuename *qn;
	char *icon;
	if (!strlen(name)) 
		return NULL;
	while(queue) {
		if (!strcmp(name, queue->name))
			return queue;
		prev = queue;
		queue = queue->next;
	}
	queue = malloc(sizeof(struct ast_queue));
	if (queue) {
		icon = find_icon_by_channel(name);
		memset(queue, 0, sizeof(struct ast_queue));
		strncpy(queue->name, name, sizeof(queue->name) - 1);
		strncpy(queue->queue, queuen, sizeof(queue->queue) - 1);
		queue->magic = QUEUE_MAGIC;
		strncpy(queue->icon, icon, sizeof(queue->icon) - 1);
		queue->obj = gui_add_queue(name, queuen, icon, queue, TYPE_QUEUE);
		if (prev) 
			prev->next = queue;
		else
			queues = queue;
		qn = find_queuename(queuen);
		if (qn) {
			qn->current++;
			gui_queue_set_queueinfo(qn->obj, qn->current, qn->max);
		}
	}
	return queue;
}

static struct ast_queuename *find_queuename(char *name)
{
	struct ast_queuename *prev = NULL, *queue = queuenames;
	if (!strlen(name)) 
		return NULL;
	while(queue) {
		if (!strcmp(name, queue->name))
			return queue;
		prev = queue;
		queue = queue->next;
	}
	queue = malloc(sizeof(struct ast_queuename));
	if (queue) {
		memset(queue, 0, sizeof(struct ast_queuename));
		strncpy(queue->name, name, sizeof(queue->name) - 1);
		queue->magic = QUEUENAME_MAGIC;
		queue->obj = gui_add_queue(NULL, name, NULL, queue, TYPE_QUEUENAME);
		if (prev) 
			prev->next = queue;
		else
			queuenames = queue;
	}
	return queue;
}

static struct ast_chan *find_chan(char *name)
{
	struct ast_chan *prev = NULL, *chan = chans;
	char *icon;
	if (!strlen(name)) 
		return NULL;
	while(chan) {
		if (!strcmp(name, chan->name))
			return chan;
		prev = chan;
		chan = chan->next;
	}
	chan = malloc(sizeof(struct ast_chan));
	if (chan) {
		icon = find_icon_by_channel(name);
		memset(chan, 0, sizeof(struct ast_chan));
		strncpy(chan->name, name, sizeof(chan->name) - 1);
		snprintf(chan->ident, sizeof(chan->ident), "[%s]chan[%s]", curhost, chan->name);
		chan->magic = CHAN_MAGIC;
		strncpy(chan->icon, icon, sizeof(chan->icon) - 1);
		if (prev) 
			prev->next = chan;
		else
			chans = chan;
		chan->obj = gui_add_object(name, name, icon, chan->ident, chan, 0, 0, TYPE_CALL);
	}
	check_extens();
	return chan;
}

static void del_chan(char *name)
{
	struct ast_chan *prev = NULL, *chan = chans;
	while(chan) {
		if (!strcmp(name, chan->name)) {
			if (current == chan)
				current = NULL;
			if (prev)
				prev->next = chan->next;
			else
				chans = chan->next;
			gui_del_object(chan->obj);
			chan->magic = 0;
			free(chan);
			check_extens();
			return;
		}
		prev = chan;
		chan = chan->next;
	}
	
}

static void del_queue(char *name)
{
	struct ast_queue *prev = NULL, *queue = queues;
	struct ast_queuename *qn;
	while(queue) {
		if (!strcmp(name, queue->name)) {
			qn = find_queuename(queue->queue);
			if (qn) {
				qn->current--;
				gui_queue_set_queueinfo(qn->obj, qn->current, qn->max);
			}
			if (currentq == queue)
				currentq = NULL;
			if (prev)
				prev->next = queue->next;
			else
				queues = queue->next;
			gui_del_queue(queue->obj);
			queue->magic = 0;
			free(queue);
			return;
		}
		prev = queue;
		queue = queue->next;
	}
	
}

static void fdprintf(int fd, char *fmt, ...)
{
	char stuff[4096];
	va_list ap;
	va_start(ap, fmt);
	vsnprintf(stuff, sizeof(stuff), fmt, ap);
	va_end(ap);
#ifndef _WIN32
	write(fd, stuff, strlen(stuff));
#else
	send(fd, stuff, strlen(stuff), 0);
#endif
}

static char *get_header(struct message *m, char *var)
{
	char cmp[80];
	int x;
	snprintf(cmp, sizeof(cmp), "%s: ", var);
	for (x=0;x<m->hdrcount;x++)
		if (!strncasecmp(cmp, m->headers[x], strlen(cmp)))
			return m->headers[x] + strlen(cmp);
	return "";
}

char *gastman_curhost(void)
{
	return curhost;
}

static void update_channel(struct ast_chan *chan)
{
	char tmp[1024] = "";
	char tmp2[80];
	struct ast_exten *ext;
	struct gui_object *obj = NULL;
	struct ast_chan *lchan = NULL;
	if (strlen(chan->callerid)) {
		snprintf(tmp2, sizeof(tmp2), "Caller: %s\n", chan->callerid);
		strcat(tmp, tmp2);
	}
	if (strlen(chan->exten)) {
		snprintf(tmp2, sizeof(tmp2), "At: %s@%s:%s\n", chan->exten, chan->context, chan->priority);
		strcat(tmp, tmp2);
		if (strlen(chan->name)) {
			snprintf(tmp2, sizeof(tmp2), "Channel: %s\n", chan->name);
			strcat(tmp, tmp2);
		}
	}
	if (chan->obj != NULL)
		gui_object_set_tooltip(chan->obj, tmp);
	if (strlen(chan->exten))
		snprintf(tmp, sizeof(tmp), "%s@%s:%s", chan->exten, chan->context, chan->priority);
	else
		strcpy(tmp, "");
	if (strlen(chan->link))
		lchan = find_chan(chan->link);
	ext = exten_match(chan->exten, chan->context);
	gui_object_set_callinfo(chan->obj, strlen(chan->callerid) ? chan->callerid : "<unknown>", 
					chan->link, tmp, lchan ? lchan->obj : NULL, ext ? ext->obj : NULL);
	if (ext)
		obj = ext->obj;
	if (obj != chan->eobj) {
		if (chan->eobj)
			gui_unlink(chan->obj, chan->eobj);
		chan->eobj = obj;
		if (obj) {
			gui_link(chan->obj, chan->eobj);
			gui_move_near(chan->eobj, chan->obj);
		}
	}
	/* Update location if appropriate */
	if (current == chan)
		gastman_object_select(current);
}

static void update_channels(void)
{
	struct ast_chan *cur = chans;
	while(cur) {
		update_channel(cur);
		cur = cur->next;
	}
}

void gastman_queue_select(void *pvt)
{
	struct ast_queue *q = pvt;
	printf("Selected '%s', CID '%s', waiting for %ld seconds, position %d\n",
		q->name, q->callerid, time(NULL) - q->start, q->position);
}

void gastman_object_select(void *pvt)
{
	struct ast_chan *chan = pvt;
	struct ast_exten *exten = pvt;
	char tmp[1024] = "";
	char tmp2[80];
	char tmp3[80];
	
	if (chan->magic == CHAN_MAGIC) {
		if (strlen(chan->callerid)) 
			snprintf(tmp2, sizeof(tmp2), "%s (%s)", chan->name, chan->callerid);
		else
			snprintf(tmp2, sizeof(tmp2), "%s", chan->name);
		if (strlen(chan->exten)) 
		 	snprintf(tmp3, sizeof(tmp3), " is at %s@%s:%s", chan->exten, chan->context, chan->priority);
		else
			strcpy(tmp3, "");
		
		snprintf(tmp, sizeof(tmp), "%s%s", tmp2, tmp3);
	} else if (chan->magic == EXTEN_MAGIC) {
		snprintf(tmp, sizeof(tmp), "%s at Extension %s", exten->label, exten->name);
	}
	current = chan;
	gui_status(tmp);
}

static char *get_callerid(struct message *m)
{
	char *callerid, *calleridname;
	static char combined[256];
	callerid = get_header(m, "Callerid");
	calleridname = get_header(m, "CalleridName");
	if (strlen(callerid) && strlen(calleridname))
		snprintf(combined, sizeof(combined), "\"%s\" <%s>", calleridname, callerid);
	else if (strlen(callerid))
		strncpy(combined, callerid, sizeof(combined) - 1);
	else
		combined[0] = '\0';
	return combined;
}

static int event_newstate(struct mansession *s, struct message *m)
{
	struct ast_chan *chan;
	char *callerid;
	chan = find_chan(get_header(m, "Channel"));
	callerid = get_callerid(m);
	if (strlen(callerid))
		strncpy(chan->callerid, callerid, sizeof(chan->callerid) - 1);
	strncpy(chan->state, get_header(m, "State"), sizeof(chan->state) - 1);
	update_channel(chan);
	return 0;
}

static int event_newexten(struct mansession *s, struct message *m)
{
	struct ast_chan *chan;
	chan = find_chan(get_header(m, "Channel"));
	strncpy(chan->exten, get_header(m, "Extension"), sizeof(chan->exten) - 1);
	strncpy(chan->context, get_header(m, "Context"), sizeof(chan->context) - 1);
	strncpy(chan->priority, get_header(m, "Priority"), sizeof(chan->priority) - 1);
	update_channel(chan);
	return 0;
}

static int event_newchannel(struct mansession *s, struct message *m)
{
	struct ast_chan *chan;
	char tmp[256];
	chan = find_chan(get_header(m, "Channel"));
	strncpy(chan->state, get_header(m, "State"), sizeof(chan->state) - 1);
	strncpy(chan->callerid, get_callerid(m), sizeof(chan->callerid) - 1);
	update_channel(chan);
	if (strlen(chan->callerid))
		snprintf(tmp, sizeof(tmp), "New channel %s from %s", chan->name, chan->callerid);
	else
		snprintf(tmp, sizeof(tmp), "New channel %s", chan->name);
	gui_status(tmp);
	return 0;
}

static int event_status(struct mansession *s, struct message *m)
{
	struct ast_chan *chan, *chan2;
	chan = find_chan(get_header(m, "Channel"));
	strncpy(chan->state, get_header(m, "State"), sizeof(chan->state) - 1);
	strncpy(chan->callerid, get_callerid(m), sizeof(chan->callerid) - 1);
	strncpy(chan->exten, get_header(m, "Extension"), sizeof(chan->exten) - 1);
	strncpy(chan->context, get_header(m, "Context"), sizeof(chan->context) - 1);
	strncpy(chan->priority, get_header(m, "Priority"), sizeof(chan->priority) - 1);
	strncpy(chan->link, get_header(m, "Link"), sizeof(chan->link) - 1);
	if (strlen(chan->link) && (chan2 = find_chan(chan->link))) {
		gui_link(chan->obj, chan2->obj);
	}
	update_channel(chan);
	return 0;
}

static int event_newcallerid(struct mansession *s, struct message *m)
{
	struct ast_chan *chan;
	chan = find_chan(get_header(m, "Channel"));
	strncpy(chan->callerid, get_callerid(m), sizeof(chan->callerid) - 1);
	update_channel(chan);
	return 0;
}

static int event_hangup(struct mansession *s, struct message *m)
{
	char *name = get_header(m, "Channel");
	char tmp[80];
	del_chan(name);
	snprintf(tmp, sizeof(tmp), "%s hungup", name);
	gui_status(tmp);
	return 0;
}

static int event_ignore(struct mansession *s, struct message *m)
{
	return 0;
}

static int event_rename(struct mansession *s, struct message *m)
{
	struct ast_chan *chan, *chan2;
	char *icon;
	chan = find_chan(get_header(m, "Oldname"));
	strncpy(chan->name, get_header(m, "Newname"), sizeof(chan->name) - 1);
#if 0
	printf("%s is now %s\n", get_header(m, "Oldname"), get_header(m, "Newname"));
#endif	
	gui_object_set_text(chan->obj, chan->name);
	/* Add object back after it changes names */
	gui_del_object(chan->obj);
	icon = find_icon_by_channel(chan->name);
	if (icon)
		strncpy(chan->icon, icon, sizeof(chan->icon) - 1);
	chan->obj = gui_add_object(chan->name, chan->name, chan->icon, chan->ident, chan, 0, 0, TYPE_CALL);
	update_channel(chan);
	if (strlen(chan->link) && (chan2 = find_chan(chan->link))) {
		gui_link(chan->obj, chan2->obj);
	}
	check_extens();
	return 0;
}

static void alloc_exten(char *exten, char *chans, char *icon, char *label, int size, int x, int y)
{
	struct ast_exten *cur;
	char tmp[256];
	cur = malloc(sizeof(struct ast_exten));
	if (cur) {
		memset(cur, 0, sizeof(struct ast_exten));
		cur->magic = EXTEN_MAGIC;
		strncpy(cur->name, exten, sizeof(cur->name) - 1);
		strtok(cur->name, ",");
		strncpy(cur->extens, exten, sizeof(cur->extens) - 1);
		strncpy(cur->icon, icon, sizeof(cur->icon) - 1);
		strncpy(cur->chans, chans, sizeof(cur->chans) - 1);
		strncpy(cur->label, label, sizeof(cur->label) - 1);
		snprintf(cur->ident, sizeof(cur->ident), "[%s]exten[%s:%s]", curhost, cur->name, cur->chans);
		if ((cur->name[0] != '_') && (cur->name[0] != '\0')) {
			if (strlen(cur->label))
				snprintf(tmp, sizeof(tmp), "%s - %s", cur->name, cur->label);
			else
				snprintf(tmp, sizeof(tmp), "%s", cur->name);
		} else
			snprintf(tmp, sizeof(tmp), "%s", cur->label);
		cur->next = extens;
		cur->obj = gui_add_object(cur->name, tmp, cur->icon, cur->ident, cur, size, strlen(cur->chans) ? 1 : 0, TYPE_EXTEN);
		if ((x > 0) && (y > 0))
			gui_object_move(cur->obj, x, y);
		extens = cur;
#if 0
		printf("Added extension %s with %s and '%s'\n", exten, icon, label);
#endif		
	}
}

char *get_old_ident(void *pvt, char *old_ident, int old_ident_size)
{
	struct ast_exten *exten = pvt;
	snprintf(old_ident, old_ident_size, "[%s]exten[%s]", curhost, exten->name);
	return old_ident;
}

static char *exten_file(void)
{
	static char fn[256];
	snprintf(fn, sizeof(fn), "%s" G_DIR_SEPARATOR_S ".gastman", gui_get_home_dir());
#ifndef _WIN32
	mkdir(fn, 0777);
#else
	_mkdir(fn);
#endif
	if (gui_get_home_dir() && strlen(curhost)) {
		snprintf(fn, sizeof(fn), "%s" G_DIR_SEPARATOR_S ".gastman" G_DIR_SEPARATOR_S "%s.extens", gui_get_home_dir(), curhost);
		return fn;
	}
	return NULL;
}

static char *history_file(void)
{
	static char fn[256];
	snprintf(fn, sizeof(fn), "%s" G_DIR_SEPARATOR_S ".gastman", gui_get_home_dir());
#ifndef _WIN32
	mkdir(fn, 0777);
#else
	_mkdir(fn);
#endif
	if (gui_get_home_dir() && strlen(curhost)) {
		snprintf(fn, sizeof(fn), "%s" G_DIR_SEPARATOR_S ".gastman" G_DIR_SEPARATOR_S "%s.history", gui_get_home_dir(), curhost);
		return fn;
	}
	return NULL;
}

static int loaded =0;

static void save_extens(void)
{
	char *fn = exten_file();
	struct ast_exten *cur;
	FILE *f;
	if (!fn)
		return;
	if (!loaded)
		return;
	if ((f = fopen(fn, "w"))) {
		cur = extens;
		while(cur) {
			int x=0,y=0;
			if (!gui_object_location(cur->obj, &x, &y))
				fprintf(f, "%s:%s:%s:%i:%i:%s\n", cur->extens, cur->chans, cur->icon, x, y, cur->label);
			else
				fprintf(f, "%s:%s:%s:%i:%i:%s\n", cur->extens, cur->chans, cur->icon, 0, 0, cur->label);
			cur = cur->next;
		}
		fclose(f);
	}
}

static void save_history(void)
{
	char *fn = history_file();
	int x;
	FILE *f;
	if (!fn)
		return;
	if (!loaded)
		return;
	if ((f = fopen(fn, "w"))) {
		for (x=0;x<maxhist;x++) {
			if (history[x])
				fprintf(f, "%s\n", history[x]);
		}
		fclose(f);
	}
}

void gastman_add_exten(void)
{
	char *exten = NULL, *chans = NULL, *icon = NULL, *label = NULL;
	struct ast_exten *cur;
	if (!gui_exten(&exten, &chans, &icon, &label, "Add Extension")) {
		if (strlen(exten)) {
			cur = extens;
			while(cur) {
				if (!strcasecmp(cur->name, exten))
					break;
				cur = cur->next;
			}
			if (cur) {
				gui_show_message("In Use", "Sorry, that extension already exists!\n");
			} else {
				/* alloc_exten(exten, chans, icon, label); */
				alloc_exten(exten, chans, icon, label, 1, -1, -1);
				save_extens();
				check_extens();
				update_channels();
			}
		} else {
			/* Blank extension (i.e. channel indicator) */
			alloc_exten(exten, chans, icon, label, 1, -1, -1);
			save_extens();
			check_extens();
			update_channels();
		}
	}
}

static void load_extens(void)
{
	char *fn = exten_file();
	char *exten, *chans = NULL, *x, *y;
	char *icon, *label;
	char buf[512];
	char *tmp;
	FILE *f;
	loaded = 1;
	if ((f = fopen(fn, "r"))) {
		while(!feof(f)) {
			fgets(buf, sizeof(buf), f); 
			if (!feof(f)) {
				buf[strlen(buf) - 1] = '\0';
				tmp = buf;
				icon = label = NULL;
				exten = strsep(&tmp, ":");
				if (exten)
					chans = strsep(&tmp, ":");
				if (chans)
					icon = strsep(&tmp, ":");
				if (icon)
					x = strsep(&tmp, ":");
				if (x)
					y = strsep(&tmp, ":");
				if (y)
					label = strsep(&tmp, ":");
				else {
					/* Backwards compatibility with old format */
					label = x;
					x = "0";
					y = "0";
				}
				if (exten && icon && label)
					alloc_exten(exten, chans, icon, label, 1, atoi(x), atoi(y));
			}
		}
		fclose(f);
	}
}

static void load_history(void)
{
	char *fn = history_file();
	char buf[512];
	FILE *f;
	loaded = 1;
	maxhist = curhistpos = 0;
	if ((f = fopen(fn, "r"))) {
		while(!feof(f)) {
			fgets(buf, sizeof(buf), f); 
			if (!feof(f)) {
				buf[strlen(buf) - 1] = '\0';
				if (curhistpos > MAX_HIST - 1)
					shrink_hist();
				history[curhistpos] = strdup(buf);
				curhistpos++;
				maxhist++;
			}
		}
		fclose(f);
	}
}

void gastman_quit(void)
{
	save_extens();
	save_history();
	gui_exit();
}

static int event_link(struct mansession *s, struct message *m)
{
	struct ast_chan *chan1, *chan2;
	chan1 = find_chan(get_header(m, "Channel1"));
	chan2 = find_chan(get_header(m, "Channel2"));
	if (chan1 && chan2) {
		strncpy(chan1->link, chan2->name, sizeof(chan1->link) - 1);
		strncpy(chan2->link, chan1->name, sizeof(chan2->link) - 1);
		gui_link(chan1->obj, chan2->obj);
	}
	update_channel(chan1);
	update_channel(chan2);
	return 0;
}

static int event_queueparams(struct mansession *s, struct message *m)
{
	char *queue = get_header(m, "Queue");
	struct ast_queuename *q;
	int max = atoi(get_header(m, "Max"));
	int calls =  atoi(get_header(m, "Calls"));
	printf("Queue %s has %d of %d members\n", queue, calls, max);
	q = find_queuename(queue);
	if (q) {
		q->current = 0 /* calls */;
		q->max = max;
		gui_queue_set_queueinfo(q->obj, q->current, q->max);
	}
	return 0;
}

static int event_queueentry(struct mansession *s, struct message *m)
{
	struct ast_queue *q;
	char *queue = get_header(m, "Queue");
	char *channel = get_header(m, "Channel");
	char *callerid = get_callerid(m);
	int position = atoi(get_header(m, "Position"));
	int waittime =  atoi(get_header(m, "Wait"));
	q = find_queue(channel, queue);
	printf("Channel %s on queue %s has position %d and has been waiting %d seconds\n", 
		channel, queue, position, waittime);
	if (q) {
		time(&q->start);
		q->start -= waittime;
		gui_queue_set_callinfo(q->obj, callerid, position, waittime);
	}
	return 0;
}

static int event_join(struct mansession *s, struct message *m)
{
	struct ast_queue *q;
	char *queue = get_header(m, "Queue");
	char *channel = get_header(m, "Channel");
	char *callerid = get_callerid(m);
	int position = atoi(get_header(m, "Position"));
	q = find_queue(channel, queue);
	printf("Channel %s on queue %s has position %d.\n", 
		channel, queue, position);
	time(&q->start);
	gui_queue_set_callinfo(q->obj, callerid, position, 0);
	return 0;
}

static int event_leave(struct mansession *s, struct message *m)
{
	char *name = get_header(m, "Channel");
	del_queue(name);
	return 0;
}

static int event_unlink(struct mansession *s, struct message *m)
{
	struct ast_chan *chan1, *chan2;
	chan1 = find_chan(get_header(m, "Channel1"));
	chan2 = find_chan(get_header(m, "Channel2"));
	if (chan1 && chan2) {
		strcpy(chan1->link, "");
		strcpy(chan2->link, "");
		gui_unlink(chan1->obj, chan2->obj);
	}
	return 0;
}
static struct event {
	char *event;
	int (*func)(struct mansession *s, struct message *m);
} events[] = {
	{ "Newstate", event_newstate },
	{ "Newchannel", event_newchannel },
	{ "Newexten", event_newexten },
	{ "Hangup", event_hangup },
	{ "Rename", event_rename },
	{ "Status", event_status },
	{ "Link", event_link },
	{ "Unlink", event_unlink },
	{ "Newcallerid", event_newcallerid },
	{ "QueueParams", event_queueparams },
	{ "QueueEntry", event_queueentry },
	{ "QueueMember", event_ignore },
	{ "Join", event_join },
	{ "Leave", event_leave },
};

static int process_message(struct mansession *s, struct message *m)
{
	int x;
	char event[80];
	strncpy(event, get_header(m, "Event"), sizeof(event));
	if (!strlen(event)) {
		fprintf(stderr, "Missing event in request\n");
		return 0;
	}
	for (x=0;x<sizeof(events) / sizeof(events[0]);x++) {
		if (!strcasecmp(event, events[x].event)) {
			if (events[x].func(s, m))
				return -1;
			break;
		}
	}
	if (x >= sizeof(events) / sizeof(events[0]))
		fprintf(stderr, "Ignoring unknown event '%s'\n", event);
#if 0
	for (x=0;x<m->hdrcount;x++) {
		printf("Header: %s\n", m->headers[x]);
	}
#endif	
	return 0;
}


static int has_input(struct mansession *s)
{
	int x;
	for (x=1;x<s->inlen;x++) 
		if ((s->inbuf[x] == '\n') && (s->inbuf[x-1] == '\r')) 
			return 1;
	return 0;
}

static int get_input(struct mansession *s, char *output)
{
	/* output must have at least sizeof(s->inbuf) space */
	int res;
	int x;
	struct timeval tv = {0, 0};
	fd_set fds;
	for (x=1;x<s->inlen;x++) {
		if ((s->inbuf[x] == '\n')) {
			/* Copy output data up to and including \r\n */
			memcpy(output, s->inbuf, x + 1);
			/* Add trailing \0 */
			output[x+1] = '\0';
			/* Move remaining data back to the front */
			memmove(s->inbuf, s->inbuf + x + 1, s->inlen - x);
			s->inlen -= (x + 1);
			return 1;
		}
	} 
	if (s->inlen >= sizeof(s->inbuf) - 1) {
		fprintf(stderr, "Dumping long line with no return from %s: %s\n", inet_ntoa(s->sin.sin_addr), s->inbuf);
		s->inlen = 0;
	}
	FD_ZERO(&fds);
	FD_SET(s->fd, &fds);
	res = select(s->fd + 1, &fds, NULL, NULL, &tv);
	if (res < 0) {
		fprintf(stderr, "Select returned error: %s\n", strerror(errno));
	} else if (res > 0) {
#ifndef _WIN32
		res = read(s->fd, s->inbuf + s->inlen, sizeof(s->inbuf) - 1 - s->inlen);
#else
		res = recv(s->fd, s->inbuf + s->inlen, sizeof(s->inbuf) - 1 - s->inlen, 0);
#endif
		if (res < 1)
			return -1;
		s->inlen += res;
		s->inbuf[s->inlen] = '\0';
	} else {
		return 2;
	}
	return 0;
}

static struct message *mout;

static void dump_message(struct message *m)
{
	int x;
	printf("< Received:\n");
	for (x=0;x<m->hdrcount;x++) {
		printf("< %s\n", m->headers[x]);
	}
	printf("\n");
}

static int input_check(struct mansession *s)
{
	static struct message m;
	int res;
	char *sp;

	for(;;) {
		res = get_input(s, m.headers[m.hdrcount]);
		if (res == 1) {
#if 0
			fprintf(stderr, "Got header: %s", m.headers[m.hdrcount]);
			fgetc(stdin);
#endif
			if (!m.gettingdata) {
				/* Strip trailing \r\n */
				if (strlen(m.headers[m.hdrcount]) < 2)
					continue;
				m.headers[m.hdrcount][strlen(m.headers[m.hdrcount]) - 2] = '\0';
				if (!strlen(m.headers[m.hdrcount])) {
					if (debug)
						dump_message(&m);
					if (mout && strlen(get_header(&m, "Response"))) {
						memcpy(mout, &m, sizeof(m));
						mout = NULL;
						memset(&m, 0, sizeof(&m));
						return 0;
					}
					if (process_message(s, &m))
						break;
					memset(&m, 0, sizeof(&m));
				} else if (m.hdrcount < MAX_HEADERS - 1) {
					if (!strncasecmp(m.headers[m.hdrcount], "Response: Follows", strlen("Response: Follows"))) {
						m.gettingdata = 1;
					}
					m.hdrcount++;
				}
			} else {
				/* Get raw data from command */
				if ((sp = strstr(m.headers[m.hdrcount], "--END COMMAND--"))) {
					/* End of getting data */
					*sp = '\0';
					m.gettingdata = 0;
				}
				if (m.hdrcount < MAX_HEADERS - 1)
					m.hdrcount++;
			}
		} else if (res < 0) {
			return -1;
		} else if (res == 2)
			return 0;
	}
	return -1;
}

static struct message *wait_for_response(int timeout)
{
	static struct message m;
	
	mout = &m;
	while(mout && timeout) {
		gui_run_a_little(&timeout);
	}
	mout = NULL;
	return &m;
}

static void dump_out_message(char *action, char *tmp)
{
	char *s;
	printf("> Transmitted:\n");
	printf("> Action: %s\n", action);
	while(*tmp) {
		s = tmp;
		while(*s && (*s != '\r')) s++;
		if (!*s)
			break;
		*s = '\0';
		printf("> %s\n", tmp);
		s+=2;
		tmp = s;
	}
	printf("\n");
}

static int manager_action(char *action, char *fmt, ...)
{
	struct mansession *s;
	char tmp[4096];
	va_list ap;

	s = &session;
	fdprintf(s->fd, "Action: %s\r\n", action);
	va_start(ap, fmt);
	vsnprintf(tmp, sizeof(tmp), fmt, ap);
	va_end(ap);
#ifndef _WIN32
	write(s->fd, tmp, strlen(tmp));
#else
	send(s->fd, tmp, strlen(tmp), 0);
#endif
	fdprintf(s->fd, "\r\n");
	if (debug)
		dump_out_message(action, tmp);
	return 0;
}

static void try_status()
{
	struct message *m;
	manager_action("Status", "");
	m = wait_for_response(10000);
	if (!m) {
		gui_show_message("Status Failed", "Timeout waiting for response");
	} else if (strcasecmp(get_header(m, "Response"), "Success"))  {
		gui_show_message("Status Failed", get_header(m, "Message"));
	}
}

static void try_queuestatus()
{
	struct message *m;
	manager_action("QueueStatus", "");
	m = wait_for_response(10000);
	if (!m) {
		gui_show_message("Queue Status Failed", "Timeout waiting for response");
	}
}

static char *term_strip(char *dest, char *src, int buflen) {
	int destoffset=0,srcoffset=0;
	while (destoffset < buflen) {
		switch (src[srcoffset]) {
			case '\x1b':
				while ((src[srcoffset] != 'm') && (src[srcoffset] != '\0'))
					srcoffset++;
				if (src[srcoffset] == 'm')
					srcoffset++;
				break;
			default:
				dest[destoffset++] = src[srcoffset++];
		}
		if (src[srcoffset] == '\0') {
			dest[destoffset] = '\0';
			break;
		}
	}
	return dest;
}

static char *__gastman_run_command(const char *cmd)
{
	static char buf[8192], buf2[8192];
	int yah = 0;
	int x;
	struct message *m;
	manager_action("Command", "Command: %s\r\n", cmd);
	m = wait_for_response(100000);
	if (!m) {
		gui_show_message("Command Failed", "Timeout waiting for response");
	} else if (strcasecmp(get_header(m, "Response"), "Follows"))  {
		gui_show_message("Status Failed", get_header(m, "Message"));
	} else {
		memset(buf, 0, sizeof(buf));
		for (x=0;x<m->hdrcount;x++) {
			if (!strncasecmp(m->headers[x], "Response:", strlen("Response:")))
				yah = 1;
			else if (yah) {
				strncat(buf, m->headers[x], sizeof(buf) - strlen(buf));
			}
		}
		term_strip(buf2, buf, sizeof(buf2));
		return buf2;
	}
	return NULL;
}
void gastman_run_command(const char *cmd)
{
	char *buf;
	if (cmd && strlen(cmd)) {
		hist_append(cmd);
	}
	if (!strcasecmp(cmd, "quit") || !strcasecmp(cmd, "exit")) {
		gui_status("Use File->Quit instead.");
		return;
	}
	buf = __gastman_run_command(cmd);
	if (buf)
		gui_cli_result(buf);	
}
	
void gastman_right_click(void *obj)
{
	struct ast_chan *chan = obj;
	if (chan->magic == CHAN_MAGIC) {
		gui_show_chan_menu();
	} else if (chan->magic == EXTEN_MAGIC) {
		gui_show_exten_menu();
	} else {
		printf("Magic is %x\n", chan->magic);
	}
}

int gastman_pre_drag_drop(void *src, void *dst)
{
	struct ast_chan *chansrc = src;
	struct ast_exten *extendst = dst;
	struct ast_chan *chandst = dst;
	int res = 0;
	if ((chansrc->magic == CHAN_MAGIC) && (chandst->magic == EXTEN_MAGIC) &&
		(extendst->extens[0] != '_')) {
		res = 1;
	} else if ((chansrc->magic == CHAN_MAGIC) && (chandst->magic == TRASH_MAGIC)) {
		res = 1;
	} else if ((chansrc->magic == EXTEN_MAGIC) && (chandst->magic == TRASH_MAGIC)) {
		if (gui_confirm("Delete Confirmation", "Are you sure want to delete this extension?", 0, "confirmdelete")) 
			res = 1;
		else
			res = -1;
	}
	return res;
}

static void gastman_really_del_exten(void *data)
{
	struct ast_exten *prev, *orig, *cur;
	orig = data;
	prev = NULL;
	cur = extens;
	prev = NULL;
	while(cur) {
		if (cur == orig)  {
			/* Unlink */
			if (prev)
				prev->next = cur->next;
			else
				extens = cur->next;
			gui_del_object(cur->obj);
			free(cur);
			break;
		}
		prev = cur;
		cur = cur->next;
	}
}

void gastman_del_exten(void *data)
{
	if (gui_confirm("Delete Confirmation", "Are you sure want to delete this extension?", 0, "confirmdelete")) 
		gastman_really_del_exten(data);
}

void gastman_drag_drop(void *src, void *dst)
{
	struct ast_chan *chansrc = src;
	struct ast_chan *chandst = dst;
	struct ast_exten *extendst = dst;
	struct ast_exten *extensrc = src;
	char dest[256];
	char source[256];
	char tmp[256];
	char xchan[256] = "";
	char *context, *s;
	struct message *m;
#if 0
	printf("Drag %s onto %s\n", chansrc->name, chandst->name);
#endif	
	if ((chansrc->magic == CHAN_MAGIC) && (chandst->magic == EXTEN_MAGIC)) {
		if (extendst->extens[0] == '_') {
			gui_show_message("Redirect Failed", "That is not a valid extension to transfer to\n");
			return;
		}
		if (strlen(chansrc->link)) {
			snprintf(tmp, sizeof(tmp), "Do you want to transfer the person they're talking to (%s), too?", chansrc->link);
			if (gui_yesno("Confirm additional transfer", tmp, 1))
				snprintf(xchan, sizeof(xchan), "ExtraChannel: %s\r\n", chansrc->link);
		}
		strncpy(dest, extendst->name, sizeof(dest));
		s = strchr(dest, ',');
		if (s) *s = '\0';
		/* Move src to destination */
		if ((context = strchr(dest, '@'))) {
			*context = '\0';
			context++;
			manager_action("Redirect", "Channel: %s\r\n%sContext: %s\r\nExten: %s\r\nPriority: 1\r\n", chansrc->name,xchan,context,dest);
		} else {
			manager_action("Redirect", "Channel: %s\r\n%sExten: %s\r\nPriority: 1\r\n", chansrc->name, xchan, dest);
		}
		m = wait_for_response(10000);
		if (!m) {
			gui_show_message("Redirect Failed", "Timeout waiting for response");
		} else if (strcasecmp(get_header(m, "Response"), "Success"))  {
			gui_show_message("Redirect Failed", get_header(m, "Message"));
		}
	} else if ((chansrc->magic == CHAN_MAGIC) && (chandst->magic == TRASH_MAGIC)) {
		/* Hangup */
		gastman_hangup(chansrc);
	} else if ((chansrc->magic = EXTEN_MAGIC) && (chandst->magic == TRASH_MAGIC)) {
		gastman_really_del_exten(extensrc);
		save_extens();
	} else if ((chansrc->magic == EXTEN_MAGIC) && (chandst->magic == EXTEN_MAGIC)) {
		if (extendst->extens[0] == '_') {
			gui_show_message("Originate Failed", "That is not a valid extension to originate to\n");
			return;
		}
		/* Call Destination from source */
		strncpy(dest, extendst->name, sizeof(dest));
		s = strchr(dest, ',');
		if (s) *s = '\0';
		strncpy(source, extensrc->chans, sizeof(source));
		s = strchr(source, ',');
		if (s) *s = '\0';
		if (strlen(source)) {
			if ((context = strchr(dest, '@'))) {
				*context = '\0';
				context++;
				manager_action("Originate", "Channel: %s\r\nContext: %s\r\nExten: %s\r\nPriority: 1\r\nAsync: true\r\n", 
					source,context,dest);
			} else {
				manager_action("Originate", "Channel: %s\r\nExten: %s\r\nPriority: 1\r\nAsync: true\r\n", source, dest);
			}
			m = wait_for_response(10000);
			if (!m) {
				gui_show_message("Originate Failed", "Timeout waiting for response");
			} else if (strcasecmp(get_header(m, "Response"), "Success"))  {
				gui_show_message("Originate Failed", get_header(m, "Message"));
			}
		} else {
			snprintf(source, sizeof(source), "There is no channel associated with extension %s", extensrc->name);
			gui_show_message("No channel", source);
		}
	}
}

void gastman_hangup(void *data)
{
	struct ast_chan *chan = data;
	struct message *m;

	if (chan) {
		manager_action("Hangup", "Channel: %s\r\n", chan->name);
		m = wait_for_response(10000);
		if (!m) {
			gui_show_message("Hangup Failed", "Timeout waiting for response");
		} else if (strcasecmp(get_header(m, "Response"), "Success"))  {
			if (strcasecmp(get_header(m, "Message"), "Channel not existant")) {
				/* A hangup occurred, but we missed it. */
				char tmp[80];
				snprintf(tmp, sizeof(tmp), "Ouch.  %s doesn't exist on the server?", chan->name);
				del_chan(chan->name);
				gui_status(tmp);
				/*
				 * What this really calls for here is to have a ast_reliable_write, not ast_careful_write
				 * on the server.  That is, the data needs to go to the manager client, even if the manager
				 * client is not quite ready to receive it.  This calls for creating a separate event queue
				 * and thread for each manager client, thus allowing every event to be reliably received.
				 *
				 * However, the goal here is to fix gastman, so that it works even when we miss an event,
				 * so the Asterisk fix will have to wait until later.
				 *
				 * Tilghman (Corydon76) 2005-03-21
				 */
			} else {
				gui_show_message("Hangup Failed", get_header(m, "Message"));
			}
		}
	}
}

void gastman_double_click(void *data)
{
	struct ast_exten *cur = data;
	char *exten, *chans, *icon, *label;
	int x=0, y=0;
	if (cur->magic == EXTEN_MAGIC) {
		exten = cur->extens;
		chans = cur->chans;
		icon = cur->icon;
		label = cur->label;
		if (!gui_exten(&exten, &chans, &icon, &label, "Update Extension")) {
			/* Delete old and add back */
			if (!gui_object_location(cur->obj,&x,&y)) {
				gastman_really_del_exten(cur);
				alloc_exten(exten, chans, icon, label, 1, x, y);
			} else {
				gastman_really_del_exten(cur);
				alloc_exten(exten, chans, icon, label, 1, -1, -1);
			}
			check_extens();
			update_channels();
			save_extens();
		}
	}
}

void gastman_redirect(void *data)
{
	struct ast_chan *chan = data;
	char dest[256];
	struct message *m;
	char channame[256];
	char xchan[256] = "";
	char tmp[80];
	char *context;

	if (chan) {
		strncpy(channame, chan->name, sizeof(channame) - 1);
		snprintf(tmp, sizeof(tmp), "Enter new extension for %s", channame);
		if (gui_get_user_input("Redirect", tmp, dest, sizeof(dest))) 
			return;
		if (strlen(chan->link)) {
			snprintf(tmp, sizeof(tmp), "Do you want to transfer the person they're talking to (%s), too?", chan->link);
			if (gui_yesno("Confirm additional transfer", tmp, 1))
				snprintf(xchan, sizeof(xchan), "ExtraChannel: %s\r\n", chan->link);
		}
		if ((context = strchr(dest, '@'))) {
			*context = '\0';
			context++;
			manager_action("Redirect", "Channel: %s\r\n%sContext: %s\r\nExten: %s\r\nPriority: 1\r\n", chan->name,xchan, context,dest);
		} else {
			manager_action("Redirect", "Channel: %s\r\n%sExten: %s\r\nPriority: 1\r\n", chan->name, xchan, dest);
		}
		m = wait_for_response(10000);
		if (!m) {
			gui_show_message("Redirect Failed", "Timeout waiting for response");
		} else if (strcasecmp(get_header(m, "Response"), "Success"))  {
			gui_show_message("Redirect Failed", get_header(m, "Message"));
		}
	}
	
}

void gastman_originate_exten(void *data)
{
	struct ast_exten *exten = data;
	char dest[256];
	char channame[256];
	struct message *m;
	char tmp[80];
	char *s;
	char *context;

	if (exten) {
		if (!strlen(exten->chans)) {
			snprintf(tmp, sizeof(tmp), "Can't originate since %s has no associated channels", exten->name);
			gui_show_message("Invalid origination", tmp);
			return;
		}
		strncpy(channame, exten->chans, sizeof(channame) - 1);
		s = strchr(channame, ',');
		if (s)
			*s = '\0';
		snprintf(tmp, sizeof(tmp), "Enter extension for %s", channame);
		if (gui_get_user_input("Originate", tmp, dest, sizeof(dest))) 
			return;
		if ((context = strchr(dest, '@'))) {
			*context = '\0';
			context++;
			manager_action("Originate", "Channel: %s\r\nContext: %s\r\nExten: %s\r\nPriority: 1\r\nAsync: true\r\n", 
					channame,context,dest);
		} else {
			manager_action("Originate", "Channel: %s\r\nExten: %s\r\nPriority: 1\r\nAsync: true\r\n", 
					channame,dest);
		}
		m = wait_for_response(10000);
		if (!m) {
			gui_show_message("Originate Failed", "Timeout waiting for response");
		} else if (strcasecmp(get_header(m, "Response"), "Success"))  {
			gui_show_message("Originate Failed", get_header(m, "Message"));
		}
	}
	
}

void gastman_invite_exten(void *data)
{
	struct ast_exten *exten = data;
	char dest[256];
	char channame[256];
	char ext[256];
	struct message *m;
	char tmp[80];
	char *s;
	char *context;

	if (exten) {
		if (exten->extens[0] == '_') {
			snprintf(tmp, sizeof(tmp), "Can't originate since %s is a pattern match", exten->name);
			gui_show_message("Invalid origination", tmp);
			return;
		}
		strncpy(dest, exten->extens, sizeof(dest) - 1);
		s = strchr(dest, ',');
		if (s) 
			*s = '\0';
		snprintf(tmp, sizeof(tmp), "Enter extension to invite to %s", dest);
		if (gui_get_user_input("Invite", tmp, ext, sizeof(ext))) 
			return;
		snprintf(channame, sizeof(channame), "Local/%s", ext);
		if ((context = strchr(dest, '@'))) {
			*context = '\0';
			context++;
			manager_action("Originate", "Channel: %s\r\nContext: %s\r\nExten: %s\r\nPriority: 1\r\nAsync: true\r\n", 
					channame,context,dest);
		} else {
			manager_action("Originate", "Channel: %s\r\nExten: %s\r\nPriority: 1\r\nAsync: true\r\n", 
					channame,dest);
		}
		m = wait_for_response(10000);
		if (!m) {
			gui_show_message("Invite Failed", "Timeout waiting for response");
		} else if (strcasecmp(get_header(m, "Response"), "Success"))  {
			gui_show_message("Invite Failed", get_header(m, "Message"));
		}
	}
	
}

static int manage_calls(char *host)
{

	/* If there's one thing you learn from this code, it is this...
	   Never, ever fly Air France.  Their customer service is absolutely
	   the worst.  I've never heard the words "That's not my problem" as 
	   many times as I have from their staff -- It should, without doubt
	   be their corporate motto if it isn't already.  Don't bother giving 
	   them business because you're just a pain in their side and they
	   will be sure to let you know the first time you speak to them.
	   
	   If you ever want to make me happy just tell me that you, too, will
	   never fly Air France again either (in spite of their excellent
	   cuisine). */

	char tmp[256];

	gui_show_box(host);
	snprintf(tmp, sizeof(tmp), "[%s]trash", curhost);
	gui_add_object("Trash", "Trash", "wastebasket", tmp, &wastebasket, 1, 0, TYPE_TRASH);
	load_extens();
	load_history();
	gui_show_doing("Getting Status", "Retrieving system status...");
	try_status();
	try_queuestatus();
	gui_hide_doing();
	gui_run();
	return 0;
}

int gastman_get_fd(void)
{
	return session.fd;
}

int gastman_input_ready(void)
{
	do {
		if (input_check(&session)) {
#ifndef _WIN32
			close(session.fd);
#else
			closesocket(session.fd);
#endif
			gui_show_message("Disconnected", "Disconnected from remote host");
			save_extens();
			gui_exit();
		}	
	} while(has_input(&session));
	return 0;
}

static int login(char *hostname)
{
	char *user;
	char *pass;
	struct message *m;
	char tmp[55];
	struct hostent *hp;
	int res = -1;
	
	session.fd = socket(AF_INET, SOCK_STREAM, 0);
	if (session.fd < 0) {
		snprintf(tmp, sizeof(tmp), "socket() failed: %s\n", strerror(errno));
		gui_show_message("Socket failed", tmp);
		return -1;
	}

	if (gui_login(&user, &pass, hostname))
		return -1;
	
	snprintf(tmp, sizeof(tmp), "Looking up %s\n", hostname);
	gui_show_doing("Connecting....", tmp);
	
	strncpy(curhost, hostname, sizeof(curhost) - 1);
	
	hp = gethostbyname(hostname);
	if (!hp) {
		snprintf(tmp, sizeof(tmp), "No such address: %s\n", hostname);
		gui_show_message("Host lookup failed", tmp);
		return -1;
	}
	gui_hide_doing();
	snprintf(tmp, sizeof(tmp), "Connecting to %s", hostname);
	gui_show_doing("Connecting...", tmp);

	session.sin.sin_family = AF_INET;
	session.sin.sin_port = htons(DEFAULT_MANAGER_PORT);
	memcpy(&session.sin.sin_addr, hp->h_addr, sizeof(session.sin.sin_addr));

#ifndef _WIN32
	if (connect(session.fd, (struct sockaddr *)&session.sin, sizeof(session.sin))) {
		snprintf(tmp, sizeof(tmp), "%s failed: %s\n", hostname, strerror(errno));
#else
	if (connect(session.fd, (struct sockaddr *)&session.sin, sizeof(session.sin)) == SOCKET_ERROR) {
		snprintf(tmp, sizeof(tmp), "%s failed: %d\n", hostname, WSAGetLastError());
#endif
		gui_show_message("Connect Failed", tmp);
		return -1;
	}
	
	gui_watch_fd(session.fd);
	gui_hide_doing();
	
	snprintf(tmp, sizeof(tmp), "Logging in %s '...", user);
	gui_show_doing("Logging in", tmp);
	/* Check to see if remote host supports MD5 Authentication */
	manager_action("Challenge", "AuthType: MD5\r\n");
	m = wait_for_response(10000);
	if (m && !strcasecmp(get_header(m, "Response"), "Success")) {
		char *challenge = get_header(m, "Challenge");
		int x;
		int len = 0;
		char md5key[256] = "";
		struct MD5Context md5;
		unsigned char digest[16];
		MD5Init(&md5);
		MD5Update(&md5, challenge, strlen(challenge));
		MD5Update(&md5, pass, strlen(pass));
		MD5Final(digest, &md5);
		for (x=0; x<16; x++)
			len += sprintf(md5key + len, "%2.2x", digest[x]);
		manager_action("Login",
			"AuthType: MD5\r\n"
			"Username: %s\r\n"
			"Key: %s\r\n",
			user, md5key);
		m = wait_for_response(10000);
		gui_hide_doing();
		if (m) {
			if (!strcasecmp(get_header(m, "Response"), "Success")) {
				res = 0;
			} else {
				gui_show_message("Login Failed", get_header(m, "Message"));
			}
		}
	} else {	
		manager_action("Login", 
			"Username: %s\r\n"
			"Secret: %s\r\n",
			user, pass);
		m = wait_for_response(10000);
		gui_hide_doing();
		if (m) {
			if (!strcasecmp(get_header(m, "Response"), "Success")) {
				res = 0;
			} else {
				gui_show_message("Login Failed", get_header(m, "Message"));
			}
		}
	}
	return res;
}

int main(int argc, char *argv[])
{
	char host[256] = "";
#ifdef _WIN32
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
#endif

#ifdef _WIN32
	wVersionRequested = MAKEWORD( 2, 2 );
	
	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 ) {
		fprintf(stderr, "WSAStartup: Could not find usable WinsSock DLL\n");
		return 1;
	}
#endif

	gui_init(&argc, &argv);
	argv++;
	argc--;
	if (argc && !strcmp(*argv, "-d")) {
		debug = 1;
		argc--;
		argv++;
	}
	if (argc)
		strncpy(host, *argv, sizeof(host) - 1);

	if (strlen(host) || 
		(!gui_get_hostname(host, sizeof(host) - 1) &&
			strlen(host))) {
		if (!login(host)) {
			manage_calls(host);
		}
	}
#ifdef _WIN32
	WSACleanup( );
#endif
	return 0;
}

char *gastman_prevhist(void)
{
	if (curhistpos > 0) {
		curhistpos--;
		return history[curhistpos];
	}
	return NULL;
}

char *gastman_nexthist(void)
{
	if (curhistpos<maxhist) {
		curhistpos++;
		return history[curhistpos];
	}
	return NULL;
}
char *gastman_complete(const char *orig, int *pos)
{
	static char buf[4096];
	char cmd[4096];
	char tmp[4096];
	char sch[1024];
	const char *ptr;
	int len;
	int count;
	int excl;
	char *ret;
	char *w, *n;
	
	strncpy(buf, orig, sizeof(buf) - 1);
#if 0
	printf("Completing %s at position %d\n", orig, *pos);
#endif
	ptr = orig + *pos;
	if (ptr) {
		while (ptr > orig) {
			if (isspace(*ptr)) {
				ptr++;
				break;
			}
			ptr--;
		}
	}

	len = ptr - orig;
	snprintf(cmd, sizeof(cmd), "_COMMAND NUMMATCHES \"%s\" \"%s\"", orig, ptr);
#if 0
	printf("Command: '%s'\n", cmd);
#endif	
	ret = __gastman_run_command(cmd);
	if (ret) {
		if ((sscanf(ret, "%d", &count) == 1)) {
			/* Check for a count */
			if (count) {
				snprintf(cmd, sizeof(cmd), "_COMMAND MATCHESARRAY \"%s\" \"%s\"", orig, ptr);
#if 0
				printf("Command: '%s'\n", cmd);
#endif
				ret = __gastman_run_command(cmd);
#if 0
				printf("Ret is '%s'\n", ret);
#endif
				w = ret;
				while(*w && (*w > 32)) w++;
				if (*w) {
					*w = '\0';
					w++;
				}
				excl = 1;
				if (strlen(ret) && strcmp(ret, ptr)) {
					while(*w) {
						n = w;
						while(*n && (*n > 32)) n++;
						if (*n) {
							*n = '\0';
							n++;
							if (strcmp(w, ret)) {
								excl = 0;
								break;
							}
						}
						w = n;
					}
				} else
					excl = 0;
				snprintf(buf + len, sizeof(buf) - len, "%s%s", ret, excl ? " " : "");
				*pos = len + strlen(ret) + excl;
				if (!strlen(ret) || !strcmp(ret, ptr)) {
					strcpy(tmp, "Possibilities: ");
					while(*w) {
						n = w;
						while(*n && (*n > 32)) n++;
						if (*n) {
							*n = '\0';
							n++;
							snprintf(sch, sizeof(sch), "%s ", w);
							if (!strstr(tmp, sch))
								strcat(tmp, sch);
						}
						w = n;
					}
					gui_status(tmp);
				} else
					gui_status("");
			} else
				gui_status("No matches");
		}
	}
	return buf;
}
