#include "userlist.h"
#include "session.h"

mwServiceStorage *service_storage = 0;
mwServiceAware *service_aware = 0;
mwServiceResolve *service_resolve = 0;

mwAwareList *aware_list = 0;

HANDLE hContactDeletedEvent = 0;

HANDLE FindContactByUserId(const char *id) {
	char *proto;
	DBVARIANT dbv;
	HANDLE hContact = ( HANDLE )CallService( MS_DB_CONTACT_FINDFIRST, 0, 0 );
	while ( hContact != NULL )
	{
		proto = ( char* )CallService( MS_PROTO_GETCONTACTBASEPROTO, ( WPARAM )hContact,0 );
		if ( proto && !strcmp( PROTO, proto)) {
			if(!DBGetContactSettingUtf(hContact, PROTO, "stid", &dbv)) {
				if(dbv.pszVal && strcmp(id, dbv.pszVal) == 0) {
					DBFreeVariant(&dbv);
					return hContact;
				}
				DBFreeVariant(&dbv);
			}
		}
		hContact = ( HANDLE )CallService( MS_DB_CONTACT_FINDNEXT,( WPARAM )hContact, 0 );
	}	

	return 0;
}

bool GetAwareIdFromContact(HANDLE hContact, mwAwareIdBlock *id_block) {
	char *proto = ( char* )CallService( MS_PROTO_GETCONTACTBASEPROTO, ( WPARAM )hContact,0 );
	DBVARIANT dbv;
	if ( proto && !strcmp( PROTO, proto)) {
		if(!DBGetContactSettingUtf(hContact, PROTO, "stid", &dbv)) {
			if(dbv.pszVal) {
				id_block->type = mwAware_USER;
				id_block->user = _strdup(dbv.pszVal);
				id_block->community = 0;
				DBFreeVariant(&dbv);
				return true;
			}
			DBFreeVariant(&dbv);
		}
	}
	return false;
}

void SetContactGroup(HANDLE hContact, const char *name) {
	if(ServiceExists(MS_CLIST_GROUPRENAME"W"))
		DBWriteContactSettingStringUtf(hContact, "CList", "Group", name);
	else {
		wchar_t buff[512];
		char mb[512];

		MultiByteToWideChar(CP_UTF8, 0, name, -1, buff, 512);
		WideCharToMultiByte(CallService(MS_LANGPACK_GETCODEPAGE, 0, 0), 0, buff, -1, mb, 512, 0, 0);

		DBWriteContactSettingString(hContact, "CList", "Group", mb);
	}
	
}

void AddGroup(const char *name, bool expanded) {
	if(name && strcmp(name, "MetaContacts Hidden Group") == 0)
		return;

	if(name && strcmp(name, Translate("None")) == 0)
		return;

	HANDLE hGroup = (HANDLE)GroupNameExists(name, -1);
	if(!hGroup) {
		wchar_t namew[512];
		MultiByteToWideChar(CP_UTF8, 0, name, -1, namew, 512);

		hGroup = (HANDLE)CallService(MS_CLIST_GROUPCREATE, 0, 0);

		if(ServiceExists(MS_CLIST_GROUPRENAME"W")) {
			CallService(MS_CLIST_GROUPRENAME"W", (WPARAM)hGroup, (LPARAM)namew);
		} else {
			char mb[512];
			WideCharToMultiByte(CallService(MS_LANGPACK_GETCODEPAGE, 0, 0), 0, namew, -1, mb, 512, 0, 0);

			CallService(MS_CLIST_GROUPRENAME, (WPARAM)hGroup, (LPARAM)mb);
		}

	}

	// doesn't call clui! arg!
	//CallService(MS_CLIST_GROUPSETEXPANDED, (WPARAM)hGroup, (LPARAM)(expanded ? 1 : 0));
	
	HWND hwndClist = (HWND)CallService(MS_CLUI_GETHWND, 0, 0);
	HWND hwndClc = FindWindowEx(hwndClist, 0, CLISTCONTROL_CLASS, 0);

	HANDLE hItem = (HANDLE)SendMessage(hwndClc, CLM_FINDGROUP, (WPARAM)hGroup, 0);
	SendMessage(hwndClc, CLM_EXPAND, (WPARAM)hItem, (LPARAM) (expanded ? 1 : 0));
}


