#include "skype.h"
#include "skype_chat.h"

enum CHAT_LIST_MENU
{
	ICM_CANCEL,

	ICM_DETAILS,
	ICM_AUTH_REQUEST,
	ICM_CONF_INVITE,
	ICM_ROLE, ICM_ROLE_ADMIN, ICM_ROLE_SPEAKER, ICM_ROLE_WRITER, ICM_ROLE_SPECTATOR,
	ICM_ADD, ICM_KICK, ICM_BAN,
	ICM_COPY_SID, ICM_COPY_URI
};

static struct gc_item crListItems[] =
{
	{ LPGENT("&User details"),        ICM_DETAILS,            MENU_ITEM      }, 
	{ LPGENT("&Request auth"),        ICM_AUTH_REQUEST,       MENU_ITEM      },
	{ NULL,                           0,                      MENU_SEPARATOR },
	{ LPGENT("Invite to conference"), ICM_CONF_INVITE,        MENU_ITEM      },
	{ NULL,                           0,                      MENU_SEPARATOR },
	{ LPGENT("Set &role"),            ICM_ROLE,               MENU_NEWPOPUP  },
	{ LPGENT("&Master"),              ICM_ROLE_ADMIN,         MENU_POPUPITEM },
	{ LPGENT("&Helper"),              ICM_ROLE_SPEAKER,       MENU_POPUPITEM },
	{ LPGENT("&User"),                ICM_ROLE_WRITER,        MENU_POPUPITEM },
	{ LPGENT("&Listener"),            ICM_ROLE_SPECTATOR,     MENU_POPUPITEM },
	{ NULL,                           0,                      MENU_SEPARATOR },
	{ LPGENT("&Add"),                 ICM_ADD,                MENU_ITEM      },
	{ LPGENT("&Kick"),                ICM_KICK,               MENU_ITEM      },
	{ LPGENT("Outlaw (&ban)"),        ICM_BAN,                MENU_ITEM      },	
	{ NULL,                           0,                      MENU_SEPARATOR },
	{ LPGENT("Copy &skypename"),      ICM_COPY_SID,           MENU_ITEM      },
	{ LPGENT("Copy room &uri"),       ICM_COPY_URI,           MENU_ITEM      }
};

static void CheckChatMenuItem(CHAT_LIST_MENU checkedId)
{
	for (int i = 0; i < SIZEOF(crListItems); i++)
	{
		if (crListItems[i].dwID == checkedId)
		{
			if (crListItems[i].uType == MENU_ITEM)
				crListItems[i].uType = MENU_CHECK;
			else if (crListItems[i].uType == MENU_POPUPITEM)
				crListItems[i].uType = MENU_POPUPCHECK;
			break;
		}
	}
}

static void DisableChatMenuItem(CHAT_LIST_MENU disabledId)
{
	for (int i = 0; i < SIZEOF(crListItems); i++)
	{
		if (crListItems[i].dwID == disabledId)
		{
			crListItems[i].bDisabled = TRUE;
			break;
		}
	}
}

static void DisableChatMenuItems(CHAT_LIST_MENU disabledIds[])
{
	for (int i = 0; i < SIZEOF(disabledIds); i++)
		DisableChatMenuItem(disabledIds[i]);
}

static void ResetChatMenuItem()
{
	for (int i = 0; i < SIZEOF(crListItems); i++)
	{
		crListItems[i].bDisabled = FALSE;
		if (crListItems[i].uType == MENU_CHECK)
			crListItems[i].uType = MENU_ITEM;
		else if (crListItems[i].uType == MENU_POPUPCHECK)
			crListItems[i].uType = MENU_POPUPITEM;
	}
}

void CSkypeProto::InitChatModule()
{
	GCREGISTER gcr = {0};
	gcr.cbSize = sizeof(gcr);
	gcr.iMaxText = 0;
	gcr.ptszDispName = this->m_tszUserName;
	gcr.pszModule = this->m_szModuleName;
	::CallServiceSync(MS_GC_REGISTER, 0, (LPARAM)&gcr);

	this->HookProtoEvent(ME_GC_EVENT, &CSkypeProto::OnGCEventHook);
	this->HookProtoEvent(ME_GC_BUILDMENU, &CSkypeProto::OnGCMenuHook);
}

///

TCHAR *ChatRoom::Roles[] = 
{ 
	_T(""),			// ---
	LPGENT("Creator"),		// CREATOR	= 1
	LPGENT("Master"),		// ADMIN	= 2
	LPGENT("Helper"),		// SPEAKER	= 3
	LPGENT("User"),		// WRITER	= 4
	LPGENT("Listener"),	// SPECTATOR= 5
	LPGENT("Applicant"),	// APPLICANT= 6
	LPGENT("Retried"),		// RETIRED	= 7
	LPGENT("Outlaw"),		// OUTLAW	= 8
};

ChatRoom::ChatRoom(const wchar_t *cid) : members(1, CompareMembers) 
{
	this->cid = ::mir_wstrdup(cid);
	this->name = NULL;
	this->me = NULL;
}

ChatRoom::ChatRoom(const wchar_t *cid, const wchar_t *name, CSkypeProto *ppro) : members(1, CompareMembers) 
{
	this->cid = ::mir_wstrdup(cid);
	this->name = ::mir_wstrdup(name);
	this->ppro = ppro;
	//
	this->me = new ChatMember(ppro->login);
	this->me->SetNick(::TranslateT("me"));
	this->me->SetRank(0);
	this->me->SetStatus(ID_STATUS_OFFLINE);
	//
	this->sys = new ChatMember(L"sys");
	this->sys->SetNick(L"System");
	this->sys->SetRank(0);
	this->sys->SetStatus(ID_STATUS_OFFLINE);
}

ChatRoom::~ChatRoom()
{
	::mir_free(this->cid);
	::mir_free(this->name);
	delete this->me;
	delete this->sys;
	this->members.destroy();
}

HANDLE ChatRoom::GetContactHandle() const
{
	return this->hContact;
}

void ChatRoom::CreateChatSession(bool showWindow)
{
	SEString data;

	// start chat session
	GCSESSION gcw = {0};
	gcw.cbSize = sizeof(gcw);
	gcw.iType = GCW_CHATROOM;
	gcw.pszModule = ppro->m_szModuleName;
	gcw.ptszName = this->name;
	gcw.ptszID = this->cid;
	gcw.dwItemData = (DWORD)this;
	::CallServiceSync(MS_GC_NEWSESSION, 0, (LPARAM)&gcw);

	// load chat roles
	GCDEST gcd = { ppro->m_szModuleName, this->cid, GC_EVENT_ADDGROUP };
	GCEVENT gce = { sizeof(gce), &gcd };
	for (int i = 1; i < SIZEOF(ChatRoom::Roles) - 2; i++) {
		gce.ptszStatus = ::TranslateTS(ChatRoom::Roles[i]);
		::CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
	}

	// init [and show window]
	gcd.iType = GC_EVENT_CONTROL;
	gce.ptszStatus = NULL;
	::CallServiceSync(MS_GC_EVENT, showWindow ? SESSION_INITDONE : WINDOW_HIDDEN, (LPARAM)&gce);
	::CallServiceSync(MS_GC_EVENT, SESSION_ONLINE, (LPARAM)&gce);

	this->ppro->debugLogW(L"Created new chat session %s", this->cid);
}

void ChatRoom::SetTopic(const wchar_t *topic)
{
	if (this->conversation)
		this->conversation->SetTopic((char *)ptrA(::mir_utf8encodeW(topic)));
}

wchar_t *ChatRoom::GetUri()
{
	SEString data;
	this->conversation->GetJoinBlob(data);
	ptrW blob( ::mir_utf8decodeW(data));
				
	wchar_t *uri = (wchar_t *)::mir_alloc(sizeof(wchar_t) * MAX_PATH);
	::mir_sntprintf(uri, SIZEOF(uri), L"skype:?chat&blob=%s", blob);

	return uri;
}

