/* $Id: addrbook.cpp,v 1.9 2003/07/19 00:12:37 fesnel Exp $ */
/*******************************************************************************
 *   This program is part of the XFMail email client.                          *
 *                                                                             *
 *   Copyright : (C) 1995-1998 Gennady B. Sorokopud (gena@NetVision.net.il)    *
 *               (C) 1995 Ugen. J. S. Antsilevich (ugen@latte.worldbank.org)   *
 *               (C) 1998-2001 by the Archimedes Project                       *
 *                   http://sourceforge.net/projects/archimedes                *
 *                                                                             *
 *             --------------------------------------------                    *
 *                                                                             *
 *   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.  *
 *                                                                             *
 *   Additional Permission granted:                                            *
 *                                                                             *
 *   This program is designed to use the XForms library, so we consider        *
 *   permission to link to that non-GPL-compatible library is implicit.        *
 *   However, in case this is not considered so, we explicitly state:          *
 *                                                                             *
 *   "As a special exception, the Archimedes Project, with the permission      *
 *    of all earlier copyright holders, formally gives permission to link      *
 *    this program with the XForms library, and distribute the resulting       *
 *    executable without the source code for XForms in the source              *
 *    distribution".                                                           *
 *                                                                             *
 ******************************************************************************/


#include <glib.h>

#include <config.h>
#include <fmail.h>
#include <umail.h>
#include <choose_folder.h>
#include <cfgfile.h>

#include "addrbook_util.h"
#include "addrbookdb.h"

/* Function Prototypes */

void Address_Browse_Dbl_Call(FL_OBJECT * obj, long param);
int Address_Browse_Handler(FL_OBJECT * obj, int event, FL_Coord mx,
    FL_Coord my, int key, void *raw_ev);
void AB_Alias_Call(FL_OBJECT * obj, long param);

static bool is_invalid_addressbook_name(const char *name);

static void display_book(string bookname);
static void display_books();

static void edit_entry(AddressBookEntry *entry);
static void import_book(const char *afile, const char *deffile,
	bool (*convert_func)(FILE *, FILE *));

static int get_selected_browser_line();
static AddressBookEntry *get_selected_entry();
static AddressBookEntry *browser_line_to_entry(const char *browser_line);
static const char *entry_to_browser_line(AddressBookEntry *entry);

/* Variables */

extern cfgfile Config;

static int addrbook_ready = 0;
static int modready = 0;
static int already = 0;
static int type = TO_TYPE;
static int changed = 0;
static int abook_sel_entry = -1;
static AddressBookEntry *alias_entry = NULL;
static FD_Address_Book *addr_book_obj;
static FD_addrmod *amod_obj;
static FD_aliased *alias_obj;

/* deleted books */
static vector<string> delbooks;

/*
extern struct _abook *book = NULL;
extern struct _abook_entry *cur_entry = NULL;
*/
static addr_dbl_cb dbl_callback;
static int dbl_callback_data;
static int abookx = -1, abooky = -1;

static string abook_openbook;

/* The address book, from addr_book_util.cpp */
extern AddressBookDB addrbookdb;

static bool is_invalid_addressbook_name(const char *name)
{
	const char *p;

	if (addrbookdb.FindBook(name) != NULL)
		return 1;

	for (p = name; *p != '\0' && (isalnum(*p) || *p == '_' || *p == '-'); p++);

	return (*p != '\0');
}

static int get_selected_browser_line()
{

	for (int i = 1; i <= fl_get_browser_maxline(addr_book_obj->Address_Browse); i++)
		if (fl_isselected_browser_line(addr_book_obj->Address_Browse, i))
			return i;
	return 0;
}

static AddressBookEntry *get_selected_entry()
{
	int i;

	i = get_selected_browser_line();
	if (i == 0)
		return NULL;

	return browser_line_to_entry(fl_get_browser_line(addr_book_obj->Address_Browse, i));
}

/* Convert from the browser line to an AddressBookEntry. Handles alias
 * lines.
 */
static AddressBookEntry *browser_line_to_entry(const char *browser_line)
{
	const char *cp;
	char buf[256];
	AddressBookEntry *entry;

	if (browser_line == NULL || strlen(browser_line) == 0)
		return NULL;

	if (browser_line[0] == '[' && (cp = strchr(browser_line, ']')) != NULL) {
		// First search by the alias name
		snprintf(buf, sizeof(buf), "%.*s", (cp - &browser_line[1]), browser_line+1);
		entry = addrbookdb.FindBook(abook_openbook)->FindEntry(buf);
		if (entry != NULL)
			return entry;

		// Second, try searching by the included address
		if (strlen(cp) > 2) { /* 2 == "] " */
			snprintf(buf, sizeof(buf), "%s", cp+2);
			return addrbookdb.FindBook(abook_openbook)->FindEntry(buf);
		}
		return NULL;
	} else
		return addrbookdb.FindBook(abook_openbook)->FindEntry(browser_line);
}

/* Convert from an AddressBookEntry to a browser line. Handles alias'.
 */
static const char *entry_to_browser_line(AddressBookEntry *entry)
{
	static char buf[256];

	if (entry->GetType() == ADDRESSBOOKENTRY_ALIAS) {
		if (entry->GetDescription().length() > 0)
			snprintf(buf, sizeof(buf), "[%s] %s", entry->GetDescription().c_str(),
					 get_full_addr_line(entry->GetAddress()));
		else
			snprintf(buf, sizeof(buf), "[%s]", get_full_addr_line(entry->GetAddress()));
	} else
		snprintf(buf, sizeof(buf), "%s", get_full_addr_line(entry->GetAddress()));

	return buf;
}