HANDLE AddContact(mwSametimeUser *user, bool temporary) {
	const char *id = mwSametimeUser_getUser(user);
	const char *name = mwSametimeUser_getShortName(user);
	const char *nick = mwSametimeUser_getAlias(user);
	//const char *nick = mwSametimeUser_getShortName(user);
	mwSametimeUserType type = mwSametimeUser_getType(user);

	HANDLE hContact = FindContactByUserId(id);
	bool new_contact = false;
	if(!hContact) {
		hContact = (HANDLE)CallService(MS_DB_CONTACT_ADD, 0, 0);
		new_contact = true;
	} else if(!temporary) {
		DBDeleteContactSetting(hContact, "CList", "NotOnList");
		DBDeleteContactSetting(hContact, "CList", "Hidden");
	}

	if(hContact) {
		// add to miranda
		if(new_contact) DBWriteContactSettingStringUtf(hContact, PROTO, "stid", id);

		if(name && strlen(name))
			DBWriteContactSettingStringUtf(hContact, PROTO, "Name", name);

		if(nick && strlen(nick)) {
			DBWriteContactSettingStringUtf(hContact, PROTO, "Nick", nick);
		} else if(name && strlen(name)) {
			DBWriteContactSettingStringUtf(hContact, PROTO, "Nick", name);
		} else {
			DBWriteContactSettingStringUtf(hContact, PROTO, "Nick", id);
		}

		DBWriteContactSettingByte(hContact, PROTO, "type", (BYTE)type);

		if(new_contact) {
			CallService(MS_PROTO_ADDTOCONTACT, (WPARAM)hContact, (LPARAM)PROTO);

			//add to our awareness list
			mwAwareIdBlock id_block;
			if(GetAwareIdFromContact(hContact, &id_block)) {
				
				GList *gl;

				gl = g_list_prepend(NULL, &id_block);
				mwAwareList_addAware(aware_list, gl);

				g_list_free(gl);  

				free(id_block.user);
			}
		}

		if(temporary) {
			DBWriteContactSettingByte(hContact, "CList", "NotOnList", 1);
			DBWriteContactSettingByte(hContact, "CList", "Hidden", 1);
		} else {
			DBDeleteContactSetting(hContact, "CList", "NotOnList");
			DBDeleteContactSetting(hContact, "CList", "Hidden");
		}
	}

	return hContact;
}

void ImportContactsFromList(mwSametimeList *user_list, bool temporary) {
	// add contacts
	mwSametimeGroup *stgroup;
	mwSametimeUser *stuser;

	GList *gl, *gtl, *ul, *utl;

	const char *group_name;
	const char *group_alias;
	mwSametimeGroupType group_type;
	bool group_open;

	gl = gtl = mwSametimeList_getGroups(user_list);
	for(; gl; gl = gl->next) {
		char buff[256];

		stgroup = (mwSametimeGroup *) gl->data;

		group_name = mwSametimeGroup_getName(stgroup);
		group_alias = mwSametimeGroup_getAlias(stgroup);
		if(!group_alias) group_alias = group_name;

		group_type = mwSametimeGroup_getType(stgroup);
		group_open = (mwSametimeGroup_isOpen(stgroup) != 0);

		mir_snprintf(buff, 256, "GN_%s", group_alias);
		DBWriteContactSettingStringUtf(0, PROTO_GROUPS, buff, group_name);
		mir_snprintf(buff, 256, "GT_%s", group_alias);
		DBWriteContactSettingByte(0, PROTO_GROUPS, buff, (BYTE)group_type);
		mir_snprintf(buff, 256, "GO_%s", group_alias);
		DBWriteContactSettingByte(0, PROTO_GROUPS, buff, (BYTE)(group_open ? 1 : 0));

		// inverse mapping
		mir_snprintf(buff, 256, "GA_%s", group_name);
		DBWriteContactSettingStringUtf(0, PROTO_GROUPS, buff, group_alias);

		AddGroup(group_alias, group_open);

		if(group_type == mwSametimeGroup_DYNAMIC) {
			
			mwAwareIdBlock id_block;
			id_block.type = mwAware_GROUP;
			id_block.user = (char *)group_name;
			id_block.community = 0;

			GList *gl;

			gl = g_list_prepend(NULL, &id_block);
			mwAwareList_addAware(aware_list, gl);

			g_list_free(gl);  
		}

		ul = utl = mwSametimeGroup_getUsers(stgroup);
		for(; ul; ul = ul->next) {

			stuser = (mwSametimeUser *) ul->data;
			HANDLE hContact = AddContact(stuser, temporary);
		
			if(hContact && group_alias && strcmp(group_alias, Translate("None")) != 0 && strcmp(group_alias, "MetaContacts Hidden Group") != 0) {
				SetContactGroup(hContact, group_alias);
				// mark contact as belonging to dynamic group
			}
		}
		g_list_free(utl);
	}
	g_list_free(gtl);


}

