/*
UserinfoEx plugin for Miranda IM

Copyright:
© 2006-2010 DeathAxe, Yasnovidyashii, Merlin, K. Romanov, Kreol

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "stdafx.h"

/**
 * name:	BoldGroupTitlesEnumChildren()
 * desc:	set font of groupboxes to bold
 *
 * return:	0
 **/
BOOL CALLBACK BoldGroupTitlesEnumChildren(HWND hWnd, LPARAM lParam)
{
	 wchar_t szClass[64];
	 GetClassName(hWnd, szClass, 64);
	 if (!mir_wstrcmp(szClass, L"Button") && (GetWindowLongPtr(hWnd, GWL_STYLE) & 0x0F) == BS_GROUPBOX)
			SendMessage(hWnd, WM_SETFONT, lParam, NULL);
	 return TRUE;
}

/**
 * name:	CPsTreeItem
 * class:	CPsTreeItem
 * desc:	default constructor
 * param:	pPsh		- propertysheet's init structure
 *			odp			- optiondialogpage structure with the info about the item to add
 * return: nothing
 **/
CPsTreeItem::CPsTreeItem()
{
}

/**
 * name:	~CPsTreeItem
 * class:	CPsTreeItem
 * desc:	default destructor
 * param:	none
 * return:	nothing
 **/
CPsTreeItem::~CPsTreeItem()
{
	if (_hWnd)		DestroyWindow(_hWnd);
	if (_pszName)	mir_free(_pszName);
	if (_ptszLabel)	mir_free(_ptszLabel);
	if (_pszProto)	mir_free((LPVOID)_pszProto);
}

/**
 * name:	PropertyKey
 * class:	CPsTreeItem
 * desc:	merge the treeitem's unique name with a prefix to form a setting string
 * param:	pszProperty - the prefix to add
 * return:	pointer to the setting string
 **/
LPCSTR CPsTreeItem::PropertyKey(LPCSTR pszProperty)
{
	static CHAR pszSetting[MAXSETTING];
	mir_snprintf(pszSetting, "{%s\\%s}_%s", _pszPrefix, _pszName, pszProperty);
	return pszSetting;
}

/**
 * name:	GlobalName
 * class:	CPsTreeItem
 * desc:	return item name without prepended protocol name
 * param:	nothing
 * return:	item name without protocol name
 **/
LPCSTR CPsTreeItem::GlobalName()
{
	LPSTR pgn = nullptr;
	
	if (_dwFlags & PSPF_PROTOPREPENDED) {
		pgn = mir_strchr(_pszName, '\\');
		if (pgn && pgn[1])
			pgn++;
	}
	return (!pgn || !*pgn) ?_pszName : pgn;
}

/**
 * name:	GlobalPropertyKey
 * class:	CPsTreeItem
 * desc:	merge the treeitem's unique name with a prefix to form a setting string
 * param:	pszProperty - the prefix to add
 * return:	pointer to the setting string
 **/
LPCSTR CPsTreeItem::GlobalPropertyKey(LPCSTR pszProperty)
{
	static CHAR pszSetting[MAXSETTING];
	mir_snprintf(pszSetting, "{Global\\%s}_%s", GlobalName(), pszProperty);
	return pszSetting;
}

/**
 * name:	IconKey
 * class:	CPsTreeItem
 * desc:	merge the treeitem's unique name with a prefix to form a setting string
 * param:	pszProperty			- the prefix to add
 * return:	pointer to the setting string
 **/
LPCSTR CPsTreeItem::IconKey()
{
	LPCSTR pszIconName = GlobalName();
	if (pszIconName)
	{
		static CHAR pszSetting[MAXSETTING];
		mir_snprintf(pszSetting, MODULENAME"_{%s}", pszIconName);
		return pszSetting;
	}
	return nullptr;
}

/**
 * name:	ParentItemName()
 * class:	CPsTreeItem
 * desc:	returns the unique name for the parent item
 * param:	nothing
 * return:	length of group name
 **/