static void display_books() {
	AddressBookDB::iterator p;
	int i, selentry;

	fl_freeze_form(addr_book_obj->Address_Book);
	fl_clear_browser(addr_book_obj->Abooks_Browse);

	if (abook_openbook == "")
		abook_openbook = "default";
	for (p = addrbookdb.begin(), i = selentry = 0; p != addrbookdb.end(); p++, i++) {
		fl_add_browser_line(addr_book_obj->Abooks_Browse, (*p)->GetName().c_str());
		if ((*p)->GetName() == abook_openbook)
			selentry = i;
	}
		

	fl_select_browser_line(addr_book_obj->Abooks_Browse, i > 0 ? selentry+1 : 0);
	fl_unfreeze_form(addr_book_obj->Address_Book);

	display_book(abook_openbook);
}

void display_book(string bookname) {
	AddressBook::iterator p;

	if(bookname.length() == 0 || addrbookdb.FindBook(bookname) == NULL)
		return;

	if (abook_sel_entry == -1)
		abook_sel_entry = 1;
	fl_freeze_form(addr_book_obj->Address_Book);
	fl_clear_browser(addr_book_obj->Address_Browse);
	for (p = addrbookdb.FindBook(bookname)->begin();
		 p != addrbookdb.FindBook(bookname)->end(); p++)
		fl_add_browser_line(addr_book_obj->Address_Browse, entry_to_browser_line(*p));

	// Determine which entry to select and display in top input
	if (fl_get_browser_maxline(addr_book_obj->Address_Browse) >= abook_sel_entry) {
		const char *browserline = fl_get_browser_line(
			addr_book_obj->Address_Browse, abook_sel_entry);
		AddressBookEntry *entry = browser_line_to_entry(browserline);

		fl_set_input(addr_book_obj->AB_Input,
			entry->GetDescription().length() > 0 ?
			entry->GetDescription().c_str() :
			get_full_addr_line(entry->GetAddress()));
		fl_set_input(addr_book_obj->AB_PGP,
			entry->GetAddress()->pgpid != NULL ?
			entry->GetAddress()->pgpid : "");
		fl_select_browser_line(addr_book_obj->Address_Browse, abook_sel_entry);

		if (abook_sel_entry >= fl_get_browser_screenlines(addr_book_obj->Address_Browse))
			fl_set_browser_topline(addr_book_obj->Address_Browse, abook_sel_entry);
		else
			fl_set_browser_topline(addr_book_obj->Address_Browse, 1);
	} else {
		abook_sel_entry = -1;
		fl_set_input(addr_book_obj->AB_Input, "");
		fl_set_input(addr_book_obj->AB_PGP, "");
	}
	fl_set_input_selected(addr_book_obj->AB_Input, 1);
	fl_set_input_cursorpos(addr_book_obj->AB_Input, 0, 0);
	fl_set_input_selected(addr_book_obj->AB_PGP, 1);
	fl_set_input_cursorpos(addr_book_obj->AB_PGP, 0, 0);

	fl_unfreeze_form(addr_book_obj->Address_Book);
}

int close_abook(FL_FORM * form, void *data) {
	if(addr_book_obj)
		fl_trigger_object(addr_book_obj->AB_Ok);

	abook_sel_entry = -1;
	return FL_IGNORE;
}

void addr_book_set_placement(int x, int y) {
	abookx = x;
	abooky = y;
}