void ExportContactsToList(mwSametimeList *user_list) {
	mwSametimeGroup *stgroup = 0;
	char *group_name;
	char *group_alias;
	mwSametimeGroupType group_type;
	bool group_open;

	mwSametimeUser *stuser;
	char *user_alias;
	char *user_shortName;
	mwSametimeUserType user_type;

	char *proto;
	DBVARIANT dbv, dbv2;
	char buff[256];
	HANDLE hContact = ( HANDLE )CallService( MS_DB_CONTACT_FINDFIRST, 0, 0 );
	mwAwareIdBlock id_block;

	mwIdBlock uid;

	GList *gl = 0;
	while ( hContact != NULL )
	{
		proto = ( char* )CallService( MS_PROTO_GETCONTACTBASEPROTO, ( WPARAM )hContact,0 );
		if ( proto && !strcmp( PROTO, proto)) {
			if(!DBGetContactSettingUtf(hContact, PROTO, "stid", &dbv)) {
				if(dbv.pszVal) {
					if(GetAwareIdFromContact(hContact, &id_block)) {
						if(!DBGetContactSettingUtf(hContact, "CList", "Group", &dbv2)) {
							group_alias = _strdup(dbv2.pszVal);
							DBFreeVariant(&dbv2);
						} else
							group_alias = _strdup(Translate("None"));

						if(group_alias) {
							mir_snprintf(buff, 256, "GT_%s", group_alias);
							group_type = (mwSametimeGroupType)DBGetContactSettingByte(0, PROTO_GROUPS, buff, (BYTE)mwSametimeGroup_NORMAL);
							// apparently we don't want to upload contacts in dynamic groups - see gaim sametime plugin comments
							if(group_type == mwSametimeGroup_DYNAMIC) {
								DBFreeVariant(&dbv);
								free(id_block.user);
								free(group_alias);
								hContact = ( HANDLE )CallService( MS_DB_CONTACT_FINDNEXT,( WPARAM )hContact, 0 );
								continue;
							}


							mir_snprintf(buff, 256, "GN_%s", group_alias);
							if(!DBGetContactSettingUtf(0, PROTO_GROUPS, buff, &dbv2)) {
								group_name = _strdup(dbv2.pszVal);
								DBFreeVariant(&dbv2);
							} else
								group_name = _strdup(group_alias);

							//group_open = (DBGetContactSettingByte(0, PROTO_GROUPS, buff, 0) == 1);
							HANDLE hGroup = (HANDLE)GroupNameExists(group_alias, -1);
							if(hGroup) {
								int expanded;
								CallService(MS_CLIST_GROUPGETNAME, (WPARAM)hGroup, (LPARAM)&expanded);
								group_open = (expanded != 0);
							} else {
								mir_snprintf(buff, 256, "GO_%s", group_alias);
								group_open = (DBGetContactSettingByte(0, PROTO_GROUPS, buff, 0) == 1);
							}


							stgroup = 0;
							stgroup = mwSametimeList_findGroup(user_list, group_name);
							if(!stgroup) {
								if(group_name) stgroup = mwSametimeGroup_new(user_list, group_type, group_name);
								mwSametimeGroup_setAlias(stgroup, group_alias);							
								mwSametimeGroup_setOpen(stgroup, group_open);
							}

							free(group_name);
							free(group_alias);

							if(!DBGetContactSettingUtf(hContact, PROTO, "Name", &dbv2)) {
								user_shortName = _strdup(dbv2.pszVal);
								DBFreeVariant(&dbv2);
							} else
								user_shortName = 0;

							if(!DBGetContactSettingUtf(hContact, "CList", "MyHandle", &dbv2)) {
								user_alias = _strdup(dbv2.pszVal);
								DBFreeVariant(&dbv2);
							} else
								user_alias = 0;

							user_type = (mwSametimeUserType)DBGetContactSettingByte(hContact, PROTO, "type", (BYTE)mwSametimeUser_NORMAL);

							uid.user = id_block.user;
							uid.community = id_block.community;

							stuser = mwSametimeUser_new(stgroup, user_type, &uid);
							if(user_shortName) {
								mwSametimeUser_setShortName(stuser, user_shortName);
								free(user_shortName);
							}
							if(user_alias) {
								mwSametimeUser_setAlias(stuser, user_alias);
								free(user_alias);
							}
						}

						free(id_block.user);
					}
				}
				DBFreeVariant(&dbv);
			}
		}
		hContact = ( HANDLE )CallService( MS_DB_CONTACT_FINDNEXT,( WPARAM )hContact, 0 );
	}
}