void ChatRoom::ShowWindow()
{
	// show window
	GCDEST gcd = { this->ppro->m_szModuleName, this->cid, GC_EVENT_CONTROL };
	GCEVENT gce = { sizeof(gce), &gcd };
	::CallServiceSync(MS_GC_EVENT, WINDOW_VISIBLE, (LPARAM)&gce);
}

void ChatRoom::Invite(const StringList &contacts)
{
	SEStringList needToAdd;
	for (size_t i = 0; i < contacts.size(); i++)
	{
		if ( !contacts.contains(contacts[i]))
			needToAdd.append((char *)_T2A(contacts[i]));
	}
	this->conversation->AddConsumers(needToAdd);
}

void ChatRoom::Create(const ChatRoomParam *param, CSkypeProto *ppro)
{
	SEString data;
	ChatRoom *room = NULL;

	ConversationRef conversation;
	if (ppro->CreateConference(conversation))
	{
		conversation->SetOption(Conversation::P_OPT_JOINING_ENABLED, param->enableJoining);
		conversation->SetOption(Conversation::P_OPT_ENTRY_LEVEL_RANK, (Participant::RANK)param->joinRank);
		conversation->SetOption(Conversation::P_OPT_DISCLOSE_HISTORY, true);
		if (::wcslen(param->topic) > 0)
			conversation->SetTopic((char *)ptrA(::mir_utf8encodeW(param->topic)));
		if (::wcslen(param->guidline) > 0)
			conversation->SetGuidelines((char *)ptrA(::mir_utf8encodeW(param->guidline)));
		if (param->passwordProtection && ::wcslen(param->password) > 0)
		{
			conversation->SetPassword(
				(char *)ptrA(::mir_utf8encodeW(param->password)),
				(char *)ptrA(::mir_utf8encodeW(param->hint)));
		}
		
		SEStringList consumers;
		for (size_t i = 0; i < param->invitedContacts.size(); i++)
		{
			data = ::mir_utf8encodeW(param->invitedContacts[i]);
			consumers.append(data);
		}
		conversation->AddConsumers(consumers);
	}
}

void ChatRoom::Start(const ConversationRef &conversation, bool showWindow)
{
	SEString data;

	this->CreateChatSession(showWindow);

	this->conversation = conversation;
	this->conversation.fetch();
	this->conversation->SetChatRoom(this);

	GC_INFO gci = { 0 };
	gci.Flags = BYID | HCONTACT;
	gci.pszModule = ppro->m_szModuleName;
	gci.pszID = this->cid;

	if ( !::CallServiceSync(MS_GC_GETINFO, 0, (LPARAM)&gci))
	{
		this->hContact = gci.hContact;
		ptrW cid( ::db_get_wsa(gci.hContact, ppro->m_szModuleName, SKYPE_SETTINGS_SID));
		if (cid == NULL)
		{
			this->conversation->GetPropIdentity(data);
			cid = ::mir_utf8decodeW(data);
			::db_set_ws(gci.hContact, ppro->m_szModuleName, SKYPE_SETTINGS_SID, cid);
		}
	}

	ParticipantRefs participants;
	this->conversation->GetParticipants(participants, Conversation::CONSUMERS_AND_APPLICANTS);
	for (uint i = 0; i < participants.size(); i++)
	{
		auto participant = participants[i];

		participant->GetPropIdentity(data);
		ptrW sid( ::mir_utf8decodeW(data));

		ChatMember member(sid);
		member.SetRank(participant->GetUintProp(Participant::P_RANK));
				
		Contact::Ref contact;
		this->ppro->GetContact(data, contact);

		Contact::AVAILABILITY status;
		contact->GetPropAvailability(status);
		member.SetStatus(CSkypeProto::SkypeToMirandaStatus(status));

		contact->GetPropFullname(data);
		if (data.length() != 0)
		{
			ptrW nick( ::mir_utf8decodeW(data));
			member.SetNick(nick);
		}

		member.SetPaticipant(participant);
		/*member.participant.fetch();
		member.participant->SetOnChangedCallback(&ChatRoom::OnParticipantChanged, this);*/

		this->AddMember(member, NULL, NULL);
	}
}

void ChatRoom::Join(const wchar_t *joinBlob, CSkypeProto *ppro)
{
	SEString data;
	ConversationRef conversation;
	if (ppro->GetConversationByBlob(data, conversation))
	{
		conversation->GetPropIdentity(data);
		ptrW cid(::mir_utf8decodeW(data));

		conversation->GetPropDisplayname(data);
		ptrW name(::mir_utf8decodeW(data));

		CSkypeProto::ReplaceSpecialChars(cid);
		ChatRoom *room = new ChatRoom(cid, name, ppro);
		ppro->chatRooms.insert(room);
		room->Start(conversation, true);
	}
}

void ChatRoom::SendMessage(const wchar_t *text)
{
	this->ppro->debugLogW(L"Sending chat message %s", this->cid);

	CMessage::Ref message;
		if (this->conversation->PostText((char *)ptrA(::mir_utf8encodeW(text)), message))
			this->ppro->debugLogW(L"Chat message sent %s", this->cid);
}

void ChatRoom::LeaveChat()
{
	this->ppro->debugLogW(L"Leaving chat session %s", this->cid);

	if (this->conversation->RetireFrom())
		this->ppro->debugLogW(L"Retired from conversation %s", this->cid);

	GCDEST gcd = { ppro->m_szModuleName, this->cid, GC_EVENT_CONTROL };
	GCEVENT gce = { sizeof(gce), &gcd };
	::CallServiceSync(MS_GC_EVENT, SESSION_OFFLINE, (LPARAM)&gce);
	::CallServiceSync(MS_GC_EVENT, SESSION_TERMINATE, (LPARAM)&gce);
}

void ChatRoom::LeaveChatAndDelete()
{
	this->ppro->debugLogW(L"Leaving chat session %s", this->cid);

	if (this->conversation->RetireFrom())
		this->ppro->debugLogW(L"Retired from conversation %s", this->cid);

	if (this->conversation->Delete())
		this->ppro->debugLogW(L"Delete conversation %s", this->cid);

	GCDEST gcd = { ppro->m_szModuleName, this->cid, GC_EVENT_CONTROL };
	GCEVENT gce = { sizeof(gce), &gcd };
	::CallServiceSync(MS_GC_EVENT, SESSION_OFFLINE, (LPARAM)&gce);
	::CallServiceSync(MS_GC_EVENT, SESSION_TERMINATE, (LPARAM)&gce);
}

void ChatRoom::SendEvent(const ChatMember &item, int eventType, DWORD timestamp, DWORD flags, DWORD itemData, const wchar_t *status, const wchar_t *message)
{
	bool isMe = this->IsMe(item);

	GCDEST gcd = { ppro->m_szModuleName, this->cid, eventType };
	GCEVENT gce = { sizeof(gce), &gcd };
	gce.dwFlags = flags;
	gce.ptszUID = item.GetSid();
	gce.ptszNick = !isMe ? item.GetNick() : ::TranslateT("me");
	gce.bIsMe = isMe;
	gce.dwItemData = itemData;
	gce.ptszStatus = status;
	gce.ptszText = message;
	gce.time = timestamp;

	::CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
}

void ChatRoom::SendEvent(const wchar_t *sid, int eventType, DWORD timestamp, DWORD flags, DWORD itemData, const wchar_t *status, const wchar_t *message)
{
	if (this->IsMe(sid))
		this->SendEvent(*this->me, eventType, timestamp, flags, itemData, status, message);
	else if (this->IsSys(sid))
		this->SendEvent(*this->sys, eventType, timestamp, flags, itemData, status, message);
	else
	{
		ChatMember search(sid);
		ChatMember *member = this->members.find(&search);
		if (member != NULL)
			this->SendEvent(*member, eventType, timestamp, flags, itemData, status, message);
	}
}

bool ChatRoom::IsMe(const wchar_t *sid) const
{
	return ::lstrcmpi(this->ppro->login, sid) == 0;
}