struct _mail_addr *addr_book(int *addr_type, addr_dbl_cb callback,
							 int data) {
	XWMHints *xwmh;
	int w, h;
	char geom[16];
	AddressBookEntry *entry;

	if(addrbook_ready) {
		XRaiseWindow(fl_display, addr_book_obj->Address_Book->window);
		display_msg(MSG_WARN, "addrbook",
					"Address book is currently in use by another window, close it and try again");
		return NULL;
	}

	addrbook_ready = 1;
	changed = 0;
	delbooks.clear();
	addr_book_obj = create_form_Address_Book();

	fl_set_browser_fontsize(addr_book_obj->Address_Browse,
							Config.getInt("AddrBookFSize",
										  FL_NORMAL_SIZE));
	fl_set_browser_fontstyle(addr_book_obj->Address_Browse,
							 Config.getInt("AddrBookFStyle",
										   FL_NORMAL_STYLE));
	fl_set_object_color(addr_book_obj->Address_Browse,
						Config.getInt("AddrBookBgCol",
									  FL_TOP_BCOL),
						Config.getInt("AddrBookFgCol",
									  FL_YELLOW));
	fl_set_browser_dblclick_callback(addr_book_obj->Address_Browse,
									 Address_Browse_Dbl_Call, 0);
	fl_set_object_posthandler(addr_book_obj->Address_Browse,
							  Address_Browse_Handler);

	fl_set_browser_fontsize(addr_book_obj->Abooks_Browse,
							Config.getInt("AddrBookFSize",
										  FL_NORMAL_SIZE));
	fl_set_browser_fontstyle(addr_book_obj->Abooks_Browse,
							 Config.getInt("AddrBookFStyle",
										   FL_NORMAL_STYLE));
	fl_set_object_color(addr_book_obj->Abooks_Browse,
						Config.getInt("AddrBookBgCol",
									  FL_TOP_BCOL),
						Config.getInt("AddrBookFgCol",
									  FL_YELLOW));
	fl_set_object_lsize(addr_book_obj->AB_Input,
						Config.getInt("AddrBookFSize",
									  FL_NORMAL_SIZE));
	fl_set_object_lstyle(addr_book_obj->AB_Input,
						 Config.getInt("AddrBookFStyle",
									   FL_NORMAL_STYLE));
	fl_set_input_color(addr_book_obj->AB_Input, FL_BLACK,
					   Config.getInt("AddrBookFgCol",
									 FL_BLUE));
	fl_set_object_color(addr_book_obj->AB_Input,
						Config.getInt("AddrBookBgCol",
									  FL_BLACK),
						Config.getInt("AddrBookBgCol",
									  FL_BLUE));
	fl_set_input_return(addr_book_obj->AB_Input, FL_RETURN_ALWAYS);

	fl_set_object_lsize(addr_book_obj->AB_PGP,
						Config.getInt("AddrBookFSize",
									  FL_NORMAL_SIZE));
	fl_set_object_lstyle(addr_book_obj->AB_PGP,
						 Config.getInt("AddrBookFStyle",
									   FL_NORMAL_STYLE));
	fl_set_input_color(addr_book_obj->AB_PGP, FL_BLACK,
					   Config.getInt("AddrBookFgCol",
									 FL_BLUE));
	fl_set_object_color(addr_book_obj->AB_PGP,
						Config.getInt("AddrBookBgCol",
									  FL_BLACK),
						Config.getInt("AddrBookBgCol",
									  FL_BLUE));
	fl_set_form_minsize(addr_book_obj->Address_Book, 700, 280);
	fl_set_form_maxsize(addr_book_obj->Address_Book, 900, 600);
	fl_set_form_atclose(addr_book_obj->Address_Book, close_abook, NULL);
	Config.setFlags("abookgeom", CF_NOTCHANGED);
	w = 700;
	h = 280;
	sscanf(Config.get("abookgeom", "").c_str(), "%d %d", &w, &h);
	if(Config.getInt("wplace", 1))
		fl_set_form_geometry(addr_book_obj->Address_Book,
							 (abookx > 0)
							 || !main_form ? abookx : main_form->x,
							 (abooky > 0)
							 || !main_form ? abooky : main_form->y, w, h);

	dbl_callback = callback;
	dbl_callback_data = data;

	fl_show_form(addr_book_obj->Address_Book, FL_PLACE_FREE, FL_FULLBORDER,
				 "Address Book");

	if((xwmh = XAllocWMHints()) != NULL) {
		xwmh->icon_pixmap = icon_ABook;
		xwmh->icon_mask = icon_ABook_sh;
		xwmh->flags = IconPixmapHint | IconMaskHint;
		XSetWMHints(fl_display, addr_book_obj->Address_Book->window, xwmh);
		XFree(xwmh);
	}

	display_books();

	if(addr_type) {
		type = *addr_type;
		switch(*addr_type) {
			case TO_TYPE:
				fl_set_button(addr_book_obj->AB_To, 1);
				break;

			case CC_TYPE:
				fl_set_button(addr_book_obj->AB_Cc, 1);
				break;

			case BCC_TYPE:
				fl_set_button(addr_book_obj->AB_Bcc, 1);
				break;

			default:
				fl_set_button(addr_book_obj->AB_To, 1);
				break;
		}
	} else {
		fl_set_button(addr_book_obj->AB_To, 1);
		type = TO_TYPE;
	}

	fl_do_only_forms();

	if(changed && !readonly) {
		if(display_msg(MSG_QUEST, "address book",
			   "Do you want to save changes to the address book?")) {
			save_addressbooks();
			for (int i = 0; i < delbooks.size(); i++)
				addrbookdb.DeleteBookFile(configdir, delbooks[i]);
			delbooks.clear();
		} else {
			addrbookdb.Clear();
			delbooks.clear();
			load_addressbooks();
		}
	}
	changed = 0;
	abook_sel_entry = -1;

	entry = get_selected_entry();
	sprintf(geom, "%d %d", addr_book_obj->Address_Book->w,
			addr_book_obj->Address_Book->h);
	Config.set("abookgeom", geom);
	fl_hide_form(addr_book_obj->Address_Book);
	fl_free_form(addr_book_obj->Address_Book);
	free(addr_book_obj);
	addr_book_obj = NULL;

	if(addr_type)
		*addr_type = type;

	addrbook_ready = 0;
	return (entry != NULL) ? entry->GetAddress() : NULL;
}

void AB_Add_Call(FL_OBJECT * obj, long param) {
	const char *addr, *pgpid;
	struct _mail_addr *ma;

	addr = fl_get_input(addr_book_obj->AB_Input);
	if(strlen(addr) < 2)
		return;

	if(!(ma = get_address(addr, ADDR_IGNORE_COMMAS))) {
		display_msg(MSG_WARN, "add", "Invalid address:\n%s", addr);
		return;
	}

	pgpid = fl_get_input(addr_book_obj->AB_PGP);
	if(pgpid != NULL && strlen(pgpid) > 0 && strncmp(pgpid, "0x", 2) != 0) {
		display_msg(MSG_WARN, "add", "Invalid PGP Id: %s", pgpid);
		discard_address(ma);
		return;
	}

	discard_address(ma->next_addr);
	ma->next_addr = NULL;

	ma->pgpid = (pgpid != NULL && strlen(pgpid) > 0) ? strdup(pgpid) : NULL;

	addrbookdb.FindBook(abook_openbook)->AddEntry(new AddressBookEntry(ma));
	discard_address(ma);
	changed = 1;

	fl_set_input(addr_book_obj->AB_Input, "");
	fl_set_input(addr_book_obj->AB_PGP, "");
	display_book(abook_openbook);
}

void AB_Delete_Call(FL_OBJECT * obj, long param) {
	AddressBookEntry *entry;
	int i;

	entry = get_selected_entry();
	if (entry == NULL)
		return;
	i = get_selected_browser_line();

	if (!addrbookdb.FindBook(abook_openbook)->DeleteEntry(entry)) {
		display_msg(MSG_WARN, "delete", "Could not delete entry");
		return;
	}

	changed = 1;

	fl_delete_browser_line(addr_book_obj->Address_Browse, i);
	if (i > fl_get_browser_maxline(addr_book_obj->Address_Browse))
		i = 1;
	if (fl_get_browser_maxline(addr_book_obj->Address_Browse) > 0)
		fl_select_browser_line(addr_book_obj->Address_Browse, i);

	display_book(abook_openbook);
}

void AB_Copy(const char *name) {
	AddressBookEntry *entry;

	if (name == NULL || abook_openbook == string(name))
		return;

	entry = get_selected_entry();
	if (entry == NULL)
		return;

	if (addrbookdb.FindBook(name)->FindEntry(entry->GetAddress()) == NULL)
		addrbookdb.FindBook(name)->AddEntry(new AddressBookEntry(*entry));

	display_msg(MSG_MSG, "copy",
		"%d address(es) copied to address book %s", entry->Size(), name);
	changed = 1;
}