LPSTR CPsTreeItem::ParentItemName()
{
	// try to read the parent item from the database
	DBVARIANT dbv;
	if (!DB::Setting::GetAString(0, MODULENAME, PropertyKey(SET_ITEM_GROUP), &dbv))
		return dbv.pszVal;

	const CHAR* p = mir_strrchr(_pszName, '\\');
	if (p) {
		int cchGroup = p - _pszName + 1;
		return mir_strncpy((LPSTR)mir_alloc(cchGroup), _pszName, cchGroup);
	}
	return nullptr;
}

/**
 * name:	SetName
 * class:	CPsTreeItem
 * desc:	set the unique name for this item from a given title as it comes with OPTIONDIALOGPAGE
 * param:	ptszTitle		- the title which is the base for the unique name
 *			bIsUnicode		- if TRUE the title is unicode
 * return:	0 on success, 1 to 4 indicating the failed operation
 **/
int CPsTreeItem::Name(const wchar_t *ptszTitle, bool bIsUnicode)
{
	// convert title to utf8
	_pszName = (bIsUnicode) ? mir_utf8encodeW((LPWSTR)ptszTitle) : mir_utf8encode((LPSTR)ptszTitle);
	if (_pszName) {
		// convert disallowed characters
		for (uint32_t i = 0; _pszName[i] != 0; i++) {
			switch (_pszName[i]) {
			case '{': _pszName[i] = '('; break;
			case '}': _pszName[i] = ')'; break;
			}
		}
	}
	return _pszName == nullptr;
}

/**
 * name:	HasName
 * class:	CPsTreeItem
 * desc:	returns true, if current item has the name provided by the parameter
 * params:	pszName	- the name to match the item with
 * return:	nothing
 **/
uint8_t	CPsTreeItem::HasName(const LPCSTR pszName) const
{ 
	return !mir_strcmpi(_pszName, pszName); 
};

/**
 * name:	Rename
 * class:	CPsTreeItem
 * desc:	Rename label for the treeitem
 * params:	pszLabel	- the desired new label
 * return:	nothing
 **/
void CPsTreeItem::Rename(const LPTSTR pszLabel)
{
	if (pszLabel && *pszLabel) {
		LPTSTR pszDup = mir_wstrdup(pszLabel);
		if (pszDup) {
			if (_ptszLabel)
				mir_free(_ptszLabel);
			_ptszLabel =  pszDup;
			// convert disallowed characters
			while(*pszDup) {
				switch(*pszDup) {
					case '{': *pszDup = '('; break;
					case '}': *pszDup = ')'; break;
				}
				pszDup++;
			}
			AddFlags(PSTVF_LABEL_CHANGED);
		}
	}
}

/**
 * name:	ItemLabel
 * class:	CPsTreeItem
 * desc:	returns the label for a given item. The returned value must be freed after use!
 * param:	pszName		- uniquely identifiing string for a propertypage encoded with utf8 (e.g.: {group\item})
 * return:	Label in a newly allocated piece of memory
 **/
int CPsTreeItem::ItemLabel(bool bReadDBValue)
{
	// try to get custom label from database
	DBVARIANT dbv;
	if (!bReadDBValue || DB::Setting::GetWString(0, MODULENAME, GlobalPropertyKey(SET_ITEM_LABEL), &dbv) || (_ptszLabel = dbv.pwszVal) == nullptr) {
		// extract the name
		char *pszName = mir_strrchr(_pszName, '\\');
		if (pszName && pszName[1])
			pszName++;
		else
			pszName = _pszName;

		ptrW ptszLabel(mir_utf8decodeW(pszName));
		if (ptszLabel)
			replaceStrW(_ptszLabel, TranslateW(ptszLabel));
	}
	// return nonezero if label is invalid
	return _ptszLabel == nullptr;
}

/**
 * name:	ProtoIcon
 * class:	CPsTreeItem
 * desc:	check if current tree item name is a protocol name and return its icon if so
 * params:	none
 * return:	nothing
 **/