bool ChatRoom::IsMe(const ChatMember &item) const
{
	return ::lstrcmpi(this->ppro->login, item.GetSid()) == 0;
}

bool ChatRoom::IsSys(const wchar_t *sid) const
{
	return ::lstrcmpi(L"sys", sid) == 0;
}

bool ChatRoom::IsSys(const ChatMember &item) const
{
	return ::lstrcmpi(L"sys", item.GetSid()) == 0;
}

ChatMember *ChatRoom::FindChatMember(const wchar_t *sid)
{
	if ( !IsMe(sid))
	{
		ChatMember search(sid);
		return this->members.find(&search);
	}
	else
		return this->me;
}

void ChatRoom::AddMember(const ChatMember &item, const ChatMember &author, DWORD timestamp)
{
	if ( !this->IsMe(item))
	{
		ChatMember *member = this->FindChatMember(item.GetSid());
		if (member != NULL)
		{
			this->UpdateMember(item, timestamp);
		}
		else
		{
			ChatMember *newMember = new ChatMember(item);
			newMember->participant.fetch();
			newMember->participant->SetChatRoom(this);
			//newMember->participant->SetOnChangedCallback(&ChatRoom::OnParticipantChanged, this);
			this->members.insert(newMember);

			if (newMember->GetRank() == CParticipant::APPLICANT)
				this->SendEvent(
					*this->sys, 
					GC_EVENT_INFORMATION, 
					time(NULL),
					GCEF_ADDTOLOG, 
					0, 
					NULL, 
					::TranslateT("waits to join"));
			else
				this->SendEvent(item, GC_EVENT_JOIN, timestamp, GCEF_ADDTOLOG, 0, ::TranslateW(ChatRoom::Roles[item.GetRank()]));

			int status = item.GetStatus();
			if (status == ID_STATUS_AWAY || status == ID_STATUS_DND)
				this->SendEvent(item, GC_EVENT_SETSTATUSEX, timestamp, 0, GC_SSE_ONLYLISTED | GC_SSE_ONLINE, (const wchar_t*)0, item.GetSid());
			else if (status == ID_STATUS_OFFLINE)
				this->SendEvent(item, GC_EVENT_SETSTATUSEX, timestamp, 0, GC_SSE_ONLYLISTED | GC_SSE_OFFLINE, (const wchar_t*)0, item.GetSid());
			else
				this->SendEvent(item, GC_EVENT_SETSTATUSEX, timestamp, 0, GC_SSE_ONLYLISTED, (const wchar_t*)0, item.GetSid());

			this->SendEvent(item, GC_EVENT_SETCONTACTSTATUS, timestamp, 0, status);
		}
	}
	else
	{
		if (!this->me->participant)
		{
			this->me->participant = item.participant;
			this->me->participant.fetch();
			this->me->participant->SetChatRoom(this);
			//this->me->participant->SetOnChangedCallback(&ChatRoom::OnParticipantChanged, this);
		}
		if (this->me->GetRank() != item.GetRank())
		{
			this->SendEvent(*this->me, GC_EVENT_REMOVESTATUS, timestamp, 0, 0, ::TranslateW(ChatRoom::Roles[this->me->GetRank()]));
			this->SendEvent(*this->me, GC_EVENT_ADDSTATUS, timestamp, !this->me->GetRank() ? 0 : GCEF_ADDTOLOG, 0, ::TranslateW(ChatRoom::Roles[item.GetRank()]), author == NULL ? this->sys->GetNick() : author.GetNick());
			this->me->SetRank(item.GetRank());
		}
	}
}

void ChatRoom::UpdateMemberNick(ChatMember *member, const wchar_t *nick, DWORD timestamp)
{
	if (::lstrcmp(member->GetNick(), nick) != 0)
	{
		this->SendEvent(*member, GC_EVENT_NICK, timestamp, GCEF_ADDTOLOG, 0, nick);
		member->SetNick(nick);
	}
}

void ChatRoom::UpdateMemberRole(ChatMember *member, int role, const ChatMember &author, DWORD timestamp)
{
	if (member->GetRank() != role)
	{
		this->SendEvent(*member, GC_EVENT_REMOVESTATUS, timestamp, 0, 0, ::TranslateW(ChatRoom::Roles[member->GetRank()]));
		this->SendEvent(*member, GC_EVENT_ADDSTATUS, timestamp, GCEF_ADDTOLOG, 0, ::TranslateW(ChatRoom::Roles[role]), author == NULL ? this->sys->GetNick() : author.GetNick());
		member->SetRank(role);
	}
}

void ChatRoom::UpdateMemberStatus(ChatMember *member, int status, DWORD timestamp)
{
	if (member->GetStatus() != status)
	{
		if (status == ID_STATUS_AWAY || status == ID_STATUS_DND)
			this->SendEvent(*member, GC_EVENT_SETSTATUSEX, timestamp, 0, GC_SSE_ONLYLISTED | GC_SSE_ONLINE, (const wchar_t*)0, member->GetSid());
		else if (status == ID_STATUS_OFFLINE)
			this->SendEvent(*member, GC_EVENT_SETSTATUSEX, timestamp, 0, GC_SSE_ONLYLISTED | GC_SSE_OFFLINE, (const wchar_t*)0, member->GetSid());
		else
			this->SendEvent(*member, GC_EVENT_SETSTATUSEX, timestamp, 0, GC_SSE_ONLYLISTED, (const wchar_t*)0, member->GetSid());

		this->SendEvent(*member, GC_EVENT_SETCONTACTSTATUS, timestamp, 0, status);
		member->SetStatus(status);
	}
}

void ChatRoom::UpdateMember(const wchar_t *sid, const wchar_t *nick, int role, int status, DWORD timestamp)
{
	ChatMember search(sid);
	ChatMember *member = this->members.find(&search);
	if (member != NULL)
	{
		this->UpdateMemberNick(member, nick, timestamp);
		this->UpdateMemberRole(member, role, NULL, timestamp);
		this->UpdateMemberStatus(member, status, timestamp);
	}
}

void ChatRoom::UpdateMember(const ChatMember &item, DWORD timestamp)
{
	ChatMember *member = this->FindChatMember(item.GetSid());
	if (member != NULL)
	{
		ptrW nick(item.GetNick());
		if (::lstrcmp(member->GetNick(), nick) != 0)
		{
			this->SendEvent(*member, GC_EVENT_NICK, timestamp, GCEF_ADDTOLOG, 0, nick);
			member->SetNick(nick);
		}
		if (member->GetRank() != item.GetRank())
		{
			this->SendEvent(*member, GC_EVENT_REMOVESTATUS, timestamp, 0, 0, ::TranslateW(ChatRoom::Roles[member->GetRank()]));
			this->SendEvent(*member, GC_EVENT_ADDSTATUS, timestamp, GCEF_ADDTOLOG, 0, ::TranslateW(ChatRoom::Roles[item.GetRank()]));
			member->SetRank(item.GetRank());
		}
		if (member->GetStatus() != item.GetStatus())
		{
			this->SendEvent(*member, GC_EVENT_SETCONTACTSTATUS, timestamp, 0, item.GetStatus());
			member->SetStatus(item.GetStatus());
		}
	}
}

void ChatRoom::AddApplicant(const ChatMember *member)
{
	SEStringList consumers;
	consumers.append((char *)ptrA(::mir_utf8encodeW(member->GetSid())));
	this->conversation->AddConsumers(consumers);
}

void ChatRoom::KickMember(const ChatMember &item, const ChatMember *author, DWORD timestamp)
{
	if ( !this->IsMe(item))
	{
		ChatMember *member = this->FindChatMember(item.GetSid());
		if (member != NULL)
		{
			this->SendEvent(*member, GC_EVENT_KICK, timestamp, GCEF_ADDTOLOG, 0, author->GetNick());
			this->members.remove(member);
			delete member;
		}
	}
	else
	{
		this->SendEvent(*this->me, GC_EVENT_KICK, timestamp, GCEF_ADDTOLOG, 0, author->GetNick());
		this->me->SetRank(/*RETIRED= */7);
	}
}