void AB_To_Call(FL_OBJECT * obj, long param) {
	type = TO_TYPE;
}

void AB_Cc_Call(FL_OBJECT * obj, long param) {
	type = CC_TYPE;
}

void AB_Bcc_Call(FL_OBJECT * obj, long param) {
	type = BCC_TYPE;
}

void AB_Save_Call(FL_OBJECT * obj, long param) {
	save_addressbook(abook_openbook);
}

void AB_Import_Book_Call(FL_OBJECT * obj, long param) {
	int imp_pup;

	imp_pup = fl_defpup(obj->form->window, "Import from%t|.mailrc|Pine|Text");

	switch(fl_dopup(imp_pup)) {
		case 1:
			import_book(NULL, ".mailrc", convert_addrbook_mailrc);
			break;

		case 2:
			import_book(NULL, ".addrbook", convert_addrbook_pine);
			break;

		case 3:
			import_book(NULL, ".txt", convert_addrbook_text);
			break;
	}

	if (changed)
	    display_books();

	fl_freepup(imp_pup);
}

void Address_Browse_Dbl_Call(FL_OBJECT * obj, long param) {
	if(dbl_callback && get_selected_entry() != NULL)
		dbl_callback(get_selected_entry()->GetAddress(), type, dbl_callback_data);
}

void Address_Browse_Call(FL_OBJECT * obj, long param) {
}

int
Address_Browse_Handler(FL_OBJECT * obj, int event,
					   FL_Coord mx, FL_Coord my, int key, void *raw_ev) {
	AddressBookEntry *sel_entry;
	int num, k;
	struct _mail_addr *addr;
	char *h;
	const char *browserline;

	switch(event) {
		case FL_PUSH:
			num = fl_get_browser(obj);
			browserline = fl_get_browser_line(obj, num);
			if (browserline == NULL)
				return 0;

			AddressBookEntry *entry = browser_line_to_entry(browserline);
			if (entry == NULL)
				return 0;

			if (abook_sel_entry != -1 && abook_sel_entry < fl_get_browser_maxline(obj) &&
				(browserline = fl_get_browser_line(obj, abook_sel_entry)) != NULL)
				sel_entry = browser_line_to_entry(browserline);
			else
				sel_entry = NULL;

			if(key == 3) {
				// Bailout, no need to do anything
				if (abook_sel_entry == -1 || entry == sel_entry)
					return 0;

				// Reset the selected line to the original one
				fl_select_browser_line(addr_book_obj->Address_Browse,
					abook_sel_entry);
				fl_show_browser_line(addr_book_obj->Address_Browse, abook_sel_entry);

				if(entry->GetType() != ADDRESSBOOKENTRY_ALIAS) {
					display_msg(MSG_WARN, "copy address",
								"Can copy only to aliases and address books");
					return 0;
				}
				for(addr = sel_entry->GetAddress(), k = 0; addr != NULL;
					addr = addr->next_addr, k++) {
				}

				entry->AddAddress(sel_entry->GetAddress());
				display_msg(MSG_MSG, "copy address",
							"%d address(es) appended to alias %s", k,
							entry->GetDescription().c_str());
				changed = 1;
			} else {
				if(entry->GetType() == ADDRESSBOOKENTRY_SINGLE)
					fl_set_input(addr_book_obj->AB_Input,
								 get_full_addr_line(entry->GetAddress()));
				else
					fl_set_input(addr_book_obj->AB_Input,
								 entry->GetDescription().length() > 0 ?
								 entry->GetDescription().c_str() :
								 get_full_addr_line(entry->GetAddress()));

				h = entry->GetAddress()->pgpid;
				fl_set_input(addr_book_obj->AB_PGP, h ? h : "");
				fl_set_input_selected(addr_book_obj->AB_Input, 1);
				fl_set_input_cursorpos(addr_book_obj->AB_Input, 0, 0);
				fl_set_input_selected(addr_book_obj->AB_PGP, 1);
				fl_set_input_cursorpos(addr_book_obj->AB_PGP, 0, 0);
				fl_set_focus_object(addr_book_obj->Address_Book,
									addr_book_obj->AB_Input);
				abook_sel_entry = num;
			}

			if(key == 2) {
				if(dbl_callback)
					dbl_callback(entry->GetAddress(), type, dbl_callback_data);
			}
			break;
	}

	return 0;
}

void AB_Input_Call(FL_OBJECT * obj, long param) {
	int i, sline, eline;
	const char *ln;
	const char *inp = fl_get_input(addr_book_obj->AB_Input);
	AddressBookEntry *entry;

	if(strlen(inp) < 1)
		return;

	eline = fl_get_browser_maxline(addr_book_obj->Address_Browse);
	if(eline <= 1)
		return;

	if(param == 1)
		sline = fl_get_browser(addr_book_obj->Address_Browse) + 1;
	else
		sline = 1;

	srch:
	
	for(i = sline; i <= eline; i++) {
		ln = fl_get_browser_line(addr_book_obj->Address_Browse, i);
		if(strlen(ln) < strlen(inp))
			continue;

		if(strcasestr(ln, inp, 1) != 0) {
			entry = browser_line_to_entry(ln);
			fl_select_browser_line(addr_book_obj->Address_Browse, i);
			fl_show_browser_line(addr_book_obj->Address_Browse, i);
			abook_sel_entry = i;
			fl_set_input(addr_book_obj->AB_PGP,
				entry->GetAddress()->pgpid != NULL ? entry->GetAddress()->pgpid : "");
			return;
		}
	}

	if(param == 1) {
		param = 0;
		eline = sline;
		sline = 1;
		goto srch;
	}
}