HICON CPsTreeItem::ProtoIcon()
{
	if (!_pszName)
		return nullptr;

	for (auto &pa : Accounts()) {
		if (!mir_wstrncmpi(pa->tszAccountName, _A2T(_pszName), mir_wstrlen(pa->tszAccountName))) {
			CHAR szIconID[MAX_PATH];
			mir_snprintf(szIconID, "core_status_%s1", pa->szModuleName);
			HICON hIco = IcoLib_GetIcon(szIconID);
			if (!hIco)
				hIco = (HICON)CallProtoService(pa->szModuleName, PS_LOADICON, PLI_PROTOCOL, NULL);

			return hIco;
		}
	}
	return nullptr;
}

/**
 * name:	Icon
 * class:	CPsTreeItem
 * desc:	load the icon, add to icolib if required and add to imagelist of treeview
 * params:	hIml			- treeview's imagelist to add the icon to
 *			odp				- pointer to USERINFOPAGE providing the information about the icon to load
 *			hDefaultIcon	- default icon to use
 * return: nothing
 **/
int CPsTreeItem::Icon(HIMAGELIST hIml, USERINFOPAGE *uip, bool bInitIconsOnly)
{
	// check parameter
	if (!_pszName || !uip)
		return 1;

	// load the icon if no icolib is installed or creating the required settingname failed
	LPCSTR pszIconName = IconKey();
	HANDLE hIcoLib = nullptr;

	// use icolib to handle icons
	HICON hIcon = IcoLib_GetIcon(pszIconName);
	if (!hIcon) {
		bool bNeedFree = false;
		ptrW pwszSection(mir_a2u(SECT_TREE));

		SKINICONDESC sid = {};
		sid.flags = SIDF_ALL_UNICODE;
		sid.pszName = (LPSTR)pszIconName;
		sid.description.w = _ptszLabel;
		sid.section.w = pwszSection;

		// the item to insert brings along an icon?
		if (uip->flags & ODPF_ICON) {
			// is it uinfoex item?
			if (uip->pPlugin == &g_plugin) {
				// the pszGroup holds the iconfile for items added by uinfoex
				sid.defaultFile.w = uip->szGroup.w;

				// icon library exists?
				if (sid.defaultFile.w)
					sid.iDefaultIndex = uip->dwInitParam;
				// no valid icon library
				else {
					bNeedFree = true;
					sid.hDefaultIcon = ImageList_GetIcon(hIml, 0, ILD_NORMAL);
					sid.iDefaultIndex = -1;
				}
			}
			else hIcoLib = (HANDLE)uip->dwInitParam;
		}
		// no icon to add, use default
		else {
			sid.iDefaultIndex = -1;
			sid.hDefaultIcon = ProtoIcon();
			if (!sid.hDefaultIcon)
				sid.hDefaultIcon = ImageList_GetIcon(hIml, 0, ILD_NORMAL), bNeedFree = true;
		}
		// add file to icolib
		if (!hIcoLib)
			g_plugin.addIcon(&sid);

		if (!bInitIconsOnly)
			hIcon = (hIcoLib) ? IcoLib_GetIconByHandle(hIcoLib) : IcoLib_GetIcon(pszIconName);
		if (bNeedFree)
			DestroyIcon(sid.hDefaultIcon);
	}
	
	if (!bInitIconsOnly && hIml) {
		// set custom icon to image list
		if (hIcon) return ((_iImage = ImageList_AddIcon(hIml, hIcon)) == -1);
		_iImage = 0;
	}
	else _iImage = -1;

	if (hIcoLib)
		IcoLib_ReleaseIcon(hIcon);
	return 0;
}

/**
 * name:	OnAddPage
 * class:	CPsTreeItem
 * desc:	inits the treeitem's attributes
 * params:	pPsh	- pointer to the property page's header structure
 *			odp		- USERINFOPAGE structure with the information about the page to add
 * return:	0 on success, 1 on failure
 **/