void ImportContactsFromFile(TCHAR *filename) {
#ifdef _UNICODE
#else
	std::ifstream in(filename);
	std::string text;
	std::string line;
	if(in.is_open()) {
		while(!in.eof()) {
			std::getline(in, line);
			text += line;
			text += "\r\n";
		}
		in.close();

		mwSametimeList *new_list = mwSametimeList_load(text.c_str());

		ImportContactsFromList(new_list, false);

		mwSametimeList_free(new_list);

	}
#endif

}

void ExportContactsToServer() {
	mwSametimeList *user_list;
	mwStorageUnit *unit;

	mwPutBuffer *buff;
	mwOpaque *op;

	if(MW_SERVICE_IS_DEAD(service_storage)) {
		//MessageBox(0, "Failed to upload contacts - Storage service unavailable.", "Error", MB_OK);
		ShowError(TranslateT("Failed to upload contacts - Storage service unavailable."));
		return;
	}

	user_list = mwSametimeList_new();
	ExportContactsToList(user_list);

	buff = mwPutBuffer_new();
	mwSametimeList_put(buff, user_list);
	mwSametimeList_free(user_list);

	/* put the buffer contents into a storage unit */
	unit = mwStorageUnit_new(mwStore_AWARE_LIST);
	op = mwStorageUnit_asOpaque(unit);
	mwPutBuffer_finalize(op, buff);

	/* save the storage unit to the service */
	mwServiceStorage_save(service_storage, unit, NULL, NULL, NULL);
}

void load_users_callback(mwServiceStorage *srvc, guint32 result, mwStorageUnit *item, gpointer data) {
	if(mwStorageUnit_getKey(item) == mwStore_AWARE_LIST) {
		mwGetBuffer *buff = mwGetBuffer_wrap(mwStorageUnit_asOpaque(item));

		if(mwGetBuffer_remaining(buff)) {
			mwSametimeList *user_list = mwSametimeList_new();

			mwSametimeList_get(buff, user_list);
			ImportContactsFromList(user_list, false);
			
			mwSametimeList_free(user_list);
		}
	}
}


void UserListAddStored() {
	mwStorageUnit *unit;

	unit = mwStorageUnit_new(mwStore_AWARE_LIST);
	mwServiceStorage_load(service_storage, unit, load_users_callback, 0, 0); 
}

int ContactDeleted(WPARAM wParam, LPARAM lParam) {
	mwAwareIdBlock id_block;
	HANDLE hContact = (HANDLE)wParam;
	
	if(DBGetContactSettingByte(hContact, PROTO, "ChatRoom", 0))
		return 0;

	if(GetAwareIdFromContact(hContact, &id_block)) {
		GList *gl;

		gl = g_list_prepend(NULL, &id_block);
		mwAwareList_removeAware(aware_list, gl);

		g_list_free(gl);  

		free(id_block.user);
	}

	return 0;
}

void mwServiceAware_on_attrib(mwServiceAware *srvc, mwAwareAttribute *attrib) {

}


void mwServiceAware_clear(mwServiceAware *srvc) {
}


mwAwareHandler mwAware_handler = {
	mwServiceAware_on_attrib,
	mwServiceAware_clear
};