void AB_Modify_Call(FL_OBJECT * obj, long param) {
	AddressBookEntry *entry;
	struct _mail_addr *ma = NULL;
	const char *p, *addr;

	addr = fl_get_input(addr_book_obj->AB_Input);
	if (addr == NULL || strlen(addr) <= 0)
		return;

	if((ma = get_address(addr, ADDR_IGNORE_COMMAS)) == NULL) {
		display_msg(MSG_WARN, "modify", "Invalid address\n\"%s\"", addr);
		return;
	}

	discard_address(ma->next_addr);
	ma->next_addr = NULL;

	p = fl_get_input(addr_book_obj->AB_PGP);
	if(p != NULL && strlen(p) > 0)
		ma->pgpid = strdup(p);

	if(ma->pgpid != NULL && strncmp(ma->pgpid, "0x", 2) != 0) {
		display_msg(MSG_WARN, "modify", "Invalid PGP Id: %s", ma->pgpid);
		discard_address(ma);
		return;
	}

	entry = get_selected_entry();
	if (entry == NULL) {
		display_msg(MSG_WARN, "modify", "No entry selected");
		discard_address(ma);
		return;
	}


	if(entry->GetType() == ADDRESSBOOKENTRY_SINGLE) {
		entry->SetAddress(ma);
		discard_address(ma);
	} else {
		/* Instead of changing the address, change the alias name */
		discard_address(ma);

		if(strlen(addr) > 16) {
			display_msg(MSG_WARN, "modify", "alias name too long");
			return;
		}

		p = addr;
		while(*p != '\0') {
			if(!isalpha(*p) && !isdigit(*p) && *p != '-' && *p != '_') {
				display_msg(MSG_WARN, "modify",	"invalid character in alias name");
				return;
			}
			p++;
		}

		entry->SetDescription(addr);
	}

	changed = 1;
	fl_replace_browser_line(addr_book_obj->Address_Browse,
							get_selected_browser_line(), entry_to_browser_line(entry));
}

void Abook_Browse_Call(FL_OBJECT * obj, long param) {
	int num;
	const char *p;

	num = fl_get_browser(obj);
	if(num == 0)
		return;

	p = fl_get_browser_line(obj, num);
	if(p == NULL)
		return;

	if(fl_mouse_button() == FL_RIGHT_MOUSE) {
		AB_Copy(p);
		return;
	}

	abook_sel_entry = -1;
	abook_openbook = p;
	display_book(abook_openbook);
}

void AB_Memb_Call(FL_OBJECT * obj, long param) {
	AddressBookEntry *entry = get_selected_entry();

	if(entry == NULL)
		return;

	if(entry->GetType() != ADDRESSBOOKENTRY_ALIAS) {
		if(!display_msg(MSG_QUEST, NULL, "convert to alias?"))
			return;
		AB_Alias_Call(NULL, 1);
		return;
	}

	fl_deactivate_form(addr_book_obj->Address_Book);
	edit_entry(entry);
	fl_activate_form(addr_book_obj->Address_Book);
}

void AB_Alias_Call(FL_OBJECT * obj, long param) {
	const char *aname, *p;
	AddressBookEntry *entry;

	entry = get_selected_entry();
	if(param && entry == NULL)
		return;

	aname = fl_show_input("Alias name:", "");
	if(aname == NULL || strlen(aname) <= 0)
		return;

	if(strlen(aname) > 16) {
		display_msg(MSG_WARN, "alias", "alias name too long");
		return;
	}

	p = aname;
	while(*p != '\0') {
		if(!(isalnum(*p) || *p == '-' || *p == '_')) {
			display_msg(MSG_WARN, "alias", "invalid character in alias name");
			return;
		}
		p++;
	}

	if (addrbookdb.FindBook(abook_openbook)->FindEntry(aname) != NULL) {
		display_msg(MSG_WARN, "alias",
					"alias %s already exists in this address book", aname);
		return;
	}

	if(param == 0) {
		entry = new AddressBookEntry(NULL, aname);

		entry->SetType(ADDRESSBOOKENTRY_ALIAS);
	} else {
		entry->SetDescription(aname);
		entry->SetType(ADDRESSBOOKENTRY_ALIAS);
	}

	fl_deactivate_form(addr_book_obj->Address_Book);
	edit_entry(entry);
	/* Only add if we have addresses */
	if (param == 0) {
		if (entry->GetAddress() != NULL)
			addrbookdb.FindBook(abook_openbook)->AddEntry(entry);
		else
			delete entry;
	}
	fl_activate_form(addr_book_obj->Address_Book);

	display_book(abook_openbook);
	changed = 1;
}

void AB_Refresh_Book_Call(FL_OBJECT * obj, long param) {
	display_books();
}

void AB_New_Book_Call(FL_OBJECT * obj, long param) {
	const char *p;

	p = fl_show_input("Enter address book name:", "");
	if(p == NULL || strlen(p) == 0)
		return;

	if (is_invalid_addressbook_name(p)) {
		display_msg(MSG_WARN, "address book",
			"Invalid access book name, characters must be from [0-9A-Za-z-_]");
		return;
	}

	addrbookdb.NewBook(p);
	fl_add_browser_line(addr_book_obj->Abooks_Browse, p);
	changed = 1;
}

void AB_Del_Book_Call(FL_OBJECT * obj, long param) {
	int num;
	const char *p;

	num = fl_get_browser(addr_book_obj->Abooks_Browse);
	if (num <= 0)
		return;

	p = fl_get_browser_line(addr_book_obj->Abooks_Browse, num);
	if(p == NULL || strlen(p) == 0)
		return;

	if(strcmp(p, "default") == 0) {
		display_msg(MSG_WARN, "address book",
			"You can not delete default address book");
		return;
	}

	if(!display_msg(MSG_QUEST | MSG_DEFNO, NULL,
		"Address book %s will be deleted\nAre you sure?", p))
		return;

	if (!addrbookdb.DeleteBook(p)) {
		display_msg(MSG_WARN, "address book", "Can not delete book '%s'", p);
		return;
	}

	delbooks.push_back(p);
	abook_openbook = "default";
	fl_delete_browser_line(addr_book_obj->Abooks_Browse, num);
	fl_select_browser_line(addr_book_obj->Abooks_Browse, 1);
	display_book(abook_openbook);
	changed = 1;
}