void ChatRoom::KickMember(const wchar_t *sid, const wchar_t *author, DWORD timestamp)
{
	ChatMember member(sid);
	this->KickMember(member, this->FindChatMember(author), timestamp);
}

void ChatRoom::RemoveMember(const ChatMember &item, DWORD timestamp)
{
	if ( !this->IsMe(item))
	{
		ChatMember *member = this->FindChatMember(item.GetSid());
		if (member != NULL)
		{
			this->SendEvent(*member, GC_EVENT_PART, timestamp);
			this->members.remove(member);
			delete member;
		}
	}
	else
		this->LeaveChat();
}

void ChatRoom::RemoveMember(const wchar_t *sid, DWORD timestamp)
{
	ChatMember member(sid);
	this->RemoveMember(member, timestamp);
}

void ChatRoom::OnEvent(const ConversationRef &conversation, const MessageRef &message)
{
	if ( !this->conversation)
		this->conversation = conversation;

	if ( this->conversation != conversation)
		return;

	uint messageType;
	messageType = message->GetUintProp(Message::P_TYPE);

	switch (messageType)
	{
	case CMessage::POSTED_EMOTE:
	case CMessage::POSTED_TEXT:
		{
			SEString data;

			message->GetPropAuthor(data);
			ptrW sid( ::mir_utf8decodeW(data));

			message->GetPropBodyXml(data);
			ptrW text( ::mir_utf8decodeW( ptrA(CSkypeProto::RemoveHtml(data))));

			uint timestamp;
			message->GetPropTimestamp(timestamp);
			
			this->SendEvent(
				sid, 
				messageType == CMessage::POSTED_TEXT ? GC_EVENT_MESSAGE : GC_EVENT_ACTION,
				timestamp,
				GCEF_ADDTOLOG,
				0,
				NULL,
				text);
		}
		break;

	case Message::ADDED_CONSUMERS:
	case Message::ADDED_APPLICANTS:
		{
			SEString data;

			Message::CONSUMPTION_STATUS status;
			message->GetPropConsumptionStatus(status);
			if (status != Message::CONSUMED)
			{
				uint timestamp;
				message->GetPropTimestamp(timestamp);

				message->GetPropAuthor(data);
				ChatMember *author = this->FindChatMember((wchar_t *)ptrW(::mir_utf8decodeW(data)));

				ParticipantRefs participants;
				conversation->GetParticipants(participants);
				for (size_t i = 0; i < participants.size(); i++)
				{					
					participants[i]->GetPropIdentity(data);
					ptrW sid(::mir_utf8decodeW(data));
					if (this->FindChatMember(sid) == NULL)
					{
						ChatMember member(sid);
						member.SetRank(participants[i]->GetUintProp(Participant::P_RANK));

						Contact::Ref contact;
						this->ppro->GetContact(data, contact);

						Contact::AVAILABILITY status;
						contact->GetPropAvailability(status);
						member.SetStatus(CSkypeProto::SkypeToMirandaStatus(status));

						contact->GetPropFullname(data);
						ptrW nick(::mir_utf8decodeW(data));
						if (data.length() != 0)
						{
							ptrW nick( ::mir_utf8decodeW(data));
							member.SetNick(nick);
						}
						
						member.participant = participants[i];
						/*member.participant.fetch();
						member.participant->SetOnChangedCallback(&ChatRoom::OnParticipantChanged, this);*/

						this->AddMember(member, *author, timestamp);
					}
				}

				// do not remove
				//message->GetPropIdentities(data);
				//char *identities = ::mir_strdup(data);
				//if (identities)
				//{
				//	char *identity = ::strtok(identities, " ");
				//	if (identity != NULL)
				//	{
				//		do
				//		{
				//			Contact::Ref contact;
				//			this->ppro->GetContact(identity, contact);

				//			contact->GetIdentity(data);
				//			ptrW sid = ::mir_utf8decodeW(data);

				//			ChatMember *member = new ChatMember(sid);
				//			//todo: fix rank
				//			
				//			member->rank = 
				//				messageType == Message::ADDED_APPLICANTS ? 
				//				Participant::APPLICANT : 
				//				Participant::SPEAKER;
				//				//conversation->GetUintProp(Conversation::P_OPT_ENTRY_LEVEL_RANK);
				//				//participants[i]->GetUintProp(Participant::P_RANK);

				//			Contact::AVAILABILITY status;
				//			contact->GetPropAvailability(status);
				//			member->status = CSkypeProto::SkypeToMirandaStatus(status);

				//			contact->GetPropFullname(data);
				//			member->nick = ::mir_utf8decodeW(data);

				//			this->AddMember(member, timestamp);

				//			identity = ::strtok(NULL, " ");
				//		}
				//		while (identity != NULL);
				//	}
				//	::mir_free(identities);
				//}
			}
		}
		break;

	case Message::RETIRED_OTHERS:
		{
			SEString data;

			Message::CONSUMPTION_STATUS status;
			message->GetPropConsumptionStatus(status);
			if (status != Message::CONSUMED)
			{
				this->ppro->debugLogW(L"Retired other event for conversation %s", this->cid);

				uint timestamp;
				message->GetPropTimestamp(timestamp);

				message->GetPropAuthor(data);
				ptrW author( ::mir_utf8decodeW(data));

				message->GetPropIdentities(data);
				char *identities = ::mir_strdup(data);
				if (identities)
				{
					char *identity = ::strtok(identities, " ");
					if (identity != NULL)
					{
						do
						{
							ptrW sid(::mir_utf8decodeW(identity));
							this->KickMember(sid, author, timestamp);

							identity = ::strtok(NULL, " ");
						}
						while (identity != NULL);
					}
					::mir_free(identities);
				}
			}
		}
		break;

	case Message::RETIRED:
		{
			SEString data;

			Message::CONSUMPTION_STATUS status;
			message->GetPropConsumptionStatus(status);
			if (status != Message::CONSUMED)
			{
				this->ppro->debugLogW(L"Retired event for conversation %s", this->cid);

				uint timestamp;
				message->GetPropTimestamp(timestamp);

				message->GetPropAuthor(data);
				ptrW sid( ::mir_utf8decodeW(data));

				this->RemoveMember(sid, timestamp);
			}
		}
		break;

	case Message::SET_RANK:
		{
			SEString data;
		}
		break;
	//		message->GetPropBodyXml(data);
	//		ptrA text = ::mir_strdup(data);
	//		int i = 0;

	//		/*Message::CONSUMPTION_STATUS status;
	//		message->GetPropConsumptionStatus(status);
	//		if (status == Message::UNCONSUMED_NORMAL)*/
	//		{
	//			message->GetPropAuthor(data);	
	//			ptrW sid = ::mir_utf8decodeW(data);

	//			ChatMember search(sid);
	//			ChatMember *member = this->FindChatMember(sid);
	//			if (member != NULL)
	//			{
	//				uint timestamp;
	//				message->GetPropTimestamp(timestamp);

	//				message->GetPropBodyXml(data);	
	//				ptrW rank = ::mir_utf8decodeW(data);

	//				member->SetRank(0);
	//			}
	//		}
	//	}
	//	break;

	/*case CMessage::STARTED_LIVESESSION:
		{
			SEString data;

			Message::CONSUMPTION_STATUS status;
			message->GetPropConsumptionStatus(status);
			if (status != Message::UNCONSUMED_NORMAL)
				break;

			message->GetPropAuthor(data);	
			ptrW sid = ::mir_utf8decodeW(data);

			uint timestamp;
			message->GetPropTimestamp(timestamp);
			
			this->SendEvent(
				sid, 
				GC_EVENT_INFORMATION, 
				timestamp,
				GCEF_ADDTOLOG, 
				0, 
				NULL, 
				::TranslateT("Incoming group call received"));
		}
		break;*/

	case CMessage::ENDED_LIVESESSION:
		{
			SEString data;

			//Message::CONSUMPTION_STATUS status;
			//message->GetPropConsumptionStatus(status);
			//if (status != Message::UNCONSUMED_NORMAL)
			//	break;

			message->GetPropAuthor(data);	
			ptrW sid( ::mir_utf8decodeW(data));

			uint timestamp;
			message->GetPropTimestamp(timestamp);
			
			this->SendEvent(
				*this->sys, 
				GC_EVENT_INFORMATION, 
				timestamp,
				GCEF_ADDTOLOG, 
				0, 
				NULL, 
				::TranslateT("Incoming group call finished"));
		}
		break;
	}
}