void mwResolve_handler_dyngroup(mwServiceResolve *srvc, guint32 id, guint32 code, GList *results, gpointer data) {
	mwResolveResult *result;
	mwResolveMatch *match;

	mwSametimeGroup *stgroup = (mwSametimeGroup *)data;

	g_return_if_fail(results != NULL);

	if(results) {
		result = (mwResolveResult *)results->data;
		if(result && result->matches) {

			match = (mwResolveMatch *)result->matches->data;
			if(match) {
				mwIdBlock uid;
				uid.user = match->id;
				uid.community = 0;
				mwSametimeUser *stuser = mwSametimeUser_new(stgroup, mwSametimeUser_NORMAL, &uid);
				mwSametimeUser_setShortName(stuser, match->name);

				HANDLE hContact = AddContact(stuser, false);

				const char *group_name = mwSametimeGroup_getName(stgroup);
				const char *group_alias = mwSametimeGroup_getAlias(stgroup);
				if(!group_alias) group_alias = group_name;
				if(hContact && group_alias && strcmp(group_alias, Translate("None")) && strcmp(group_alias, "MetaContacts Hidden Group")) {
					SetContactGroup(hContact, group_alias);
				}
			}
		}
	}
	

	if(stgroup)
		mwSametimeList_free(mwSametimeGroup_getList(stgroup));
}

void mwAwareList_on_aware(mwAwareList *list, mwAwareSnapshot *aware) {
	HANDLE hContact = FindContactByUserId(aware->id.user);
	char *group = 0;
	DBVARIANT dbv;
	
	// update self - necessary for some servers
	if(aware->online && !DBGetContactSettingUtf(0, PROTO, "stid", &dbv) && strcmp(aware->id.user, dbv.pszVal) == 0) {
		int new_status = ID_STATUS_OFFLINE;

		switch(aware->status.status) {
		case mwStatus_ACTIVE:	
			new_status = ID_STATUS_ONLINE; 
			break;
		case mwStatus_AWAY:		
			new_status = ID_STATUS_AWAY; 
			break;
		case mwStatus_IDLE:		
			new_status = ID_STATUS_IDLE;
			break;
		case mwStatus_BUSY:		
			new_status = ID_STATUS_DND; 
			break;
		}
		if(new_status != ID_STATUS_IDLE) //SetSessionStatus(new_status);
			UpdateSelfStatus();

		DBFreeVariant(&dbv);
	}

	if(hContact && !DBGetContactSettingUtf(hContact, "CList", "Group", &dbv)) {
		group = _strdup(dbv.pszVal);
		DBFreeVariant(&dbv);
	}
	
	if(aware->group && (!group || strcmp(aware->group, group) || !hContact)) { 
		// dynamic group member we're not already aware of
		// resolve server alias to user id via resolver
		mwSametimeList *user_list = mwSametimeList_new();
		mwSametimeGroup *stgroup = mwSametimeGroup_new(user_list, mwSametimeGroup_DYNAMIC, aware->group);
		char buff[256];
		mir_snprintf(buff, 256, "GA_%s", aware->group);
		if(!DBGetContactSettingUtf(0, PROTO_GROUPS, buff, &dbv)) {
			mwSametimeGroup_setAlias(stgroup, dbv.pszVal);
			DBFreeVariant(&dbv);
		}

		GList *query = g_list_prepend(0, (void *)aware->id.user);

		mwServiceResolve_resolve(service_resolve, query, mwResolveFlag_USERS, mwResolve_handler_dyngroup, (gpointer)stgroup, 0);

		g_list_free(query);
	} else if(hContact) {

		if(aware->online) {
			int new_status = ID_STATUS_OFFLINE;

			switch(aware->status.status) {
			case mwStatus_ACTIVE:	
				new_status = ID_STATUS_ONLINE; 
				DBWriteContactSettingDword(hContact, PROTO, "IdleTS", 0);
				DBWriteContactSettingWord(hContact, PROTO, "Status", new_status);
				break;
			case mwStatus_AWAY:		
				new_status = ID_STATUS_AWAY; 
				DBWriteContactSettingDword(hContact, PROTO, "IdleTS", 0);
				DBWriteContactSettingWord(hContact, PROTO, "Status", new_status);
				break;
			case mwStatus_IDLE:		
				if(options.idle_as_away) {
					new_status = ID_STATUS_AWAY;
					DBWriteContactSettingWord(hContact, PROTO, "Status", new_status);
				}
				DBWriteContactSettingDword(hContact, PROTO, "IdleTS", (DWORD)time(0));
				break;
			case mwStatus_BUSY:		
				new_status = ID_STATUS_DND; 
				DBWriteContactSettingWord(hContact, PROTO, "Status", new_status);
				DBWriteContactSettingDword(hContact, PROTO, "IdleTS", 0);
				break;
			}
		} else
			DBWriteContactSettingWord(hContact, PROTO, "Status", ID_STATUS_OFFLINE);

		if(service_aware) {
			const char *desc = mwServiceAware_getText(service_aware, &aware->id);
			if(desc)
				//DBWriteContactSettingStringUtf(hContact, PROTO, "StatusMsg", desc);
				DBWriteContactSettingStringUtf(hContact, "CList", "StatusMsg", desc);
			else
				//DBWriteContactSettingStringUtf(hContact, PROTO, "StatusMsg", "");
				//DBDeleteContactSetting(hContact, PROTO, "StatusMsg");
				DBDeleteContactSetting(hContact, "CList", "StatusMsg");
		}
	}

	if(group) free(group);
}