void AB_PGP_Call(FL_OBJECT * obj, long param) {
	AddressBookEntry *entry;
	char *pgpid;

	entry = get_selected_entry();
	if(entry == NULL || entry->GetAddress() == NULL)
		return;

	pgpid = (char *) fl_get_input(addr_book_obj->AB_PGP);
	if(pgpid != NULL && strlen(pgpid) > 0)
		if(entry->GetAddress()->pgpid == NULL ||
		   strcmp(entry->GetAddress()->pgpid, pgpid) != 0) {
			if (entry->GetAddress()->pgpid != NULL)
				free(entry->GetAddress()->pgpid);
			entry->GetAddress()->pgpid = strdup(pgpid);
		}

	fl_set_focus_object(addr_book_obj->Address_Book,
						addr_book_obj->AB_Input);
	fl_set_input_selected(addr_book_obj->AB_PGP, 1);
	fl_set_input_cursorpos(addr_book_obj->AB_PGP, 0, 0);
}

int mod_addr(struct _mail_addr *oldadr, int *type) {
	struct _mail_addr *newadr;
	int ftype;
	char *p;

	if(modready) {
		XRaiseWindow(fl_display, amod_obj->addrmod->window);
		display_msg(MSG_WARN, "modify address",
					"This dialog is currently in use by another window, close it and try again");
		return -1;
	}

	modready = 1;
	newadr = NULL;
	amod_obj = create_form_addrmod();

	ftype = -1;
	fl_set_input(amod_obj->AMOD_Input,
				 get_charset_addr_line(NULL, oldadr, &ftype));
	if(oldadr->pgpid && *oldadr->pgpid)
		fl_set_input(amod_obj->AMOD_PGP_Input, oldadr->pgpid);
	if(ftype >= 0) {
		fl_set_object_lstyle(amod_obj->AMOD_Input,
			charset_style_from_code(supp_charsets[ftype].charset_code));
		fl_set_object_lsize(amod_obj->AMOD_Input,
			charset_size_from_code(supp_charsets[ftype].charset_code));
		fl_set_object_lstyle(amod_obj->AMOD_PGP_Input,
			charset_style_from_code(supp_charsets[ftype].charset_code));
		fl_set_object_lsize(amod_obj->AMOD_PGP_Input,
			charset_size_from_code(supp_charsets[ftype].charset_code));
	} else {
		fl_set_object_lstyle(amod_obj->AMOD_Input,
			charset_style_from_code(supp_charsets[ftype].charset_code));
		fl_set_object_lsize(amod_obj->AMOD_Input,
			charset_size_from_code(supp_charsets[ftype].charset_code));
		fl_set_object_lstyle(amod_obj->AMOD_PGP_Input,
			charset_style_from_code(supp_charsets[ftype].charset_code));
		fl_set_object_lsize(amod_obj->AMOD_PGP_Input,
			charset_size_from_code(supp_charsets[ftype].charset_code));
	}


	if(type) {
		switch(*type) {
			case TO_TYPE:
				fl_set_button(amod_obj->AMOD_To, 1);
				break;

			case CC_TYPE:
				fl_set_button(amod_obj->AMOD_Cc, 1);
				break;

			case BCC_TYPE:
				fl_set_button(amod_obj->AMOD_Bcc, 1);
				break;

			default:
				fl_set_button(amod_obj->AMOD_To, 1);
				break;
		}
	} else
		fl_set_button(amod_obj->AMOD_To, 1);

	fl_set_form_minsize(amod_obj->addrmod, 470, 120);
	fl_set_form_maxsize(amod_obj->addrmod, 600, 135);
	fl_show_form(amod_obj->addrmod, FL_PLACE_FREE, FL_TRANSIENT,
				 "Modify Address");
	adrwait:
	fl_do_only_forms();
	p = (char *) fl_get_input(amod_obj->AMOD_Input);
	if(p && strlen(p)) {
		if((newadr = get_address(p, ADDR_IGNORE_COMMAS)) == NULL) {
			display_msg(MSG_WARN, "modify",
						"Invalid address, please reenter");
			goto adrwait;
		}
	} else {
		display_msg(MSG_WARN, "modify",
					"Use \"Delete\" button if you want to delete this address");

		fl_hide_form(amod_obj->addrmod);
		fl_free_form(amod_obj->addrmod);
		free(amod_obj);
		amod_obj = NULL;
		modready = 0;

		return -1;
	}

	if(oldadr->addr)
		free(oldadr->addr);
	oldadr->addr = newadr->addr;

	if(oldadr->name)
		free(oldadr->name);
	oldadr->name = newadr->name;

	if(oldadr->comment)
		free(oldadr->comment);
	oldadr->comment = newadr->comment;

	if(oldadr->pgpid)
		free(oldadr->pgpid);
	oldadr->pgpid = strdup(fl_get_input(amod_obj->AMOD_PGP_Input));

	free(newadr);

	if(type) {
		if(fl_get_button(amod_obj->AMOD_To))
			*type = TO_TYPE;
		else if(fl_get_button(amod_obj->AMOD_Cc))
			*type = CC_TYPE;
		else if(fl_get_button(amod_obj->AMOD_Bcc))
			*type = BCC_TYPE;
		else
			*type = TO_TYPE;
	}

	fl_hide_form(amod_obj->addrmod);
	fl_free_form(amod_obj->addrmod);
	free(amod_obj);
	amod_obj = NULL;
	modready = 0;

	return 0;
}

void AMOD_Call(FL_OBJECT * obj, long param) {
}