void ChatRoom::OnChange(const ConversationRef &conversation, int prop)
{
	if ( !this->conversation)
		this->conversation = conversation;

	if ( this->conversation != conversation)
		return;

	switch (prop)
	{
	case Conversation::P_MY_STATUS:
		{
			Conversation::MY_STATUS status;
			conversation->GetPropMyStatus(status);
			if (status == Conversation::INVALID_ACCESS_TOKEN)
			{
				PasswordRequestBoxParam param(this->name, false);
				if (this->ppro->RequestPassword(param))
				{
					if ( !this->conversation->EnterPassword(param.password))
						this->SendEvent(
							*this->sys, 
							GC_EVENT_INFORMATION, 
							time(NULL),
							GCEF_ADDTOLOG, 
							0, 
							NULL, 
							::TranslateT("The password is incorrect"));
				}
			}
			else if (status == Conversation::APPLICATION_DENIED)
			{
				this->SendEvent(
					*this->sys, 
					GC_EVENT_INFORMATION, 
					time(NULL),
					GCEF_ADDTOLOG, 
					0, 
					NULL, 
					::TranslateT("Your application to join the conference was denied"));
			}
		}
		break;

	case Conversation::P_LOCAL_LIVESTATUS:
		{
			Conversation::LOCAL_LIVESTATUS liveStatus;
			conversation->GetPropLocalLivestatus(liveStatus);
			if (liveStatus == Conversation::RINGING_FOR_ME)
			{
				SEString data;

				/*Message::CONSUMPTION_STATUS status;
				message->GetPropConsumptionStatus(status);
				if (status != Message::UNCONSUMED_NORMAL)
					break;*/

				/*message->GetPropAuthor(data);	
				ptrW sid = ::mir_utf8decodeW(data);*/

				/*uint timestamp;
				message->GetPropTimestamp(timestamp);*/
			
				this->SendEvent(
					*this->sys, 
					GC_EVENT_INFORMATION, 
					time(NULL),
					GCEF_ADDTOLOG, 
					0, 
					NULL, 
					::TranslateT("Incoming group call started"));
				}
		}
		break;
	}
}

void ChatRoom::OnParticipantChanged(const ParticipantRef &participant, int prop)
{
	if (prop == Participant::P_RANK)
	{
		Participant::RANK rank;
		participant->GetPropRank(rank);
	
		SEString identity;
		participant->GetPropIdentity(identity);

		ptrW sid(::mir_utf8decodeW(identity));
		ChatMember *member = this->FindChatMember(sid);
		if (member != NULL)
			this->UpdateMemberRole(member, rank);
	}
}

///

void CSkypeProto::ChatValidateContact(HANDLE hItem, HWND hwndList, const StringList &contacts)
{
	if (this->IsProtoContact(hItem) && !this->isChatRoom(hItem))
	{
		ptrW sid( ::db_get_wsa(hItem, this->m_szModuleName, SKYPE_SETTINGS_SID));
		if (sid == NULL || contacts.contains(sid))
			::SendMessage(hwndList, CLM_DELETEITEM, (WPARAM)hItem, 0);
	}
	else
		::SendMessage(hwndList, CLM_DELETEITEM, (WPARAM)hItem, 0);
}

void CSkypeProto::ChatPrepare(HANDLE hItem, HWND hwndList, const StringList &contacts)
{
	if (hItem == NULL)
		hItem = (HANDLE)::SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_ROOT, 0);

	while (hItem) 
	{
		HANDLE hItemN = (HANDLE)::SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_NEXT, (LPARAM)hItem);

		if (IsHContactGroup(hItem))
		{
			HANDLE hItemT = (HANDLE)::SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_CHILD, (LPARAM)hItem);
			if (hItemT) this->ChatPrepare(hItemT, hwndList, contacts);
		}
		else if (IsHContactContact(hItem))
			this->ChatValidateContact(hItem, hwndList, contacts);

		hItem = hItemN;
	}
}

void CSkypeProto::GetInvitedContacts(HANDLE hItem, HWND hwndList, StringList &chatTargets)
{
	if (hItem == NULL)
		hItem = (HANDLE)::SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_ROOT, 0);

	while (hItem)
	{
		if (IsHContactGroup(hItem))
		{
			HANDLE hItemT = (HANDLE)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_CHILD, (LPARAM)hItem);
			if (hItemT)
				this->GetInvitedContacts(hItemT, hwndList, chatTargets);
		}
		else
		{
			int chk = SendMessage(hwndList, CLM_GETCHECKMARK, (WPARAM)hItem, 0);
			if (chk)
			{
				if (IsHContactInfo(hItem))
				{
					TCHAR buf[128] = _T("");
					SendMessage(hwndList, CLM_GETITEMTEXT, (WPARAM)hItem, (LPARAM)buf);

					if (buf[0]) chatTargets.insert(buf);
				}
				else
				{
					ptrW login( ::db_get_wsa(hItem, this->m_szModuleName, SKYPE_SETTINGS_SID));
					chatTargets.insert(login);
				}
			}
		}
		hItem = (HANDLE)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_NEXT, (LPARAM)hItem);
	}
}

