/*
Copyright (C) 2012-24 Miranda NG team (https://miranda-ng.org)
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 version 2
of the License.
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, see .
*/
#include "stdafx.h"
static const wchar_t* getRoleById(uint32_t ID)
{
	switch (ID) {
	case TD::chatMemberStatusCreator::ID:
		return TranslateT("Creator");
	case TD::chatMemberStatusAdministrator::ID:
		return TranslateT("Admin");
	case TD::chatMemberStatusMember::ID:
	default:
		return TranslateT("Participant");
	}
}
void CTelegramProto::InitGroupChat(TG_USER *pUser, const wchar_t *pwszTitle)
{
	if (pUser->m_si)
		return;
		
	wchar_t wszId[100];
	_i64tow(pUser->id, wszId, 10);
	SESSION_INFO *si;
	if (pwszTitle == nullptr)
		pwszTitle = pUser->wszNick;
	if (pUser->bLoadMembers) {
		pUser->m_si = si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszId, pwszTitle, pUser);
		if (!si->pStatuses) {
			Chat_AddGroup(si, TranslateT("Creator"));
			Chat_AddGroup(si, TranslateT("Admin"));
			Chat_AddGroup(si, TranslateT("Participant"));
			// push async query to fetch users
			if (m_arBasicGroups.find((TG_BASIC_GROUP*)&pUser->id))
				SendQuery(new TD::getBasicGroupFullInfo(pUser->id), &CTelegramProto::StartGroupChat, pUser);
			else
				SendQuery(new TD::getSupergroupMembers(pUser->id, 0, 0, 100), &CTelegramProto::StartGroupChat, pUser);
		}
		else GcRun(pUser);
	}
	else {
		pUser->m_si = si = Chat_NewSession(GCW_CHANNEL, m_szModuleName, wszId, pwszTitle, pUser);
		if (!si->pStatuses) {
			Chat_AddGroup(si, TranslateT("SuperAdmin"));
			Chat_AddGroup(si, TranslateT("Visitor"));
			ptrW wszMyId(getWStringA(DBKEY_ID)), wszMyNick(Contact::GetInfo(CNF_DISPLAY, 0, m_szModuleName));
			GCEVENT gce = { si, GC_EVENT_JOIN };
			gce.pszUID.w = wszMyId;
			gce.pszNick.w = wszMyNick;
			gce.bIsMe = true;
			gce.pszStatus.w = TranslateT("Visitor");
			Chat_Event(&gce);
			gce.bIsMe = false;
			gce.pszUID.w = wszId;
			gce.pszNick.w = pwszTitle;
			gce.pszStatus.w = TranslateT("SuperAdmin");
			Chat_Event(&gce);
		}
		GcRun(pUser);
	}
}
void CTelegramProto::GcRun(TG_USER *pChat)
{
	pChat->bStartChat = false;
	Chat_Control(pChat->m_si, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE);
	Chat_Control(pChat->m_si, SESSION_ONLINE);
}
void CTelegramProto::StartGroupChat(td::ClientManager::Response &response, void *pUserData)
{
	if (!response.object)
		return;
	auto *pChat = (TG_USER *)pUserData;
	switch (response.object->get_id()) {
	case TD::basicGroupFullInfo::ID:
		GcAddMembers(pChat, ((TD::basicGroupFullInfo *)response.object.get())->members_, false);
		break;
	case TD::chatMembers::ID:
		GcAddMembers(pChat, ((TD::chatMembers *)response.object.get())->members_, false);
		break;
	default:
		debugLogA("Gotten class ID %d instead of %d, exiting", response.object->get_id(), TD::basicGroupFullInfo::ID);
		return;
	}
	if (pChat->bStartChat)
		GcRun(pChat);
}
void CTelegramProto::GcAddMembers(TG_USER *pChat, const TD::array> &pMembers, bool bSilent)
{
	for (auto &it : pMembers) {
		auto *pMember = it.get();
		if (pMember->member_id_->get_id() != TD::messageSenderUser::ID)
			continue;
		int64_t memberId = ((TD::messageSenderUser *)pMember->member_id_.get())->user_id_;
		auto *pChatUser = FindUser(memberId);
		if (pChatUser == nullptr)
			continue;
		wchar_t wszUserId[100];
		_i64tow(memberId, wszUserId, 10);
		GCEVENT gce = { pChat->m_si, GC_EVENT_JOIN };
		gce.pszUID.w = wszUserId;
		gce.pszStatus.w = getRoleById(pMember->status_->get_id());
		if (bSilent)
			gce.dwFlags = GCEF_SILENT;
		switch (pChatUser->hContact) {
		case 0:
			gce.bIsMe = true;
			__fallthrough;
		case INVALID_CONTACT_ID:
			gce.pszNick.w = pChatUser->wszNick.c_str();
			break;
		default:
			gce.pszNick.w = Clist_GetContactDisplayName(pChatUser->hContact);
			break;
		}
		if (*gce.pszNick.w == '@')
			gce.pszNick.w++;
		Chat_Event(&gce);
	}
	Chat_Control(pChat->m_si, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE);
	Chat_Control(pChat->m_si, SESSION_ONLINE);
}
/////////////////////////////////////////////////////////////////////////////////////////
int CTelegramProto::GcMuteHook(WPARAM hContact, LPARAM mode)
{
	if (Proto_IsProtoOnContact(hContact, m_szModuleName)) {
		if (auto *pUser = FindUser(GetId(hContact))) {
			auto settings = TD::make_object();
			memcpy(settings.get(), &pUser->notificationSettings, sizeof(pUser->notificationSettings));
			switch (mode) {
			case CHATMODE_MUTE:
				settings->use_default_mute_for_ = false;
				settings->mute_for_ = 45000000;
				break;
			default:
				settings->use_default_mute_for_ = true;
				settings->mute_for_ = 0;
				break;
			}
			SendQuery(new TD::setChatNotificationSettings(pUser->chatId, std::move(settings)));
		}
	}
	return 0;
}
/////////////////////////////////////////////////////////////////////////////////////////
enum
{
	IDM_LEAVE = 1,
};
int CTelegramProto::GcEventHook(WPARAM, LPARAM lParam)
{
	GCHOOK *gch = (GCHOOK *)lParam;
	if (gch == nullptr)
		return 0;
	if (mir_strcmpi(gch->si->pszModule, m_szModuleName))
		return 0;
	auto userId = _wtoi64(gch->si->ptszID);
	switch (gch->iType) {
	case GC_USER_MESSAGE:
		if (gch->ptszText && mir_wstrlen(gch->ptszText) > 0) {
			rtrimw(gch->ptszText);
			Chat_UnescapeTags(gch->ptszText);
			if (auto *pUser = FindUser(userId)) {
				TD::int53 replyId = 0;
				if (auto *pDlg = gch->si->pDlg) {
					DB::EventInfo dbei(pDlg->m_hQuoteEvent, false);
					if (dbei)
						replyId = dbei2id(dbei);
				}
				SendTextMessage(pUser->chatId, GetId(gch->si->hContact, DBKEY_THREAD), replyId, T2Utf(gch->ptszText));
			}
		}
		break;
	case GC_USER_PRIVMESS:
		Chat_SendPrivateMessage(gch);
		break;
	case GC_USER_LOGMENU:
		Chat_LogMenu(gch);
		break;
	case GC_USER_NICKLISTMENU:
		break;
	}
	return 1;
}
void CTelegramProto::Chat_LogMenu(GCHOOK *gch)
{
	switch (gch->dwData) {
	case IDM_LEAVE:
		SvcLeaveChat(gch->si->hContact, 0);
		break;
	}
}
/////////////////////////////////////////////////////////////////////////////////////////
void CTelegramProto::OnLeaveChat(td::ClientManager::Response &, void *pUserInfo)
{
	auto *pUser = (TG_USER *)pUserInfo;
	wchar_t wszId[100];
	_i64tow(pUser->id, wszId, 10);
	if (auto *si = Chat_Find(wszId, m_szModuleName))
		Chat_Terminate(si);
	db_delete_contact(pUser->hContact);
}
INT_PTR CTelegramProto::SvcLeaveChat(WPARAM hContact, LPARAM)
{
	int64_t id = GetId(hContact);
	if (auto *pUser = FindUser(id)) {
		pUser->m_si = nullptr;
		SendQuery(new TD::leaveChat(pUser->chatId), &CTelegramProto::OnLeaveChat, pUser);
	}
	return 0;
}
void CTelegramProto::Chat_SendPrivateMessage(GCHOOK *gch)
{
	MCONTACT hContact;
	TD::int53 userId = _wtoi64(gch->ptszUID);
	auto *pUser = FindUser(userId);
	if (pUser == nullptr || pUser->hContact == INVALID_CONTACT_ID) {
		PROTOSEARCHRESULT psr = { sizeof(psr) };
		psr.id.w = (wchar_t *)gch->ptszUID;
		psr.firstName.w = (wchar_t *)gch->ptszNick;
		hContact = AddToList(PALF_TEMPORARY, &psr);
		if (hContact == 0)
			return;
		setWString(hContact, "Nick", gch->ptszNick);
		Contact::Hide(hContact);
		db_set_dw(hContact, "Ignore", "Mask1", 0);
	}
	else hContact = GetRealContact(pUser);
	CallService(MS_MSG_SENDMESSAGE, hContact, 0);
}
/////////////////////////////////////////////////////////////////////////////////////////
void CTelegramProto::GcChangeMember(TG_USER *pChat, const char *adminId, TD::int53 userId, bool bJoined)
{
	if (pChat->m_si == nullptr)
		return;
	if (auto *pMember = FindUser(userId)) {
		CMStringW wszId(FORMAT, L"%lld", pMember->id), wszNick(pMember->getDisplayName());
		Utf2T wszAdminId(adminId);
		GCEVENT gce = { pChat->m_si, (bJoined) ? GC_EVENT_JOIN : GC_EVENT_PART };
		gce.pszUID.w = wszId;
		gce.pszNick.w = wszNick;
		gce.bIsMe = false;
		gce.time = time(0);
		gce.pszStatus.w = TranslateT("Participant");
		gce.pszText.w = wszAdminId;
		Chat_Event(&gce);
	}
}
void CTelegramProto::GcChangeTopic(TG_USER *pChat, const wchar_t *pwszNewTopic)
{
	if (pChat->m_si == nullptr || pwszNewTopic == nullptr)
		return;
	GCEVENT gce = { pChat->m_si, GC_EVENT_TOPIC };
	gce.pszText.w = pwszNewTopic;
	gce.time = time(0);
	Chat_Event(&gce);
}
/////////////////////////////////////////////////////////////////////////////////////////
static gc_item sttLogListItems[] =
{
	{ LPGENW("&Leave chat session"), IDM_LEAVE, MENU_ITEM }
};
int CTelegramProto::GcMenuHook(WPARAM, LPARAM lParam)
{
	GCMENUITEMS *gcmi = (GCMENUITEMS *)lParam;
	if (gcmi == nullptr)
		return 0;
	if (mir_strcmpi(gcmi->pszModule, m_szModuleName))
		return 0;
	auto *pUser = FindUser(_wtoi64(gcmi->pszID));
	if (pUser == nullptr)
		return 0;
	if (gcmi->Type == MENU_ON_LOG) {
		Chat_AddMenuItems(gcmi->hMenu, _countof(sttLogListItems), sttLogListItems, &g_plugin);
	}
	else if (gcmi->Type == MENU_ON_NICKLIST) {
	}
	return 0;
}
/////////////////////////////////////////////////////////////////////////////////////////
// Server data
void CTelegramProto::ProcessBasicGroup(TD::updateBasicGroup *pObj)
{
	auto *pBasicGroup = pObj->basic_group_.get();
	auto iStatusId = pBasicGroup->status_->get_id();
	if (iStatusId == TD::chatMemberStatusBanned::ID) {
		if (pBasicGroup->upgraded_to_supergroup_id_) {
			auto *pUser = FindUser(pBasicGroup->upgraded_to_supergroup_id_);
			if (pUser) {
				pUser->bLoadMembers = true;
				if (pUser->m_si)
					pUser->m_si->bHasNicklist = true;
				if (auto *pOldUser = FindUser(pBasicGroup->id_)) {
					pUser->hContact = pOldUser->hContact;
					SetId(pUser->hContact, pBasicGroup->upgraded_to_supergroup_id_);
				}
			}
		}
		debugLogA("We are banned here, skipping");
		return;
	}
	TG_BASIC_GROUP tmp(pBasicGroup->id_, 0);
	auto *pGroup = m_arBasicGroups.find(&tmp);
	if (pGroup == nullptr) {
		pGroup = new TG_BASIC_GROUP(tmp.id, std::move(pObj->basic_group_));
		m_arBasicGroups.insert(pGroup);
	}
	else pGroup->group = std::move(pObj->basic_group_);
	TG_USER *pUser;
	if (iStatusId == TD::chatMemberStatusLeft::ID) {
		pUser = AddFakeUser(tmp.id, true);
		pUser->wszLastName.Format(TranslateT("%d member(s)"), pGroup->group->member_count_);
	}
	else pUser = AddUser(tmp.id, true);
	pUser->bLoadMembers = true;
}
void CTelegramProto::ProcessBasicGroupInfo(TD::updateBasicGroupFullInfo *pObj)
{
	auto *pChat = FindUser(pObj->basic_group_id_);
	if (pChat == nullptr || pChat->m_si == nullptr)
		return;
	if (auto *pInfo = pObj->basic_group_full_info_.get()) {
		if (!pInfo->description_.empty())
			GcChangeTopic(pChat, Utf2T(pInfo->description_.c_str()));
		g_chatApi.UM_RemoveAll(pChat->m_si);
		GcAddMembers(pChat, pInfo->members_, true);
	}
}
void CTelegramProto::ProcessForum(TD::updateForumTopicInfo *pForum)
{
	auto *pUser = FindChat(pForum->chat_id_);
	if (!pUser) {
		debugLogA("Uknown chat id %lld, skipping", pForum->chat_id_);
		return;
	}
	if (pUser->m_si == nullptr) {
		debugLogA("No parent chat for id %lld, skipping", pForum->chat_id_);
		return;
	}
	wchar_t wszId[100];
	mir_snwprintf(wszId, L"%lld_%lld", pForum->chat_id_, pForum->info_->message_thread_id_);
	auto *si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszId, Utf2T(pForum->info_->name_.c_str()), pUser);
	si->pParent = pUser->m_si;
	SetId(si->hContact, pForum->info_->message_thread_id_, DBKEY_THREAD);
	SetId(si->hContact, pUser->id, DBKEY_OWNER);
	Chat_Mute(si->hContact, Chat_IsMuted(pUser->hContact));
	Clist_SetGroup(si->hContact, ptrW(Clist_GetGroup(pUser->hContact)));
	Chat_Control(si, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE);
	Chat_Control(si, SESSION_ONLINE);
}
void CTelegramProto::ProcessSuperGroup(TD::updateSupergroup *pObj)
{
	auto iStatusId = pObj->supergroup_->status_->get_id();
	if (iStatusId == TD::chatMemberStatusBanned::ID) {
		debugLogA("We are banned here, skipping");
		return;
	}
	TG_SUPER_GROUP tmp(pObj->supergroup_->id_, 0);
	auto *pGroup = m_arSuperGroups.find(&tmp);
	if (pGroup == nullptr) {
		pGroup = new TG_SUPER_GROUP(tmp.id, std::move(pObj->supergroup_));
		m_arSuperGroups.insert(pGroup);
	}
	else pGroup->group = std::move(pObj->supergroup_);
	if (iStatusId == TD::chatMemberStatusLeft::ID) {
		auto *pUser = AddFakeUser(tmp.id, true);
		pUser->isForum = pGroup->group->is_forum_;
		if (pUser->hContact == INVALID_CONTACT_ID) {
			// cache some information for the search
			if (pUser->wszNick.IsEmpty())
				pUser->wszNick = Utf2T(getName(pGroup->group->usernames_.get()));
			pUser->wszLastName.Format(TranslateT("%d member(s)"), pGroup->group->member_count_);
		}
		else RemoveFromClist(pUser);
	}
	else {
		auto *pChat = AddUser(tmp.id, true);
		pChat->isForum = pGroup->group->is_forum_;
		if (!pGroup->group->is_channel_)
			pChat->bLoadMembers = true;
		if (!Contact::OnList(pChat->hContact))
			Contact::PutOnList(pChat->hContact);
		if (pChat->bStartChat)
			InitGroupChat(pChat, pChat->wszNick);
		if (pChat->m_si) {
			CMStringW wszUserId(FORMAT, L"%lld", m_iOwnId);
			GCEVENT gce = { pChat->m_si, GC_EVENT_SETSTATUS };
			gce.pszUID.w = wszUserId;
			gce.time = time(0);
			gce.bIsMe = true;
			gce.pszStatus.w = getRoleById(iStatusId);
			gce.pszText.w = TranslateT("Admin");
			Chat_Event(&gce);
		}
	}
}