void display_entry(AddressBookEntry *entry) {
	struct _mail_addr *addr;

	if(entry == NULL)
		return;

	addr = entry->GetAddress();
	fl_freeze_form(alias_obj->aliased);
	fl_set_input(alias_obj->AL_Input,
				 addr != NULL ? get_full_addr_line(addr) : "");
	fl_set_input(alias_obj->AL_InputPGP,
				 (addr != NULL && addr->pgpid != NULL) ? addr->pgpid : "");
	fl_set_input_selected(alias_obj->AL_Input, 1);
	fl_set_input_cursorpos(alias_obj->AL_Input, 0, 0);
	fl_set_input_selected(alias_obj->AL_InputPGP, 1);
	fl_set_input_cursorpos(alias_obj->AL_InputPGP, 0, 0);
	fl_clear_browser(alias_obj->AL_Browse);

	while(addr) {
		fl_add_browser_line(alias_obj->AL_Browse,
							get_full_addr_line(addr));
		addr = addr->next_addr;
	}

	fl_select_browser_line(alias_obj->AL_Browse, 1);
	fl_unfreeze_form(alias_obj->aliased);
}

static void edit_entry(AddressBookEntry *entry) {
	int w, h;
	char geom[16];

	if(entry == NULL)
		return;

	if(already) {
		XRaiseWindow(fl_display, alias_obj->aliased->window);
		display_msg(MSG_WARN, "alias",
					"This dialog is currently in use by another window, close it and try again");
		return;
	}

	already = 1;
	alias_entry = entry;
	alias_obj = create_form_aliased();

	fl_set_browser_fontsize(alias_obj->AL_Browse,
							Config.getInt("AddrBookFSize",
										  FL_NORMAL_SIZE));
	fl_set_browser_fontstyle(alias_obj->AL_Browse,
							 Config.getInt("AddrBookFStyle",
										   FL_NORMAL_STYLE));
	fl_set_object_color(alias_obj->AL_Browse,
						Config.getInt("AddrBookBgCol",
									  FL_TOP_BCOL),
						Config.getInt("AddrBookFgCol",
									  FL_YELLOW));
	fl_set_object_lsize(alias_obj->AL_Input,
						Config.getInt("AddrBookFSize",
									  FL_NORMAL_SIZE));
	fl_set_object_lstyle(alias_obj->AL_Input,
						 Config.getInt("AddrBookFStyle",
									   FL_NORMAL_STYLE));
	fl_set_input_color(alias_obj->AL_Input, FL_BLACK,
					   Config.getInt("AddrBookFgCol",
									 FL_BLUE));
	fl_set_object_color(alias_obj->AL_Input,
						Config.getInt("AddrBookBgCol",
									  FL_BLACK),
						Config.getInt("AddrBookBgCol",
									  FL_BLUE));
	fl_set_object_lsize(alias_obj->AL_InputPGP,
						Config.getInt("AddrBookFSize",
									  FL_NORMAL_SIZE));
	fl_set_object_lstyle(alias_obj->AL_InputPGP,
						 Config.getInt("AddrBookFStyle",
									   FL_NORMAL_STYLE));
	fl_set_input_color(alias_obj->AL_InputPGP, FL_BLACK,
					   Config.getInt("AddrBookFgCol",
									 FL_BLUE));
	fl_set_object_color(alias_obj->AL_InputPGP,
						Config.getInt("AddrBookBgCol",
									  FL_BLACK),
						Config.getInt("AddrBookBgCol",
									  FL_BLUE));
	Config.setFlags("aliasgeom", CF_NOTCHANGED);
	w = 410;
	h = 240;
	sscanf(Config.get("aliasgeom", "").c_str(), "%d %d", &w, &h);
	display_entry(entry);
	fl_set_form_minsize(alias_obj->aliased, 410, 240);
	fl_set_form_maxsize(alias_obj->aliased, 600, 400);
	fl_set_form_size(alias_obj->aliased, w, h);
	fl_show_form(alias_obj->aliased, FL_PLACE_FREE, FL_FULLBORDER,
				 "Alias Members");
	fl_do_only_forms();

	sprintf(geom, "%d %d", alias_obj->aliased->w, alias_obj->aliased->h);
	Config.set("aliasgeom", geom);
	fl_hide_form(alias_obj->aliased);
	fl_free_form(alias_obj->aliased);
	free(alias_obj);
	alias_obj = NULL;
	already = 0;

	alias_entry = NULL;
}

void AL_Add_Call(FL_OBJECT * obj, long param) {
	char *addr = (char *) fl_get_input(alias_obj->AL_Input);
	const char *pgpid;
	AddressBookEntry *entry;
	struct _mail_addr *ma;

	if (alias_entry == NULL)
		return;

	if(strlen(addr) < 2)
		return;

	if(!(ma = get_address(addr, ADDR_IGNORE_COMMAS))) {
		display_msg(MSG_WARN, "add", "Invalid address:\n%s", addr);
		return;
	}

	discard_address(ma->next_addr);
	ma->next_addr = NULL;

	pgpid = fl_get_input(alias_obj->AL_InputPGP);
	if (pgpid != NULL && strlen(pgpid) > 0)
		ma->pgpid = strdup(pgpid);

	entry = alias_entry;
	if (entry == NULL || !entry->AddAddress(ma)) {
		discard_address(ma);
		return;
	}
	discard_address(ma);

	changed = 1;
	display_entry(entry);
	fl_replace_browser_line(addr_book_obj->Address_Browse,
							get_selected_browser_line(), entry_to_browser_line(entry));
}

void AL_Delete_Call(FL_OBJECT * obj, long param) {
	AddressBookEntry *entry;
	int num;

	if (alias_entry == NULL)
		return;

	num = fl_get_browser(alias_obj->AL_Browse);
	if(num < 1)
		return;

	entry = alias_entry;
	if(entry->Size() <= 1) {
		display_msg(MSG_WARN, "delete alias",
					"Alias should have at least one e-mail address in it");
		return;
	}

	if (!entry->DeleteAddress(fl_get_browser_line(alias_obj->AL_Browse, num))) {
		display_msg(MSG_WARN, "delete alias", "delete failed");
		return;
	}

	changed = 1;
	display_entry(entry);

	fl_replace_browser_line(addr_book_obj->Address_Browse,
							get_selected_browser_line(), entry_to_browser_line(entry));
  }