INT_PTR CALLBACK CSkypeProto::ChatRoomProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	ChatRoomParam *param = (ChatRoomParam *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);

	switch (msg)
	{
	case WM_INITDIALOG:
		TranslateDialogDefault(hwndDlg);

		SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
		param = (ChatRoomParam *)lParam;
		{
			HWND hwndClist = GetDlgItem(hwndDlg, IDC_CCLIST);
			SetWindowLongPtr(hwndClist, GWL_STYLE, GetWindowLongPtr(hwndClist, GWL_STYLE) & ~CLS_HIDEOFFLINE);

			if ( !param->ppro->IsOnline())
			{
				::EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE);
				::EnableWindow(GetDlgItem(hwndDlg, IDC_ADDSCR), FALSE);
				::EnableWindow(GetDlgItem(hwndDlg, IDC_CCLIST), FALSE);
			}

			SendDlgItemMessage(hwndDlg, IDC_CHAT_JOINING, BM_SETCHECK, param->enableJoining, 0);
			for (int i = 1; i < SIZEOF(ChatRoom::Roles) - 4; i++)
			{
				int nItem = ::SendMessage(::GetDlgItem(hwndDlg, IDC_CHAT_ROLES), CB_ADDSTRING, 0, (LPARAM)::TranslateW(ChatRoom::Roles[i]));

				if (i == Participant::WRITER)
					::SendMessage(::GetDlgItem(hwndDlg, IDC_CHAT_ROLES), CB_SETCURSEL, nItem, 0);
			}

			SendDlgItemMessage(hwndDlg, IDC_CHAT_SECURED, BM_SETCHECK, param->passwordProtection, 0);
		}
		break;

	case WM_CLOSE:
		::EndDialog(hwndDlg, 0);
		break;

	case WM_NOTIFY:
		{
			NMCLISTCONTROL *nmc = (NMCLISTCONTROL *)lParam;
			if (nmc->hdr.idFrom == IDC_CCLIST)
			{
				switch (nmc->hdr.code)
				{
				case CLN_NEWCONTACT:
					if (param && (nmc->flags & (CLNF_ISGROUP | CLNF_ISINFO)) == 0)
						param->ppro->ChatValidateContact(nmc->hItem, nmc->hdr.hwndFrom, param->invitedContacts);
					break;

				case CLN_LISTREBUILT:
					if (param)
						param->ppro->ChatPrepare(NULL, nmc->hdr.hwndFrom, param->invitedContacts);
					break;
				}
			}
		}
		break;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDC_ADDSCR:
			if (param->ppro->IsOnline())
			{
				wchar_t sid[SKYPE_SID_LIMIT];
				::GetDlgItemText(hwndDlg, IDC_EDITSCR, sid, SIZEOF(sid));

				CLCINFOITEM cii = {0};
				cii.cbSize = sizeof(cii);
				cii.flags = CLCIIF_CHECKBOX | CLCIIF_BELOWCONTACTS;
				cii.pszText = ::wcslwr(sid);

				HANDLE hItem = (HANDLE)::SendDlgItemMessage(hwndDlg, IDC_CCLIST, CLM_ADDINFOITEM, 0, (LPARAM)&cii);
				::SendDlgItemMessage(hwndDlg, IDC_CCLIST, CLM_SETCHECKMARK, (LPARAM)hItem, 1);
			}
			break;

		case IDC_CHAT_SECURED:
			{
				BOOL enable = (BOOL)::IsDlgButtonChecked(hwndDlg, IDC_CHAT_SECURED);
				::EnableWindow(::GetDlgItem(hwndDlg, IDC_CHAT_PASSWORD), enable);
				::EnableWindow(::GetDlgItem(hwndDlg, IDC_CHAT_CONFIRMATION), enable);
				::EnableWindow(::GetDlgItem(hwndDlg, IDC_CHAT_HINT), enable);
			}
			break;

		case IDC_CHAT_PASSWORD:
		case IDC_CHAT_CONFIRMATION:
			{
				wchar_t pwd[32], cfn[32];
				GetDlgItemText(hwndDlg, IDC_CHAT_PASSWORD, pwd, SIZEOF(pwd));
				GetDlgItemText(hwndDlg, IDC_CHAT_CONFIRMATION, cfn, SIZEOF(cfn));

				BOOL secured = ::IsDlgButtonChecked(hwndDlg, IDC_CHAT_SECURED);
				::EnableWindow(::GetDlgItem(hwndDlg, IDOK), secured && ::wcscmp(pwd, cfn) == 0);
			}
			break;

		case IDOK:
			{
				HWND hwndList = ::GetDlgItem(hwndDlg, IDC_CCLIST);

				param->invitedContacts.clear();
				param->ppro->GetInvitedContacts(NULL, hwndList, param->invitedContacts);

				if ( !param->invitedContacts.empty())
				{
					SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
					::EndDialog(hwndDlg, IDOK);
				}
				else
					param->ppro->ShowNotification(::TranslateT("You did not select any contact"));

				GetDlgItemText(hwndDlg, IDC_CHAT_TOPIC, param->topic, SIZEOF(param->topic));
				GetDlgItemText(hwndDlg, IDC_CHAT_GUIDLINE, param->guidline, SIZEOF(param->guidline));

				param->enableJoining = ::IsDlgButtonChecked(hwndDlg, IDC_CHAT_JOINING) != 0;
				param->joinRank = ::SendMessage(::GetDlgItem(hwndDlg, IDC_CHAT_ROLES), CB_GETCURSEL, 0, 0) + 1;

				param->passwordProtection = ::IsDlgButtonChecked(hwndDlg, IDC_CHAT_SECURED) != 0;
				if (param->passwordProtection)
				{
					GetDlgItemText(hwndDlg, IDC_CHAT_PASSWORD, param->password, SIZEOF(param->password));
					GetDlgItemText(hwndDlg, IDC_CHAT_CONFIRMATION, param->confirmation, SIZEOF(param->confirmation));
					GetDlgItemText(hwndDlg, IDC_CHAT_HINT, param->hint, SIZEOF(param->hint));
				}
			}
			break;

		case IDCANCEL:
			::EndDialog(hwndDlg, IDCANCEL);
			break;
		}
		break;
	}
	return FALSE;
}

INT_PTR CSkypeProto::CreateChatRoomCommand(WPARAM, LPARAM)
{
	ChatRoomParam *param = new ChatRoomParam(NULL, NULL, this);

	if (::DialogBoxParam(g_hInstance, MAKEINTRESOURCE(IDD_CHATROOM_CREATE), NULL, CSkypeProto::ChatRoomProc, (LPARAM)param) == IDOK && param->invitedContacts.size() > 0)
		ChatRoom::Create(param, this);

	delete param;

	return 0;
}

void CSkypeProto::ChatRoomInvite(HANDLE hContact)
{
	ptrT chat_id(::db_get_tsa(hContact, this->m_szModuleName, "ChatRoomID"));

	GC_INFO gci = { 0 };
	gci.Flags = BYID | USERS | DATA;
	gci.pszModule = this->m_szModuleName;
	gci.pszID = chat_id;
	if ( !::CallService(MS_GC_GETINFO, 0, (LPARAM)&gci))
	{
		ChatRoom *room = (ChatRoom *)gci.dwItemData;
		if (room != NULL && gci.pszUsers != NULL)
		{
			StringList invitedContacts(_A2T(gci.pszUsers));
			ChatRoomParam *param = new ChatRoomParam(NULL, invitedContacts, this);

			if (::DialogBoxParam(g_hInstance, MAKEINTRESOURCE(IDD_CHATROOM_INVITE), NULL, CSkypeProto::ChatRoomProc, (LPARAM)param) == IDOK && param->invitedContacts.size() > 0)
				room->Invite(param->invitedContacts);

			delete param;
		}
	}	
}

void CSkypeProto::CloseAllChatSessions()
{
	GC_INFO gci = {0};
	gci.Flags = BYINDEX | ID | DATA;
	gci.pszModule = this->m_szModuleName;

	int count = ::CallServiceSync(MS_GC_GETSESSIONCOUNT, 0, (LPARAM)this->m_szModuleName);
	for (int i = 0; i < count ; i++)
	{
		gci.iItem = i;
		if ( !::CallServiceSync(MS_GC_GETINFO, 0, (LPARAM)&gci))
		{
			GCDEST gcd = { this->m_szModuleName, gci.pszID, GC_EVENT_CONTROL };
			GCEVENT gce = { sizeof(gce), &gcd };			
			::CallServiceSync(MS_GC_EVENT, SESSION_OFFLINE, (LPARAM)&gce);
			::CallServiceSync(MS_GC_EVENT, SESSION_TERMINATE, (LPARAM)&gce);
		}
	}
}

ChatRoom *CSkypeProto::FindChatRoom(const wchar_t *cid)
{
	GC_INFO gci = { 0 };
	gci.Flags = BYID | DATA;
	gci.pszModule = this->m_szModuleName;
	gci.pszID = (wchar_t*)cid;

	if ( !::CallServiceSync(MS_GC_GETINFO, 0, (LPARAM)&gci))
		return (ChatRoom *)gci.dwItemData;

	return NULL;
}