void mwAwareList_on_attrib(mwAwareList *list, mwAwareIdBlock *id, mwAwareAttribute *attrib) {
}


void mwAwareList_clear(mwAwareList *list) {
}


mwAwareListHandler mwAwareList_handler = {
	mwAwareList_on_aware,
	mwAwareList_on_attrib,
	mwAwareList_clear
};

void UserListCreate() {
	mwServiceAware_unsetAttribute(service_aware, mwAttribute_SPEAKERS);
	mwServiceAware_unsetAttribute(service_aware, mwAttribute_MICROPHONE);
	mwServiceAware_unsetAttribute(service_aware, mwAttribute_VIDEO_CAMERA);

	mwServiceAware_setAttributeBoolean(service_aware, mwAttribute_AV_PREFS_SET, TRUE);

	mwServiceAware_setAttributeBoolean(service_aware, mwAttribute_FILE_TRANSFER, TRUE);

	
	aware_list = mwAwareList_new(service_aware, &mwAwareList_handler);

	// add all contacts

	char *proto;
	DBVARIANT dbv;
	HANDLE hContact = ( HANDLE )CallService( MS_DB_CONTACT_FINDFIRST, 0, 0 );
	mwAwareIdBlock id_block;
	GList *gl = 0;

	while ( hContact != NULL )
	{
		proto = ( char* )CallService( MS_PROTO_GETCONTACTBASEPROTO, ( WPARAM )hContact,0 );
		if (DBGetContactSettingByte(hContact, PROTO, "ChatRoom", 0) == 0 &&  proto && !strcmp( PROTO, proto)) {
			if(!DBGetContactSettingUtf(hContact, PROTO, "stid", &dbv)) {
				if(dbv.pszVal) {
					if(GetAwareIdFromContact(hContact, &id_block)) {
						// add user to aware list
						gl = g_list_prepend(0, &id_block);

						mwAwareList_addAware(aware_list, gl);
						
						free(id_block.user);
						g_list_free(gl);
					}
				}
				DBFreeVariant(&dbv);
			}
		}
		hContact = ( HANDLE )CallService( MS_DB_CONTACT_FINDNEXT,( WPARAM )hContact, 0 );
	}	


	// add self - might be necessary for some servers
	if(!DBGetContactSettingUtf(0, PROTO, "stid", &dbv)) {
		id_block.type = mwAware_USER;
		id_block.user = dbv.pszVal;
		id_block.community = 0;

		gl = g_list_prepend(0, &id_block);
		mwAwareList_addAware(aware_list, gl);
		g_list_free(gl);

		DBFreeVariant(&dbv);
	}
}

void UserListDestroy() {
	mwAwareList_free(aware_list);
	aware_list = 0;
}

void UserRecvAwayMessage(HANDLE hContact) {

	DBVARIANT dbv;
	char buff[512];
	buff[0] = 0;

	if(!DBGetContactSettingUtf(hContact, "CList", "StatusMsg", &dbv) && strlen(dbv.pszVal)) {
		strncpy(buff, dbv.pszVal, 512);
		buff[511] = 0;
		DBFreeVariant(&dbv);
	}

	CCSDATA ccs = {0};
	PROTORECVEVENT pre = {0};

	ccs.hContact = hContact;
	ccs.szProtoService = PSR_AWAYMSG;
	ccs.wParam = 0;
	ccs.lParam = (LPARAM)⪯

	pre.timestamp = (DWORD)time(0);
	if(strlen(buff))
		pre.szMessage = buff;
	else
		pre.szMessage = 0;

	CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs);
}