void AL_Modify_Call(FL_OBJECT * obj, long param) {
	char *addr = (char *) fl_get_input(alias_obj->AL_Input);
	struct _mail_addr *ma, *ma1;
	AddressBookEntry *entry;
	char *pgpid;
	int num, i;

	if (alias_entry == NULL)
		return;

	num = fl_get_browser(alias_obj->AL_Browse);
	if(num < 1)
		return;

	entry = alias_entry;
	if (entry == NULL)
		return;

	i = num - 1;
	ma = entry->GetAddress();

	// Determine address being modified
	while(ma != NULL && i--)
		ma = ma->next_addr;

	if(!ma)
		return;

	if(!(ma1 = get_address(addr, ADDR_IGNORE_COMMAS))) {
		display_msg(MSG_WARN, "modify", "Invalid address:\n%s", addr);
		return;
	}

	discard_address(ma1->next_addr);

	pgpid = (char *) fl_get_input(alias_obj->AL_InputPGP);
	if (pgpid != NULL && strlen(pgpid) > 0)
		ma1->pgpid = strdup(pgpid);
	else
		ma1->pgpid = NULL;

	if(ma->addr)
		free(ma->addr);
	if(ma->name)
		free(ma->name);
	if(ma->comment)
		free(ma->comment);
	if(ma->pgpid)
		free(ma->pgpid);

	ma->addr = ma1->addr;
	ma->name = ma1->name;
	ma->comment = ma1->comment;
	ma->pgpid = ma1->pgpid;

	free(ma1);

	changed = 1;
	display_entry(entry);

	fl_replace_browser_line(addr_book_obj->Address_Browse,
							get_selected_browser_line(), entry_to_browser_line(entry));
}

void AL_Call(FL_OBJECT * obj, long param) {
}

void AL_CallPGP(FL_OBJECT * obj, long param) {
}

void AL_Browse_Call(FL_OBJECT * obj, long param) {
	int line = fl_get_browser(obj);
	AddressBookEntry *entry;

	fl_set_input(alias_obj->AL_Input, fl_get_browser_line(obj, line));
	
	entry = browser_line_to_entry(fl_get_browser_line(obj, line));
	fl_set_input(alias_obj->AL_InputPGP,
				 entry->GetAddress()->pgpid != NULL ? entry->GetAddress()->pgpid : "");
	fl_set_input_selected(alias_obj->AL_Input, 1);
	fl_set_input_cursorpos(alias_obj->AL_Input, 0, 0);
	fl_set_input_selected(alias_obj->AL_InputPGP, 1);
	fl_set_input_cursorpos(alias_obj->AL_InputPGP, 0, 0);
}

static void import_book(const char *afile, const char *deffile,
	bool (*convert_func)(FILE *, FILE *))
{
	AddressBook *book;
	FILE *in, *out;
	char buf[64];
	char *bookname;

	if (afile == NULL) {
		fl_set_fselector_title("Select an addressbook to convert");
		afile = fl_show_file_selector("Address book", homedir, ".*", deffile);
		if (afile == NULL)
			return;
	}

	if ((in = fopen(afile, "r")) == NULL) {
		display_msg(MSG_WARN, __func__, "Can not open %s", afile);
		return;
	}

	// XXX Should use input box
	for (int i = 0;; i++) {
		sprintf(buf, "new%d", i);
		if (addrbookdb.FindBook(buf) == NULL)
			break;
	}
	
	/* Get tempfile to write conversion to */
	bookname = get_temp_file(buf);
	if ((out = fopen(bookname, "w")) == NULL) {
		display_msg(MSG_WARN, __func__, "Can not open %s", bookname);
		fclose(in);
		unlink(bookname);
		return;
	}
	
	if (!(convert_func)(in, out)) {
		display_msg(MSG_WARN, __func__, "No addresses found in file");
		fclose(in);
		fclose(out);
		unlink(bookname);
		return;
	}

	fclose(in);
	fclose(out);
	book = new AddressBook(buf);
	if (!book->LoadFromFile(bookname)) {
		display_msg(MSG_WARN, __func__, "Can not load imported address book");
		delete book;
		unlink(bookname);
		return;
	}

	addrbookdb.AddBook(book);
	book->Save(configdir);
	unlink(bookname);

	changed = 1;
	abook_openbook = buf;
}

void AB_Lookup_Call(FL_OBJECT * obj, long data) {
	int keyid;
	char buf[12];
	my_deactivate();
	keyid = PGP_Lookup_PGPId(fl_get_input(addr_book_obj->AB_Input),
							 addr_book_obj->Address_Book);
	my_activate();
	if(keyid) {
		snprintf(buf, sizeof(buf), "0x%X", keyid);
		fl_set_input(addr_book_obj->AB_PGP, buf);
	}
}

void AMOD_Lookup_Call(FL_OBJECT * obj, long data) {
	int keyid;
	char buf[12];

	keyid = PGP_Lookup_PGPId(fl_get_input(amod_obj->AMOD_Input), amod_obj->addrmod);
	if(keyid) {
		snprintf(buf, sizeof(buf), "0x%X", keyid);
		fl_set_input(amod_obj->AMOD_PGP_Input, buf);
	}
}

void AMOD_PGP_Call(FL_OBJECT * obj, long data) {
}

void AL_Lookup_Call(FL_OBJECT * obj, long data) {
	int keyid;
	char buf[12];

	keyid = PGP_Lookup_PGPId(fl_get_input(alias_obj->AL_Input), alias_obj->aliased);
	if(keyid) {
		snprintf(buf, sizeof(buf), "0x%X", keyid);
		fl_set_input(alias_obj->AL_InputPGP, buf);
	}
}