int __cdecl CSkypeProto::OnGCEventHook(WPARAM, LPARAM lParam)
{
	GCHOOK *gch = (GCHOOK *)lParam;
	if (!gch) return 1;

	if (::strcmp(gch->pDest->pszModule, this->m_szModuleName) != 0)
		return 0;

	ChatRoom *room = this->FindChatRoom(gch->pDest->ptszID);
	if (room == NULL)
		return 0;

	switch (gch->pDest->iType)
	{
	case GC_USER_MESSAGE:
		if (gch->ptszText && gch->ptszText[0])
			room->SendMessage(gch->ptszText);
		break;

	/*case GC_USER_CHANMGR:
		if (this->GetConversationByIdentity(::mir_utf8encodeW(cid), conversation, false))
		{
			StringList invitedContacts(this->GetChatUsers(cid));
			this->InviteConactsToChat(conversation, invitedContacts); 
		}
		break;*/

	case GC_USER_PRIVMESS:
			::CallService(MS_MSG_SENDMESSAGE, (WPARAM)this->GetContactBySid(gch->ptszUID), 0);
		break;

	case GC_USER_LOGMENU:
	case GC_USER_NICKLISTMENU:
		switch (gch->dwData)
		{
		case CHAT_LIST_MENU::ICM_ROLE_ADMIN:
		case CHAT_LIST_MENU::ICM_ROLE_SPEAKER:
		case CHAT_LIST_MENU::ICM_ROLE_WRITER:
		case CHAT_LIST_MENU::ICM_ROLE_SPECTATOR:
			{
				ChatMember *member = room->FindChatMember(gch->ptszUID);
				if (member != NULL)
				{
					Participant::RANK rank;
					switch (gch->dwData)
					{
						case CHAT_LIST_MENU::ICM_ROLE_ADMIN:
							rank = Participant::ADMIN;
							break;

						case CHAT_LIST_MENU::ICM_ROLE_SPEAKER:
							rank = Participant::SPEAKER;
							break;

						case CHAT_LIST_MENU::ICM_ROLE_WRITER:
							rank = Participant::WRITER;
							break;

						case CHAT_LIST_MENU::ICM_ROLE_SPECTATOR:
							rank = Participant::SPECTATOR;
							break;
					}
					if (member->participant && member->participant->SetRankTo(rank))
						room->UpdateMemberRole(member, rank, *room->me);
				}
			}
			break;

		case CHAT_LIST_MENU::ICM_ADD:
			{
				ChatMember *member = room->FindChatMember(gch->ptszUID);
				if (member != NULL)
					room->AddApplicant(member);
			}
			break;

		case CHAT_LIST_MENU::ICM_KICK:
			{
				ChatMember *member = room->FindChatMember(gch->ptszUID);
				if (member != NULL)
				{
					if (member->participant && member->participant->Retire())
						room->KickMember(gch->ptszUID, room->me->GetSid());
				}
			}
			break;

		case CHAT_LIST_MENU::ICM_BAN:
			{
				ChatMember *member = room->FindChatMember(gch->ptszUID);
				if (member != NULL && member->participant)
				{
					member->participant->SetRankTo(Participant::OUTLAW);
					if (member->participant->Retire())
						room->KickMember(gch->ptszUID, room->me->GetSid());
				}
			}
			break;

		case CHAT_LIST_MENU::ICM_CONF_INVITE:
			{
				GC_INFO gci = { 0 };
				gci.Flags = BYID | USERS;
				gci.pszModule = this->m_szModuleName;
				gci.pszID = gch->pDest->ptszID;
				if ( !::CallService(MS_GC_GETINFO, 0, (LPARAM)&gci) && gci.pszUsers != NULL)
				{
					StringList invitedContacts(_A2T(gci.pszUsers));
					ChatRoomParam *param = new ChatRoomParam(NULL, invitedContacts, this);

					if (::DialogBoxParam(g_hInstance, MAKEINTRESOURCE(IDD_CHATROOM_INVITE), NULL, CSkypeProto::ChatRoomProc, (LPARAM)param) == IDOK && param->invitedContacts.size() > 0)
						room->Invite(param->invitedContacts);

					delete param;
				}
			}
			break;

		case CHAT_LIST_MENU::ICM_AUTH_REQUEST:
			{
				CContact::Ref contact;
				SEString sid((char *)ptrA(::mir_utf8encodeW(gch->ptszUID)));
				if (this->GetContact(sid, contact))
				{
					this->AuthRequest(
						this->AddContact(contact),
						LPGENT("Hi! I\'d like to add you to my contact list"));
				}
			}
			break;

		case CHAT_LIST_MENU::ICM_DETAILS:
			::CallService(MS_USERINFO_SHOWDIALOG, (WPARAM)this->GetContactBySid(gch->ptszUID), 0);
			break;

		case CHAT_LIST_MENU::ICM_COPY_SID:
			{
				HANDLE hContact = this->GetContactBySid(gch->ptszUID);
				if (!hContact)
				{
					ptrW sid( ::db_get_wsa(hContact, this->m_szModuleName, SKYPE_SETTINGS_SID));
					if (sid != NULL)
						CSkypeProto::CopyToClipboard(sid);
				}
			}
			break;

		case CHAT_LIST_MENU::ICM_COPY_URI:
				CSkypeProto::CopyToClipboard(ptrW(room->GetUri()));
			break;
		}
		break;

	//case GC_USER_TYPNOTIFY:
		//break;
	}
	return 0;
}

int __cdecl CSkypeProto::OnGCMenuHook(WPARAM, LPARAM lParam)
{
	GCMENUITEMS *gcmi = (GCMENUITEMS*) lParam;

	if (::stricmp(gcmi->pszModule, this->m_szModuleName) != 0)
		return 0;

	ChatRoom *room = this->FindChatRoom(gcmi->pszID);
	if (room == NULL)
		return 0;

	ResetChatMenuItem();

	if (room->me->GetRank() > Participant::ADMIN || room->me->GetRank() == 0)
	{
		DisableChatMenuItem(ICM_ROLE);
		DisableChatMenuItem(ICM_ADD);
		DisableChatMenuItem(ICM_KICK);
		DisableChatMenuItem(ICM_BAN);
	}

	//todo: add other case
	if (room->me->GetRank() >= Participant::APPLICANT)
	{
		DisableChatMenuItem(ICM_CONF_INVITE);
	}	

	ChatMember *member = room->FindChatMember(gcmi->pszUID);
	if (member != NULL)
	{
		if (member->GetRank() == Participant::CREATOR)
		{
			DisableChatMenuItem(ICM_ROLE);
			DisableChatMenuItem(ICM_ADD);
			DisableChatMenuItem(ICM_KICK);
			DisableChatMenuItem(ICM_BAN);
		}
		
		if (member->GetRank() <= Participant::SPECTATOR)
		{
			CHAT_LIST_MENU type = (CHAT_LIST_MENU)(ICM_ROLE + member->GetRank() - 1);
			CheckChatMenuItem(type);
			DisableChatMenuItem(type);

			DisableChatMenuItem(ICM_ADD);
		}
		
		if (member->GetRank() > Participant::SPECTATOR)
			DisableChatMenuItem(ICM_ROLE);

		HANDLE hContact = this->GetContactBySid(gcmi->pszUID);
		if (hContact == NULL)
			DisableChatMenuItem(ICM_DETAILS);
		else if(this->getByte(hContact, "Auth", 0) == 0)
			DisableChatMenuItem(ICM_AUTH_REQUEST);
	}
	else
	{
		DisableChatMenuItem(ICM_DETAILS);
		DisableChatMenuItem(ICM_AUTH_REQUEST);
		DisableChatMenuItem(ICM_ROLE);
		DisableChatMenuItem(ICM_ADD);
		DisableChatMenuItem(ICM_KICK);
		DisableChatMenuItem(ICM_BAN);
		DisableChatMenuItem(ICM_COPY_SID);
	}

	gcmi->nItems = SIZEOF(crListItems);
	gcmi->Item = crListItems;

	return 0;
}

void CSkypeProto::UpdateChatUserStatus(const ContactRef &contact)
{
	CContact::AVAILABILITY availability;
	contact->GetPropAvailability(availability);

	SEString identity;
	contact->GetIdentity(identity);
	ptrW sid(::mir_utf8decodeW(identity));

	GC_INFO gci = { 0 };
	gci.Flags = BYINDEX | DATA;
	gci.pszModule = this->m_szModuleName;

	int count = ::CallServiceSync(MS_GC_GETSESSIONCOUNT, 0, (LPARAM)this->m_szModuleName);
	for (int i = 0; i < count ; i++)
	{
		gci.iItem = i;
		::CallServiceSync(MS_GC_GETINFO, 0, (LPARAM)&gci);

		ChatRoom *room = (ChatRoom *)gci.dwItemData;
		if (room != NULL)
		{
			ChatMember *member = room->FindChatMember(sid);
			if (member != NULL)
				room->UpdateMemberStatus(member, CSkypeProto::SkypeToMirandaStatus(availability));
		}
	}
}