void mwResolve_handler(mwServiceResolve *srvc, guint32 id, guint32 code, GList *results, gpointer data) {
	bool advanced = (data != 0);

	MYCUSTOMSEARCHRESULTS mcsr = {0};
	mcsr.nSize = sizeof(MYCUSTOMSEARCHRESULTS);
	//MYPROTOSEARCHRESULT mpsr = {0};
	//mpsr.cbSize = sizeof(MYPROTOSEARCHRESULT);
	mcsr.psr.nick = mcsr.psr.name;

	mcsr.nFieldCount = 4;
	TCHAR fields[4][512];
	TCHAR *fields_addr[4];
	mcsr.pszFields = fields_addr;
	mcsr.pszFields[0] = fields[0];
	mcsr.pszFields[1] = fields[1];
	mcsr.pszFields[2] = fields[2];
	mcsr.pszFields[3] = fields[3];

	if(advanced) {
		// send column names
		mcsr.psr.cbSize = 0;
		_tcsncpy(mcsr.pszFields[0], TranslateT("Id"), 512);
		_tcsncpy(mcsr.pszFields[1], TranslateT("Name"), 512);
		_tcsncpy(mcsr.pszFields[2], TranslateT("Description"), 512);
		_tcsncpy(mcsr.pszFields[3], TranslateT("Group?"), 512);
		ProtoBroadcastAck(PROTO,NULL,ACKTYPE_SEARCH, ACKRESULT_SEARCHRESULT, (HANDLE)id, (LPARAM)&mcsr);
	}

	mcsr.psr.cbSize = sizeof(MYPROTOSEARCHRESULT);

	if(code == mwResolveCode_SUCCESS) {
		GList *ri = results, *mri;
		for(;ri;ri = ri->next) {
			mri = ((mwResolveResult *)ri->data)->matches;
			for(;mri;mri = mri->next) {
				strncpy(mcsr.psr.stid, ((mwResolveMatch *)mri->data)->id, 256);
				mcsr.psr.stid[255] = 0;
				MultiByteToWideChar(CP_UTF8, 0, mcsr.psr.stid, -1, mcsr.pszFields[0], 512);

				strncpy(mcsr.psr.name, ((mwResolveMatch *)mri->data)->name, 256);
				mcsr.psr.name[255] = 0;
				MultiByteToWideChar(CP_UTF8, 0, mcsr.psr.name, -1, mcsr.pszFields[1], 512);

				if(((mwResolveMatch *)mri->data)->desc)
					MultiByteToWideChar(CP_UTF8, 0, ((mwResolveMatch *)mri->data)->desc, -1, mcsr.pszFields[2], 512);
				else
					mcsr.pszFields[2][0] = 0;

				mcsr.psr.group = (((mwResolveMatch *)mri->data)->type == mwResolveMatch_GROUP);
				//MultiByteToWideChar(CP_UTF8, 0, mcsr.psr.name, -1, mcsr.pszFields[1], 512);
				_tcsncpy(mcsr.pszFields[3], mcsr.psr.group ? TranslateT("True") : TranslateT("False"), 512);

				if(advanced) 
					ProtoBroadcastAck(PROTO,NULL,ACKTYPE_SEARCH, ACKRESULT_SEARCHRESULT, (HANDLE)id, (LPARAM)&mcsr);
				else
					ProtoBroadcastAck(PROTO,NULL,ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)id, (LPARAM)&mcsr.psr);
			}
		}
		ProtoBroadcastAck(PROTO,NULL,ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0);
	}
}

void mwResolve_handler_details(mwServiceResolve *srvc, guint32 id, guint32 code, GList *results, gpointer data) {
	MYPROTOSEARCHRESULT mpsr = {0};
	mpsr.cbSize = sizeof(mpsr);
	mpsr.nick = mpsr.name;

	if(code == mwResolveCode_SUCCESS) {
		GList *ri = results, *mri;
		for(;ri;ri = ri->next) {
			mri = ((mwResolveResult *)ri->data)->matches;
			for(;mri;mri = mri->next) {

				HANDLE hContact = FindContactByUserId(((mwResolveMatch *)mri->data)->id);
				if(hContact) {
					char *name = ((mwResolveMatch *)mri->data)->name;
					if(name && strlen(name)) {
						DBWriteContactSettingStringUtf(hContact, PROTO, "Name", name);
						DBWriteContactSettingStringUtf(hContact, PROTO, "Nick", name);
					}
				}
			}
		}
	}
}