int CPsTreeItem::Create(CPsHdr *pPsh, USERINFOPAGE *uip)
{
	// check parameter
	if (!pPsh || !uip || !PtrIsValid(uip->pPlugin))
		return 1;

	// instance value
	_hInst = uip->pPlugin->getInst();
	_dwFlags = uip->flags;
	_initParam = uip->dwInitParam;

	// init page owning contact
	_hContact = pPsh->_hContact;
	_pszProto = mir_strdup(pPsh->_pszProto);

	// global settings prefix for current contact (is dialog owning contact's protocol by default)
	_pszPrefix = (pPsh->_pszPrefix) ? pPsh->_pszPrefix : "Owner";

	CMStringW wszTitle;
	if (_dwFlags & ODPF_USERINFOTAB) {
		wszTitle.Append(uip->szGroup.w);
		wszTitle.AppendChar('\\');
	}

	wszTitle.Append(uip->szTitle.w);
	if (pPsh->_dwFlags & PSF_PROTOPAGESONLY)
		wszTitle.AppendFormat(L" %d", pPsh->_nSubContact + 1);

	// set the unique utf8 encoded name for the item
	if (int err = Name(wszTitle, (_dwFlags & ODPF_UNICODE) == ODPF_UNICODE)) {
		MsgErr(nullptr, LPGENW("Creating unique name for a page failed with %d and error code %d"), err, GetLastError());
		return 1;
	}

	// read label from database or create it
	if (int err = ItemLabel(TRUE)) {
		MsgErr(nullptr, LPGENW("Creating the label for a page failed with %d and error code %d"), err, GetLastError());
		return 1;
	}

	// load icon for the item
	Icon(pPsh->_hImages, uip, (pPsh->_dwFlags & PSTVF_INITICONS) == PSTVF_INITICONS);
			
	// the rest is not needed if only icons are loaded
	if (pPsh->_dwFlags & PSTVF_INITICONS)
		return 0;

	// load custom order
	if (!(pPsh->_dwFlags & PSTVF_SORTTREE)) {
		_iPosition = g_plugin.getByte(PropertyKey(SET_ITEM_POS), uip->position);
		if ((_iPosition < 0) || (_iPosition > 0x800000A))
			_iPosition = 0;
	}
	// read visibility state
	_bState = g_plugin.getByte(PropertyKey(SET_ITEM_STATE), DBTVIS_EXPANDED);

	// fetch dialog 
	_pDialog = uip->pDialog;
	return 0;
}

/**
 * name:	DBSaveItemState
 * class:	CPsTreeItem
 * desc:	saves the current treeitem to database
 * param:	pszGroup			- name of the parent item
 *			iItemPosition		- iterated index to remember the order of the tree
 *			iState				- expanded|collapsed|invisible
 *			dwFlags				- tells what to save
 * return:	handle to new (moved) treeitem if successful or NULL otherwise
 **/
uint16_t CPsTreeItem::DBSaveItemState(LPCSTR pszGroup, int iItemPosition, UINT iState, uint32_t dwFlags)
{
	uint16_t numErrors = 0;

	// save group
	if ((dwFlags & PSTVF_GROUPS) && (dwFlags & PSTVF_POS_CHANGED))
		numErrors += db_set_utf(0, MODULENAME, PropertyKey(SET_ITEM_GROUP), (LPSTR)pszGroup);

	// save label
	if ((dwFlags & PSTVF_LABEL_CHANGED) && (_dwFlags & PSTVF_LABEL_CHANGED))
		g_plugin.setWString(GlobalPropertyKey(SET_ITEM_LABEL), Label());

	// save position
	if ((dwFlags & PSTVF_POS_CHANGED) && !(dwFlags & PSTVF_SORTTREE))
		g_plugin.setByte(PropertyKey(SET_ITEM_POS), iItemPosition);

	// save state
	if (dwFlags & PSTVF_STATE_CHANGED)
		g_plugin.setByte(PropertyKey(SET_ITEM_STATE), _hItem ? ((iState & TVIS_EXPANDED) ? DBTVIS_EXPANDED : DBTVIS_NORMAL) : DBTVIS_INVISIBLE);

	RemoveFlags(PSTVF_STATE_CHANGED|PSTVF_LABEL_CHANGED|PSTVF_POS_CHANGED);
	return numErrors;
}