void CSkypeProto::UpdateChatUserNick(const ContactRef &contact)
{
	SEString data;

	contact->GetIdentity(data);
	ptrW sid(::mir_utf8decodeW(data));

	ptrW nick(::mir_utf8decodeW(((CContact::Ref)contact)->GetNick()));

	GC_INFO gci = { 0 };
	gci.Flags = BYINDEX | DATA;
	gci.pszModule = this->m_szModuleName;

	int count = ::CallServiceSync(MS_GC_GETSESSIONCOUNT, 0, (LPARAM)this->m_szModuleName);
	for (int i = 0; i < count ; i++)
	{
		gci.iItem = i;
		::CallServiceSync(MS_GC_GETINFO, 0, (LPARAM)&gci);

		ChatRoom *room = (ChatRoom *)gci.dwItemData;
		if (room != NULL)
		{
			ChatMember *member = room->FindChatMember(sid);
			if (member != NULL)
				room->UpdateMemberNick(member, nick);
		}
	}
}

INT_PTR __cdecl CSkypeProto::OnJoinChat(WPARAM wParam, LPARAM)
{
	HANDLE hContact = (HANDLE)wParam;
	if (hContact)
	{
		ptrW cid(::db_get_wsa(hContact, this->m_szModuleName, "ChatRoomID"));

		ChatRoom *room = this->FindChatRoom(cid);
		if ( !room)
		{
			ConversationRef conversation;
			if (this->GetConversationByIdentity(
				(char *)_T2A(::db_get_wsa(hContact, this->m_szModuleName, SKYPE_SETTINGS_SID)), 
				conversation))
			{
				SEString data;
				conversation->GetJoinBlob(data);
				ptrW joinBlob(::mir_utf8decodeW(data));
				ChatRoom::Join(joinBlob, this);
			}
		}
		else
			room->ShowWindow();
	}
	
	return 0;
}

INT_PTR __cdecl CSkypeProto::OnLeaveChat(WPARAM wParam, LPARAM)
{
	HANDLE hContact = (HANDLE)wParam;
	if (hContact)
	{
		ptrW cid(::db_get_wsa(hContact, this->m_szModuleName, "ChatRoomID"));
		
		ChatRoom *room = this->FindChatRoom(cid);
		if (room != NULL)
			room->LeaveChat();
	}

	return 0;
}

///

void __cdecl CSkypeProto::LoadChatList(void*)
{
	this->debugLogW(L"Updating group chats list");
	CConversation::Refs conversations;
	this->GetConversationList(conversations);

	SEString data;
	for (uint i = 0; i < conversations.size(); i++)
	{
		auto conversation = conversations[i];

		uint convoType = conversation->GetUintProp(Conversation::P_TYPE);
		if (convoType == CConversation::CONFERENCE)
		{
			CConversation::MY_STATUS status;
			conversation->GetPropMyStatus(status);
			if (status == Conversation::APPLICANT || status == Conversation::CONSUMER )
			{
				conversation->GetPropIdentity(data);
				ptrW cid( ::mir_utf8decodeW(data));
				CSkypeProto::ReplaceSpecialChars(cid);

				conversation->GetPropDisplayname(data);
				ptrW name( ::mir_utf8decodeW(data));

				ChatRoom *room = new ChatRoom(cid, name, this);
				chatRooms.insert(room);
				room->Start(conversation);
			}
		}
	}
}

///

void CSkypeProto::OnChatEvent(const ConversationRef &conversation, const MessageRef &message)
{
	uint messageType;
	messageType = message->GetUintProp(Message::P_TYPE);

	SEString data;
	conversation->GetPropIdentity(data);
	ptrW cid( ::mir_utf8decodeW(data));
	CSkypeProto::ReplaceSpecialChars(cid);

	ChatRoom *room = this->FindChatRoom(cid);
	if (room != NULL)
	{
		room->OnEvent(conversation, message);
	}
	else
	{
		Conversation::MY_STATUS status;
		conversation->GetPropMyStatus(status);
		if (status != Conversation::RETIRED_FORCEFULLY || status != Conversation::RETIRED_FORCEFULLY)
		{
			SEString data;

			conversation->GetPropDisplayname(data);
			ptrW name( ::mir_utf8decodeW(data));

			ChatRoom *room = new ChatRoom(cid, name, this);
			chatRooms.insert(room);
			room->Start(conversation, true);
		}
	}
}

void CSkypeProto::OnConversationListChange(
		const ConversationRef& conversation,
		const Conversation::LIST_TYPE& type,
		const bool& added)
{
	uint convoType = conversation->GetUintProp(Conversation::P_TYPE);
	if (convoType == Conversation::CONFERENCE && type == Conversation::INBOX_CONVERSATIONS  && added)
	{
		SEString data;

		conversation->GetPropIdentity(data);
		ptrW cid( ::mir_utf8decodeW(data));
		CSkypeProto::ReplaceSpecialChars(cid);

		if ( !this->FindChatRoom(cid))
		{
			conversation->GetPropDisplayname(data);
			ptrW name( ::mir_utf8decodeW(data));

			ChatRoom *room = new ChatRoom(cid, name, this);
			chatRooms.insert(room);
			room->Start(conversation, true);
		}
	}
}

void CSkypeProto::ChatRoomParseUriComands(const wchar_t *commands)
{
}

static void appendString(bool bIsTipper, const TCHAR *tszTitle, const TCHAR *tszValue, TCHAR* buf, size_t bufSize)
{
	if (*buf) {
		const TCHAR *szSeparator = bIsTipper ? _T("\n") : _T("\r\n");
 		_tcsncat(buf, szSeparator, bufSize);
	}

	size_t len = _tcslen(buf);
	buf += len;
	bufSize -= len;

	if (bIsTipper)
		mir_sntprintf(buf, bufSize, _T("%s%s%s%s"), _T("<b>"), TranslateTS(tszTitle), _T("</b>\t"), tszValue);
	else {
		TCHAR* p = TranslateTS(tszTitle);
		mir_sntprintf(buf, bufSize, _T("%s%s\t%s"), p, _tcslen(p)<=7 ? _T("\t") : _T(""), tszValue);
	}
}

INT_PTR __cdecl CSkypeProto::SkypeGCGetToolTipText(WPARAM wParam, LPARAM lParam)
{
	if ( !wParam || !lParam)
		return 0; //room global tooltip not supported yet

	ChatRoom *room = this->FindChatRoom((TCHAR *)wParam);
	if (room == NULL)
		return 0;  //no room found

	ChatMember *member = room->FindChatMember((TCHAR *)lParam);
	if (member == NULL)
		return 0;  //no contact found

	// ok process info output will be:
	// Skype name:	sid
	// Nick:		Nickname
	// Status:		StatusText
	// Role:		Moderator

	TCHAR outBuf[2048];
	outBuf[0]=_T('\0');

	bool bIsTipper = db_get_b(NULL, "Tab_SRMsg", "adv_TipperTooltip", 0) && ServiceExists("mToolTip/HideTip");

	//sid
	appendString(bIsTipper, _T("Skype name:"), member->GetSid(), outBuf, SIZEOF(outBuf));
	//nick
	appendString(bIsTipper, _T("Nick:"), member->GetNick(), outBuf, SIZEOF(outBuf));
	//status
	appendString(bIsTipper, _T("Status:"), (TCHAR *)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION,(WPARAM)member->GetStatus(),GSMDF_TCHAR), outBuf, SIZEOF(outBuf));
	//role
	appendString(bIsTipper, _T("Role:"), ::TranslateW(ChatRoom::Roles[member->GetRank()]), outBuf, SIZEOF(outBuf));

	return (INT_PTR)(outBuf[0] == 0 ? NULL : mir_tstrdup(outBuf));
}