/*
Copyright © 2009 Jim Porter
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,  see .
*/
#include "common.h"
#include "proto.h"
#include "utility.h"
#include "theme.h"
#include "ui.h"
#include "m_folders.h"
#include "m_historyevents.h"
#include 
#include 
#include 
#include 
TwitterProto::TwitterProto(const char *proto_name, const TCHAR *username)
{
	m_szProtoName  = mir_strdup (proto_name);
	m_szModuleName = mir_strdup (proto_name);
	m_tszUserName  = mir_tstrdup(username);
	CreateProtoService(m_szModuleName, PS_CREATEACCMGRUI, 
		&TwitterProto::SvcCreateAccMgrUI, this);
	CreateProtoService(m_szModuleName, PS_GETNAME,   &TwitterProto::GetName,     this);
	CreateProtoService(m_szModuleName, PS_GETSTATUS, &TwitterProto::GetStatus,   this);
	CreateProtoService(m_szModuleName, PS_JOINCHAT,  &TwitterProto::OnJoinChat,  this);
	CreateProtoService(m_szModuleName, PS_LEAVECHAT, &TwitterProto::OnLeaveChat, this);
	CreateProtoService(m_szModuleName, PS_GETMYAVATAR, &TwitterProto::GetAvatar, this);
	CreateProtoService(m_szModuleName, PS_SETMYAVATAR, &TwitterProto::SetAvatar, this);
	HookProtoEvent(ME_DB_CONTACT_DELETED,        &TwitterProto::OnContactDeleted,      this);
	HookProtoEvent(ME_CLIST_PREBUILDSTATUSMENU,  &TwitterProto::OnBuildStatusMenu,     this);
	HookProtoEvent(ME_OPT_INITIALISE,            &TwitterProto::OnOptionsInit,         this);
	char *profile = Utils_ReplaceVars("%miranda_avatarcache%");
	def_avatar_folder_ = std::string(profile)+"\\"+m_szModuleName;
	mir_free(profile);
	hAvatarFolder_ = FoldersRegisterCustomPath(m_szModuleName, "Avatars", 
		def_avatar_folder_.c_str());
	// Initialize hotkeys
	char text[512];
	HOTKEYDESC hkd = {sizeof(hkd)};
	hkd.cbSize = sizeof(hkd);
	hkd.pszName = text;
	hkd.pszService = text;
	hkd.pszSection = m_szModuleName; // Section title; TODO: use username?
	mir_snprintf(text, SIZEOF(text), "%s/Tweet", m_szModuleName);
	hkd.pszDescription = "Send Tweet";
	CallService(MS_HOTKEY_REGISTER,  0,  (LPARAM)&hkd);
	signon_lock_  = CreateMutex(0, false, 0);
	avatar_lock_  = CreateMutex(0, false, 0);
	twitter_lock_ = CreateMutex(0, false, 0);
	SetAllContactStatuses(ID_STATUS_OFFLINE); // In case we crashed last time
}
TwitterProto::~TwitterProto()
{
	CloseHandle(twitter_lock_);
	CloseHandle(avatar_lock_);
	CloseHandle(signon_lock_);
	mir_free(m_szProtoName);
	mir_free(m_szModuleName);
	mir_free(m_tszUserName);
	if (hNetlib_)
		Netlib_CloseHandle(hNetlib_);
	if (hAvatarNetlib_)
		Netlib_CloseHandle(hAvatarNetlib_);
}
// *************************
DWORD TwitterProto::GetCaps(int type, HANDLE hContact)
{
	switch(type)
	{
	case PFLAGNUM_1:
		return PF1_IM | PF1_MODEMSGRECV | PF1_BASICSEARCH | PF1_SEARCHBYEMAIL |
			PF1_SERVERCLIST | PF1_CHANGEINFO;
	case PFLAGNUM_2:
		return PF2_ONLINE;
	case PFLAGNUM_3:
		return PF2_ONLINE;
	case PFLAGNUM_4:
		return PF4_NOCUSTOMAUTH | PF4_IMSENDUTF | PF4_AVATARS;
	case PFLAG_MAXLENOFMESSAGE:
		return 140;
	case PFLAG_UNIQUEIDTEXT:
		return (int) "Username";
	case PFLAG_UNIQUEIDSETTING:
		return (int) TWITTER_KEY_UN;
	}
	return 0;
}
HICON TwitterProto::GetIcon(int index)
{
	if (LOWORD(index) == PLI_PROTOCOL)
	{
		HICON ico = (HICON)CallService(MS_SKIN2_GETICON, 0, (LPARAM)"Twitter_twitter");
		return CopyIcon(ico);
	}
	else
		return 0;
}
// *************************
int TwitterProto::RecvMsg(HANDLE hContact, PROTORECVEVENT *pre)
{
	CCSDATA ccs = { hContact, PSR_MESSAGE, 0, reinterpret_cast(pre) };
	return CallService(MS_PROTO_RECVMSG, 0, reinterpret_cast(&ccs));
}
// *************************
struct send_direct
{
	send_direct(HANDLE hContact, const std::tstring &msg) : hContact(hContact), msg(msg) {}
	HANDLE hContact;
	std::tstring msg;
};
void TwitterProto::SendSuccess(void *p)
{
	if (p == 0)
		return;
	send_direct *data = static_cast(p);
	DBVARIANT dbv;
	if ( !DBGetContactSettingTString(data->hContact, m_szModuleName, TWITTER_KEY_UN, &dbv))
	{
		ScopedLock s(twitter_lock_);
		twit_.send_direct(dbv.ptszVal, data->msg);
		ProtoBroadcastAck(m_szModuleName, data->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, 
			(HANDLE)1, 0);
		DBFreeVariant(&dbv);
	}
	delete data;
}
int TwitterProto::SendMsg(HANDLE hContact, int flags, const char *msg)
{
	if (m_iStatus != ID_STATUS_ONLINE)
		return 0;
	std::tstring tMsg = _A2T(msg);
	ForkThread(&TwitterProto::SendSuccess,  this, new send_direct(hContact,  tMsg));
	return 1;
}
// *************************
int TwitterProto::SetStatus(int new_status)
{
	int old_status = m_iStatus;
	if (new_status == m_iStatus)
		return 0;
	m_iDesiredStatus = new_status;
	if (new_status == ID_STATUS_ONLINE)
	{
		if (old_status == ID_STATUS_CONNECTING)
			return 0;
		m_iStatus = ID_STATUS_CONNECTING;
		ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, 
			(HANDLE)old_status, m_iStatus);
		ForkThread(&TwitterProto::SignOn, this);
	}
	else if (new_status == ID_STATUS_OFFLINE)
	{
		m_iStatus = m_iDesiredStatus;
		SetAllContactStatuses(ID_STATUS_OFFLINE);
		ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, 
			(HANDLE)old_status, m_iStatus);
	}
	return 0;
}
// *************************
int TwitterProto::OnEvent(PROTOEVENTTYPE event, WPARAM wParam, LPARAM lParam)
{
	switch(event)
	{
	case EV_PROTO_ONLOAD:    return OnModulesLoaded(wParam, lParam);
	case EV_PROTO_ONEXIT:    return OnPreShutdown  (wParam, lParam);
	case EV_PROTO_ONOPTIONS: return OnOptionsInit  (wParam, lParam);
	}
	return 1;
}
// *************************
int TwitterProto::SvcCreateAccMgrUI(WPARAM wParam, LPARAM lParam)
{
	return (int)CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_TWITTERACCOUNT),  
		 (HWND)lParam,  first_run_dialog,  (LPARAM)this );
}
int TwitterProto::GetName(WPARAM wParam, LPARAM lParam)
{
	lstrcpynA(reinterpret_cast(lParam), m_szProtoName, wParam);
	return 0;
}
int TwitterProto::GetStatus(WPARAM wParam, LPARAM lParam)
{
	return m_iStatus;
}
int TwitterProto::ReplyToTweet(WPARAM wParam, LPARAM lParam)
{
	// TODO: support replying to tweets instead of just users
	HANDLE hContact = reinterpret_cast(wParam);
	HWND hDlg = CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_TWEET), 
		 (HWND)0, tweet_proc, reinterpret_cast(this));
	DBVARIANT dbv;
	if ( !DBGetContactSettingTString(hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) {
		SendMessage(hDlg, WM_SETREPLY, reinterpret_cast(dbv.ptszVal), 0);
		DBFreeVariant(&dbv);
	}
	ShowWindow(hDlg, SW_SHOW);
	return 0;
}
int TwitterProto::VisitHomepage(WPARAM wParam, LPARAM lParam)
{
	HANDLE hContact = reinterpret_cast(wParam);
	DBVARIANT dbv;
	if ( !DBGetContactSettingString(hContact, m_szModuleName, "Homepage", &dbv)) {
		CallService(MS_UTILS_OPENURL, 1, reinterpret_cast(dbv.pszVal));
		DBFreeVariant(&dbv);
	}
	else {
		// TODO: remove this
		if ( !DBGetContactSettingString(hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) {
			std::string url = profile_base_url(twit_.get_base_url()) + http::url_encode(dbv.pszVal);
			DBWriteContactSettingString(hContact, m_szModuleName, "Homepage", url.c_str());
			CallService(MS_UTILS_OPENURL, 1, reinterpret_cast(url.c_str()));
			DBFreeVariant(&dbv);
		}
	}
	return 0;
}
// *************************
int TwitterProto::OnBuildStatusMenu(WPARAM wParam, LPARAM lParam)
{
	HGENMENU hRoot = pcli->pfnGetProtocolMenu(m_szModuleName);
	if (hRoot == NULL)
		return 0;
	CLISTMENUITEM mi = {sizeof(mi)};
	char text[200];
	strcpy(text, m_szModuleName);
	char *tDest = text+strlen(text);
	mi.pszService = text;
	mi.hParentMenu = hRoot;
	mi.flags = CMIF_ICONFROMICOLIB|CMIF_ROOTHANDLE;
	mi.position = 1001;
	HANDLE m_hMenuRoot = reinterpret_cast( CallService(
		MS_CLIST_ADDSTATUSMENUITEM, 0, reinterpret_cast(&mi)));
	// "Send Tweet..."
	CreateProtoService(m_szModuleName, "/Tweet", &TwitterProto::OnTweet, this);
	strcpy(tDest, "/Tweet");
	mi.pszName = LPGEN("Send Tweet...");
	mi.popupPosition = 200001;
	mi.icolibItem = GetIconHandle("tweet");
	HANDLE m_hMenuBookmarks = reinterpret_cast( CallService(
		MS_CLIST_ADDSTATUSMENUITEM, 0, reinterpret_cast(&mi)));
	return 0;
}
int TwitterProto::OnOptionsInit(WPARAM wParam, LPARAM lParam)
{
	OPTIONSDIALOGPAGE odp = {sizeof(odp)};
	odp.position    = 271828;
	odp.hInstance   = g_hInstance;
	odp.ptszGroup   = LPGENT("Network");
	odp.ptszTitle   = m_tszUserName;
	odp.dwInitParam = LPARAM(this);
	odp.flags       = ODPF_BOLDGROUPS | ODPF_TCHAR;
	odp.ptszTab     = LPGENT("Basic");
    odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS);
	odp.pfnDlgProc  = options_proc;
	CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp);
	if (ServiceExists(MS_POPUP_ADDPOPUPT))
	{
		odp.ptszTab     = LPGENT("Popups");
		odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS_POPUPS);
		odp.pfnDlgProc  = popup_options_proc;
		CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp);
	}
	return 0;
}
int TwitterProto::OnTweet(WPARAM wParam, LPARAM lParam)
{
	if (m_iStatus != ID_STATUS_ONLINE)
		return 1;
	HWND hDlg = CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_TWEET), 
		 (HWND)0, tweet_proc, reinterpret_cast(this));
	ShowWindow(hDlg, SW_SHOW);
	return 0;
}
int TwitterProto::OnModulesLoaded(WPARAM wParam, LPARAM lParam)
{
	TCHAR descr[512];
	NETLIBUSER nlu = {sizeof(nlu)};
	nlu.flags = NUF_OUTGOING | NUF_INCOMING | NUF_HTTPCONNS | NUF_TCHAR;
	nlu.szSettingsModule = m_szModuleName;
	// Create standard network connection
	mir_sntprintf(descr, SIZEOF(descr), TranslateT("%s server connection"), m_tszUserName);
	nlu.ptszDescriptiveName = descr;
	hNetlib_ = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu);
	if (hNetlib_ == 0)
		MessageBoxA(0, "Unable to get Netlib connection for Twitter", "", 0);
	// Create avatar network connection (TODO: probably remove this)
	char module[512];
	mir_snprintf(module, SIZEOF(module), "%sAv", m_szModuleName);
	nlu.szSettingsModule = module;
	mir_sntprintf(descr, SIZEOF(descr), TranslateT("%s avatar connection"), m_tszUserName);
	nlu.ptszDescriptiveName = descr;
	hAvatarNetlib_ = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu);
	if (hAvatarNetlib_ == 0)
		MessageBoxA(0, "Unable to get avatar Netlib connection for Twitter", "", 0);
	twit_.set_handle(hNetlib_);
	GCREGISTER gcr = {sizeof(gcr)};
	gcr.pszModule = m_szModuleName;
	gcr.pszModuleDispName = m_szModuleName;
	gcr.iMaxText = 140;
	CallService(MS_GC_REGISTER, 0, reinterpret_cast(&gcr));
	if (ServiceExists(MS_HISTORYEVENTS_REGISTER))
	{
		HISTORY_EVENT_HANDLER heh = {0};
		heh.cbSize = sizeof(heh);
		heh.module = m_szModuleName;
		heh.name = "tweet";
		heh.description = "Tweet";
		heh.eventType = TWITTER_DB_EVENT_TYPE_TWEET;
		heh.defaultIconName = "Twitter_tweet";
		heh.flags = HISTORYEVENTS_FLAG_SHOW_IM_SRMM
					| HISTORYEVENTS_FLAG_EXPECT_CONTACT_NAME_BEFORE
// Not sure:		| HISTORYEVENTS_FLAG_FLASH_MSG_WINDOW
					| HISTORYEVENTS_REGISTERED_IN_ICOLIB;
		CallService(MS_HISTORYEVENTS_REGISTER,  (WPARAM) &heh,  0);
	}
	else
	{
		DBEVENTTYPEDESCR evt = {sizeof(evt)};
		evt.eventType = TWITTER_DB_EVENT_TYPE_TWEET;
		evt.module = m_szModuleName;
		evt.descr = "Tweet";
		evt.flags = DETF_HISTORY | DETF_MSGWINDOW;
		CallService(MS_DB_EVENT_REGISTERTYPE, 0, reinterpret_cast(&evt));
	}
	return 0;
}
int TwitterProto::OnPreShutdown(WPARAM wParam, LPARAM lParam)
{
	Netlib_Shutdown(hNetlib_);
	Netlib_Shutdown(hAvatarNetlib_);
	return 0;
}
int TwitterProto::OnPrebuildContactMenu(WPARAM wParam, LPARAM lParam)
{
	HANDLE hContact = reinterpret_cast(wParam);
	if (IsMyContact(hContact))
		ShowContactMenus(true);
	return 0;
}
void TwitterProto::ShowPopup(const wchar_t *text)
{
	POPUPDATAT popup = {};
	_sntprintf(popup.lptzContactName, MAX_CONTACTNAME, TranslateT("%s Protocol"), m_tszUserName);
	wcs_to_tcs(CP_UTF8, text, popup.lptzText, MAX_SECONDLINE);
	if (ServiceExists(MS_POPUP_ADDPOPUPT))
		CallService(MS_POPUP_ADDPOPUPT, reinterpret_cast(&popup), 0);
	else
		MessageBox(0, popup.lptzText, popup.lptzContactName, 0);
}
void TwitterProto::ShowPopup(const char *text)
{
	POPUPDATAT popup = {};
	_sntprintf(popup.lptzContactName, MAX_CONTACTNAME, TranslateT("%s Protocol"), m_tszUserName);
	mbcs_to_tcs(CP_UTF8, text, popup.lptzText, MAX_SECONDLINE);
	if (ServiceExists(MS_POPUP_ADDPOPUPT))
		CallService(MS_POPUP_ADDPOPUPT, reinterpret_cast(&popup), 0);
	else
		MessageBox(0, popup.lptzText, popup.lptzContactName, 0);
}
int TwitterProto::LOG(const char *fmt, ...)
{
	va_list va;
	char text[1024];
	if (!hNetlib_)
		return 0;
	va_start(va, fmt);
	mir_vsnprintf(text, sizeof(text), fmt, va);
	va_end(va);
	return CallService(MS_NETLIB_LOG, (WPARAM)hNetlib_, (LPARAM)text);
}
// TODO: the more I think about it,  the more I think all twit.* methods should
// be in MessageLoop
void TwitterProto::SendTweetWorker(void *p)
{
	if (p == 0)
		return;
	TCHAR *text = static_cast(p);
	ScopedLock s(twitter_lock_);
	twit_.set_status(text);
	mir_free(text);
}
void TwitterProto::UpdateSettings()
{
	if (db_byte_get(0, m_szModuleName, TWITTER_KEY_CHATFEED, 0))
	{
		if (!in_chat_)
			OnJoinChat(0, 0);
	}
	else
	{
		if (in_chat_)
			OnLeaveChat(0, 0);
		for(HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
			hContact;
			hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0))
		{
			if (!IsMyContact(hContact, true))
				continue;
			if (db_byte_get(hContact, m_szModuleName, "ChatRoom", 0))
				CallService(MS_DB_CONTACT_DELETE, reinterpret_cast(hContact), 0);
		}
	}
}
std::string TwitterProto::GetAvatarFolder()
{
	char path[MAX_PATH];
	if (hAvatarFolder_ && FoldersGetCustomPath(hAvatarFolder_, path, sizeof(path),  "") == 0)
		return path;
	else
		return def_avatar_folder_;
}
int TwitterProto::GetAvatar(WPARAM, LPARAM)
{
	return 0;
}
int TwitterProto::SetAvatar(WPARAM, LPARAM)
{
	return 0;
}