/**
 * name:	CreateWnd
 * class:	CPsTreeItem
 * desc:	create the dialog for the propertysheet page
 * params:	pPs		- propertysheet's datastructure
 *			hDlg	- windowhandle of the propertysheet
 * return:	windowhandle of the dialog if successful
 **/
HWND CPsTreeItem::CreateWnd(LPPS pPs)
{
	if (pPs && !_hWnd && _pDialog) {
		_pDialog->SetParent(pPs->hDlg);
		_pDialog->SetContact(_hContact);
		_pDialog->Create();
		_hWnd = _pDialog->GetHwnd();
		if (_hWnd != nullptr) {
			// force child window (mainly for AIM property page)
			SetWindowLongPtr(_hWnd, GWL_STYLE, (GetWindowLongPtr(_hWnd, GWL_STYLE) & ~(WS_POPUP|WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME)) | WS_CHILD);
			SetWindowLongPtr(_hWnd, GWL_EXSTYLE, GetWindowLongPtr(_hWnd, GWL_EXSTYLE) & ~(WS_EX_APPWINDOW|WS_EX_STATICEDGE|WS_EX_CLIENTEDGE));

			// move dialog into the display area
			SetWindowPos(_hWnd, HWND_TOP, 
				pPs->rcDisplay.left,	pPs->rcDisplay.top,
				pPs->rcDisplay.right - pPs->rcDisplay.left,	
				pPs->rcDisplay.bottom - pPs->rcDisplay.top,	FALSE);
			// set bold titles
			if (_dwFlags & ODPF_BOLDGROUPS)
				EnumChildWindows(_hWnd, BoldGroupTitlesEnumChildren, (LPARAM)pPs->hBoldFont);
						
			// some initial notifications
			OnInfoChanged();
			OnPageIconsChanged();
			return _hWnd;
		}
	}
	return nullptr;
}

/***********************************************************************************************************
 * public event handlers
 ***********************************************************************************************************/

/**
 * name:	OnInfoChanged
 * class:	CPsTreeItem
 * desc:	Notifies the propertypage of changed contact information
 * params:	none
 * return:	nothing
 **/
void CPsTreeItem::OnInfoChanged()
{
	if (_hWnd)
		_pDialog->OnRefresh();
}

/**
 * name:	OnPageIconsChanged
 * class:	CPsTreeItem
 * desc:	Notifies the propertypage of changed icons in icolib
 * params:	none
 * return:	nothing
 **/
void CPsTreeItem::OnPageIconsChanged()
{
	if (_hWnd && _hInst == g_plugin.getInst()) {
		auto *pDlg = (PSPBaseDlg *)_pDialog;
		pDlg->OnIconsChanged();
	}
}

/**
 * name:	OnIconsChanged
 * class:	CPsTreeItem
 * desc:	Handles reloading icons if changed by icolib
 * params:	none
 * return:	nothing
 **/
void CPsTreeItem::OnIconsChanged(CPsTree *pTree)
{
	HICON hIcon;
	RECT rc;

	// update tree item icons
	if (pTree->ImageList() && (hIcon = IcoLib_GetIcon(IconKey())) != nullptr) {
		_iImage = (_iImage > 0)
			? ImageList_ReplaceIcon(pTree->ImageList(), _iImage, hIcon)
			: ImageList_AddIcon(pTree->ImageList(), hIcon);
		
		if (_hItem && TreeView_GetItemRect(pTree->Window(), _hItem, &rc, 0))
			InvalidateRect(pTree->Window(), &rc, TRUE);
	}
	// update pages icons
	OnPageIconsChanged();
}