int SearchForUser(const char *name) {
	if(current_status != ID_STATUS_OFFLINE && service_resolve) {
		GList *query = g_list_prepend(0, (void *)name);

		guint32 id = mwServiceResolve_resolve(service_resolve, query, (mwResolveFlag)(mwResolveFlag_USERS | mwResolveFlag_GROUPS), mwResolve_handler, 0, 0);

		g_list_free(query);
		return id; // search handle
	}

	return 0; // fail
}

int GetMoreDetails(const char *name) {
	if(current_status != ID_STATUS_OFFLINE && service_resolve) {
		GList *query = g_list_prepend(0, (void *)name);

		guint32 id = mwServiceResolve_resolve(service_resolve, query, (mwResolveFlag)(mwResolveFlag_USERS | mwResolveFlag_UNIQUE), mwResolve_handler_details, 0, 0);

		g_list_free(query);
		return id; // search handle
	}

	return 0; // fail
}

static BOOL CALLBACK SearchDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch ( msg ) {
	case WM_INITDIALOG: 
		{
			TranslateDialogDefault( hwndDlg );
			break;
		}
	}
	return 0;
}

int CreateSearchDialog(WPARAM wParam, LPARAM lParam) {
	//MessageBox(0, _T("Creating Dialog"), _T("CreateSearchDialog"), MB_OK);
	return (int)CreateDialog(hInst, MAKEINTRESOURCE(IDD_USERSEARCH), (HWND)lParam, SearchDialogFunc);
}

int SearchFromDialog(WPARAM wParam, LPARAM lParam) {
	//MessageBox(0, _T("Searching..."), _T("SearchFromDialog"), MB_OK);
	HWND hWnd = (HWND)lParam;
	TCHAR buf[512];
	if(GetDlgItemText(hWnd, IDC_EDIT1, buf, 512)) {
		if(current_status != ID_STATUS_OFFLINE && service_resolve) {
			char name[512];
#ifdef _UNICODE
			WideCharToMultiByte(CP_UTF8, 0, buf, -1, name, 512, 0, 0);
#else
			strncpy(name, buf, 512);
#endif

			GList *query = g_list_prepend(0, (void *)name);

			guint32 id = mwServiceResolve_resolve(service_resolve, query, (mwResolveFlag)(mwResolveFlag_USERS | mwResolveFlag_GROUPS), mwResolve_handler, (void *)1, 0);

			g_list_free(query);
			return id; // search handle
		}
	}
	return 0;
}

HANDLE AddSearchedUser(MYPROTOSEARCHRESULT *mpsr, bool temporary) {
	HANDLE hContact = 0;

	mwSametimeList *user_list = mwSametimeList_new();
	mwSametimeGroup *stgroup = 0;
	if(mpsr->group) {
		 stgroup = mwSametimeGroup_new(user_list, mwSametimeGroup_DYNAMIC, mpsr->stid);
		 mwSametimeGroup_setAlias(stgroup, mpsr->name);
		 ImportContactsFromList(user_list, temporary);
	} else {
		stgroup = mwSametimeGroup_new(user_list, mwSametimeGroup_NORMAL, Translate("None"));

		mwIdBlock uid;
		uid.user = mpsr->stid;
		uid.community = 0;
		mwSametimeUser *stuser = mwSametimeUser_new(stgroup, mwSametimeUser_NORMAL, &uid);
		mwSametimeUser_setShortName(stuser, mpsr->name);

		hContact = AddContact(stuser, temporary);
		mwSametimeList_free(mwSametimeGroup_getList(stgroup));
	}	


	return hContact;
}

void InitUserList(mwSession *session) {
	mwSession_addService(session, (mwService *)(service_storage = mwServiceStorage_new(session)));
	mwSession_addService(session, (mwService *)(service_resolve = mwServiceResolve_new(session)));
	mwSession_addService(session, (mwService *)(service_aware = mwServiceAware_new(session, &mwAware_handler)));

	hContactDeletedEvent = HookEvent(ME_DB_CONTACT_DELETED, ContactDeleted);
}

void DeinitUserList(mwSession *session) {
	UnhookEvent(hContactDeletedEvent);
	hContactDeletedEvent = 0;

	mwSession_removeService(session, mwService_AWARE);
	mwService_free((mwService *)service_aware);
	service_aware = 0;

	mwSession_removeService(session, mwService_RESOLVE);
	mwService_free((mwService *)service_resolve);
	service_resolve = 0;

	mwSession_removeService(session, mwService_STORAGE);
	mwService_free((mwService *)service_storage);
	service_storage = 0;
}