/* ========================================================================

                              Custom Status List
                              __________________

  Custom Status List plugin for Miranda-IM (www.miranda-im.org)
  Follower of Custom Status History List by HANAX
  Copyright � 2006-2008 HANAX
  Copyright � 2007-2009 jarvis
  Occasionally rewritten in 2012 by George Hazan

  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 "cslist.h"
#include "strpos.h"

CLIST_INTERFACE *pcli;
int hLangpack;
HINSTANCE g_hInst;

static LIST<CSWindow> arWindows(3, HandleKeySortT);

PLUGININFOEX pluginInfoEx =
{
	sizeof(PLUGININFOEX),
	__PLUGIN_NAME,
	PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
	__DESCRIPTION,
	__AUTHOR,
	__AUTHOREMAIL,
	__COPYRIGHT,
	__AUTHORWEB,
	UNICODE_AWARE,
	// {C8CC7414-6507-4AF6-925A-83C1D2F7BE8C}
	{0xc8cc7414, 0x6507, 0x4af6, {0x92, 0x5a, 0x83, 0xc1, 0xd2, 0xf7, 0xbe, 0x8c}}
};

// ====[ MAIN ]===============================================================

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
	g_hInst = hinstDLL;
	return TRUE;
}

// ====[ PLUGIN INFO ]========================================================

extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD)
{
	return &pluginInfoEx;
}

// ====[ LOADER ]=============================================================

static int OnDbChanged(WPARAM wparam, LPARAM lparam)
{
	HANDLE hContact = (HANDLE)wparam;
	DBCONTACTWRITESETTING *cws = ( DBCONTACTWRITESETTING* )lparam;

	// if user changes his UIN or JID on any account
	if (hContact == NULL) {
		INT_PTR szUniqueID = CallProtoService(cws->szModule, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0);
		if (szUniqueID != CALLSERVICE_NOTFOUND && !lstrcmpA(cws->szSetting, (char*)szUniqueID))
			pcli->pfnReloadProtoMenus();
	}
	return 0;
}

static int OnInitOptions(WPARAM wparam, LPARAM lparam)
{
	OPTIONSDIALOGPAGE odp = { sizeof(odp) };
	odp.position = 955000000;
	odp.hInstance = g_hInst;
	odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS);
	odp.pszTitle = MODULENAME;
	odp.pfnDlgProc = CSOptionsProc;
	odp.pszGroup = LPGEN("Status");
	odp.flags = ODPF_BOLDGROUPS;
	Options_AddPage(wparam, &odp);
	return 0;
}

static int OnCreateMenuItems(WPARAM, LPARAM)
{
	int protoCount;
	PROTOACCOUNT** pdesc;
	ProtoEnumAccounts(&protoCount, &pdesc);

	for (int i = 0; i < protoCount; i++)
		if ( ProtoServiceExists(pdesc[i]->szModuleName, PS_SETCUSTOMSTATUSEX))
			addProtoStatusMenuItem(pdesc[i]->szModuleName);

	return 0;
}

static int OnPreshutdown(WPARAM wparam, LPARAM lparam)
{
	for (int i=0; i < arWindows.getCount(); i++)
		DestroyWindow(arWindows[i]->m_handle);
	return 0;
}

extern "C" __declspec(dllexport) int Load()
{
	mir_getLP(&pluginInfoEx);
	mir_getCLI();
	
	// support for ComboBoxEx
	INITCOMMONCONTROLSEX icc;
	icc.dwSize = sizeof(icc);
	icc.dwICC = ICC_USEREX_CLASSES;
	InitCommonControlsEx(&icc);

	// init icons
	TCHAR tszFile[MAX_PATH];
	GetModuleFileName( g_hInst, tszFile, MAX_PATH );

	SKINICONDESC sid = { sizeof(sid) };
	sid.ptszDefaultFile = tszFile;
	sid.flags = SIDF_ALL_TCHAR;
	sid.cx = sid.cy = 16;
	sid.ptszSection = _T(MODULENAME);

	for (int i=0; i < SIZEOF(forms); i++) {
		char szSettingName[64];
		mir_snprintf(szSettingName, SIZEOF(szSettingName), "%s_%s", MODNAME, forms[i].pszIconIcoLib);

		sid.pszName = szSettingName;
		sid.ptszDescription = forms[i].ptszDescr;
		sid.iDefaultIndex = -forms[i].iconNoIcoLib;
		forms[i].hIcoLibItem = Skin_AddIcon(&sid);
	}

	HookEvent(ME_OPT_INITIALISE, OnInitOptions);
	HookEvent(ME_DB_CONTACT_SETTINGCHANGED, OnDbChanged);
	HookEvent(ME_CLIST_PREBUILDSTATUSMENU, OnCreateMenuItems);
	HookEvent(ME_SYSTEM_PRESHUTDOWN, OnPreshutdown);

	// if we load a plugin dynamically, it will work, otherwise fail
	OnCreateMenuItems(0, 0);
	return 0;
}

// ====[ UNLOADER ]===========================================================

extern "C" __declspec(dllexport) int Unload()
{
	return 0;
}

// ====[ FUN ]================================================================

void RegisterHotkeys(char buf[200], TCHAR* accName, int Number)
{
	HOTKEYDESC hotkey = { sizeof(hotkey) };
	hotkey.dwFlags = HKD_TCHAR;
	hotkey.pszName = buf;
	hotkey.ptszDescription = accName;
	hotkey.ptszSection = LPGENT("Custom Status List");
	hotkey.pszService = buf;
	hotkey.DefHotKey = HOTKEYCODE( HOTKEYF_CONTROL | HOTKEYF_SHIFT, '0' + Number);
	Hotkey_Register(&hotkey);
}

void SetStatus(WORD code, StatusItem* item,  char *szAccName)
{
	if ( code == IDCLOSE )
		return;

	PROTOACCOUNT* pdescr = (PROTOACCOUNT*)CallService(MS_PROTO_GETACCOUNT, 0, (LPARAM)szAccName);
	if (pdescr == NULL)
		return;

	if ( !ProtoServiceExists(szAccName, PS_SETCUSTOMSTATUSEX))
		return;

	int statusToSet;

	CUSTOM_STATUS ics = { sizeof(CUSTOM_STATUS) };
	ics.flags = CSSF_MASK_STATUS | CSSF_MASK_NAME | CSSF_MASK_MESSAGE | CSSF_TCHAR;

	if (code == IDC_CANCEL) {
		statusToSet = 0;
		ics.ptszName = _T("");
		ics.ptszMessage = _T("");
	}
	else if (code == IDOK && item != NULL) {
		statusToSet = item->m_iIcon+1;
		ics.ptszName = variables_parsedup(item->m_tszTitle, NULL, NULL);
		ics.ptszMessage = variables_parsedup(item->m_tszMessage, NULL, NULL);
	}
	else return;

	ics.status = &statusToSet;
	ProtoCallService(szAccName, PS_SETCUSTOMSTATUSEX, 0, (LPARAM)&ics);
}

INT_PTR showList(WPARAM wparam, LPARAM lparam, LPARAM param)
{
	char* szProto = (char*)param;
	for (int i=0; i < arWindows.getCount(); i++) {
		CSWindow *p = arWindows[i];
		if ( !strcmp(szProto, p->m_protoName)) {
			ShowWindow(p->m_handle, SW_SHOW);
			SetForegroundWindow(p->m_handle);
			return 1;
		}
	}

	CreateDialogParam(g_hInst, MAKEINTRESOURCE(IDD_CSLIST), NULL, CSWindowProc, (LPARAM)new CSWindow(szProto));
	return 0;
}

void addProtoStatusMenuItem(char *protoName)
{
	PROTOACCOUNT *pdescr = ProtoGetAccount(protoName);
	if (pdescr == NULL)
		return;

	HGENMENU hRoot = MO_GetProtoRootMenu(pdescr->szModuleName);
	if (hRoot == NULL)
		return;

	char buf[200];
	mir_snprintf(buf, SIZEOF(buf), "CSList/ShowList/%s", protoName);
	if ( !ServiceExists(buf))
		CreateServiceFunctionParam(buf, showList, (LPARAM)protoName);

	CLISTMENUITEM mi = { sizeof(mi) };
	mi.flags = CMIF_CHILDPOPUP | CMIF_TCHAR;
	mi.icolibItem = forms[0].hIcoLibItem;
	mi.ptszName = _T(MODULENAME);
	mi.position = 2000040000;
	mi.pszService = buf;
	mi.hParentMenu = hRoot;
	Menu_AddStatusMenuItem(&mi);

	RegisterHotkeys(buf, pdescr->tszAccountName, pdescr->iOrder);
}

///////////////////////////////////////////////////////////////////////////////

void importCustomStatuses(CSWindow* csw, int result)
{
	DBVARIANT dbv;
	char bufTitle[32], bufMessage[32], *protoName = csw->m_protoName;

	for (int i = 0; i < csw->m_statusCount; i++) {
		StatusItem* si = new StatusItem();
		si->m_iIcon = i-1;

		mir_snprintf(bufTitle, 32, "XStatus%dName", i);
		if ( !db_get_ts( NULL, protoName, bufTitle, &dbv )) {
			lstrcpy(si->m_tszTitle, dbv.ptszVal);
			db_free(&dbv);
		}
		else si->m_tszTitle[0] = 0;

		mir_snprintf(bufMessage, 32, "XStatus%dMsg", i);
		if ( !db_get_ts( NULL, protoName, bufMessage, &dbv )) {
			lstrcpy(si->m_tszMessage, dbv.ptszVal);
			db_free(&dbv);
		}
		else si->m_tszMessage[0] = 0;

		if (si->m_tszTitle[0] || si->m_tszMessage[0]) {
			csw->m_itemslist->m_list->add(si);
			csw->m_bSomethingChanged = TRUE;
		}
		else delete si;

		if (result == IDYES) {
			db_unset(NULL, protoName, bufTitle);
			db_unset(NULL, protoName, bufMessage);
		}
	}
	csw->m_listview->reinitItems(csw->m_itemslist->m_list->getListHead());
}

///////////////////////////////////////////////////////////////////////////////
// CSWindow class

CSWindow::CSWindow(char *protoName)
{
	m_protoName = protoName;
	m_handle = NULL;
	m_bExtraIcons = getByte( "AllowExtraIcons", DEFAULT_ALLOW_EXTRA_ICONS );
	m_itemslist = new CSItemsList(m_protoName);
	m_listview = NULL;
	m_addModifyDlg = NULL;
	m_bSomethingChanged = FALSE;
	m_filterString = NULL;
}

void __fastcall SAFE_FREE(void** p)
{
	if (*p)
	{
		free(*p);
		*p = NULL;
	}
}

CSWindow::~CSWindow()
{
	delete m_itemslist;
	SAFE_FREE(( void** )&m_filterString );
}

void CSWindow::initIcons()
{
	PROTOACCOUNT *pdescr = ProtoGetAccount(m_protoName);
	if (pdescr == NULL)
		return;

	char *szUniqueID = (char*)CallProtoService(m_protoName, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0);
	if (szUniqueID == NULL)
		return;

	DBVARIANT dbv;
	if ( db_get(NULL, pdescr->szModuleName, szUniqueID, &dbv))
		return;
	db_free(&dbv);

	WPARAM iNumStatuses = 0;
	CUSTOM_STATUS cs = { sizeof(cs) };
	cs.flags = CSSF_STATUSES_COUNT;
	cs.wParam = &iNumStatuses;
	if ( CallProtoService(pdescr->szModuleName, PS_GETCUSTOMSTATUSEX, 0, (LPARAM)&cs) != 0)
		return;

	m_statusCount = (int)iNumStatuses;
	if (NULL == (m_icons = ImageList_Create(16, 16, ILC_COLOR32 | ILC_MASK, m_statusCount, 0)))
		return;

	for (int i=1; i <= m_statusCount; i++) {
		HICON hIcon = (HICON)CallProtoService(pdescr->szModuleName, PS_GETCUSTOMSTATUSICON, i, 0);
		if (hIcon) {
			ImageList_AddIcon(m_icons, hIcon);
			DestroyIcon(hIcon);
		}
	}
}

void CSWindow::deinitIcons()
{
	ImageList_Destroy( m_icons );
}

void CSWindow::initButtons()
{
	for (int i = 0; i < SIZEOF(forms); i++) {
		if ( forms[i].idc < 0 )
			continue;

		SendDlgItemMessage( m_handle, forms[i].idc, BM_SETIMAGE, IMAGE_ICON, (LPARAM)Skin_GetIconByHandle(forms[i].hIcoLibItem));
		SendDlgItemMessage( m_handle, forms[i].idc, BUTTONSETASFLATBTN, TRUE, 0 ); //maybe set as BUTTONSETDEFAULT?
		SendDlgItemMessage( m_handle, forms[i].idc, BUTTONADDTOOLTIP, (WPARAM )TranslateTS(forms[i].ptszTitle), BATF_TCHAR );
	}
}

void CSWindow::loadWindowPosition()
{
	if (getByte("RememberWindowPosition", DEFAULT_REMEMBER_WINDOW_POSITION))
		Utils_RestoreWindowPosition(m_handle, NULL, MODNAME, "Position");
}

void CSWindow::toggleEmptyListMessage()
{
	HWND hwnd = GetDlgItem( m_handle, IDC_NO_ITEMS );
	ShowWindow( hwnd, ( ListView_GetItemCount( m_listview->m_handle ) > 0 ) ? FALSE : TRUE );
	SetForegroundWindow( hwnd );
	hwnd = GetDlgItem( m_handle, IDC_ADD_SAMPLE );
	ShowWindow( hwnd, ( ListView_GetItemCount( m_listview->m_handle ) > 0 ) ? FALSE : TRUE );
	SetForegroundWindow( hwnd );
}

BOOL CSWindow::itemPassedFilter( ListItem< StatusItem >* li )
{
	TCHAR filter[MAX_PATH];
	GetDlgItemText( m_handle, IDC_FILTER_FIELD, filter, MAX_PATH );

	if ( lstrlen( filter ))
	{
		TCHAR title[EXTRASTATUS_TITLE_LIMIT], message[EXTRASTATUS_MESSAGE_LIMIT];
		lstrcpy( title, li->m_item->m_tszTitle ); lstrcpy( message, li->m_item->m_tszMessage );
		if ( strpos( _tcslwr( title ), _tcslwr( filter )) == -1 )
			if ( strpos( _tcslwr( message ), _tcslwr( filter )) == -1 )
				return FALSE;
	}

	return TRUE;
}

void CSWindow::toggleFilter()
{
	HWND hFilter = GetDlgItem( m_handle, IDC_FILTER_FIELD );
	BOOL isEnabled = !IsWindowEnabled( hFilter ) ? TRUE : FALSE; // ! = + isEnabled = !isEnabled in one
	EnableWindow( hFilter, isEnabled );
	ShowWindow( hFilter, isEnabled );
	CheckDlgButton( m_handle, IDC_FILTER, isEnabled );
	SetForegroundWindow( hFilter );
	if ( isEnabled )
		SetFocus( hFilter );
	else
	{
		TCHAR filterText[255];
		GetDlgItemText(m_handle, IDC_FILTER_FIELD, filterText, SIZEOF(filterText));
		if (filterText[0] != 0)
			SetDlgItemText( m_handle, IDC_FILTER_FIELD, TEXT( ""));
	}
}

BOOL CSWindow::toggleButtons()
{
	int selection = ListView_GetSelectedItemMacro( m_listview->m_handle );
	BOOL validSelection = ( selection >= 0 && ( unsigned int )selection < m_itemslist->m_list->getCount()) ? TRUE : FALSE;
	BOOL filterEnabled = IsWindowVisible( GetDlgItem( m_handle, IDC_FILTER_FIELD ));
	BOOL somethingChanged = m_bSomethingChanged;

	EnableWindow( GetDlgItem( m_handle, IDC_ADD       ), !filterEnabled );
	EnableWindow( GetDlgItem( m_handle, IDC_MODIFY    ), validSelection && !filterEnabled );
	EnableWindow( GetDlgItem( m_handle, IDC_REMOVE    ), validSelection && !filterEnabled );
	EnableWindow( GetDlgItem( m_handle, IDC_FAVOURITE ), validSelection && !filterEnabled );
	EnableWindow( GetDlgItem( m_handle, IDC_UNDO      ), somethingChanged && !filterEnabled );
	EnableWindow( GetDlgItem( m_handle, IDC_IMPORT    ), !filterEnabled );
	EnableWindow( GetDlgItem( m_handle, IDOK          ), validSelection );

	return validSelection;
}

///////////////////////////////////////////////////////////////////////////////
// CSAMWindow class - add form window

CSAMWindow::CSAMWindow( WORD action, CSWindow* parent )
{
	m_action = action;
	m_parent = parent;
	m_bChanged = FALSE;
	m_hCombo = m_hMessage = NULL;

	if ( m_action == IDC_ADD )
		m_item = new StatusItem();
	else
		m_item = new StatusItem( *m_parent->m_itemslist->m_list->get( m_parent->m_listview->getPositionInList()) );
}

CSAMWindow::~CSAMWindow()
{
	if ( !m_bChanged )
		delete m_item;
}

void CSAMWindow::exec()
{
	DialogBoxParam( g_hInst, MAKEINTRESOURCE( IDD_ADDMODIFY ), NULL, CSAMWindowProc, (LPARAM)this );
}


void CSAMWindow::setCombo()
{
	PROTOACCOUNT *pdescr = ProtoGetAccount(m_parent->m_protoName);
	if (pdescr == NULL)
		return;

	char *szUniqueID = (char*)CallProtoService(pdescr->szModuleName, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0);
	if (szUniqueID == NULL)
		return;

	DBVARIANT dbv;
	if ( db_get(NULL, pdescr->szModuleName, szUniqueID, &dbv))
		return;
	db_free(&dbv);

	WPARAM iStatus;
	TCHAR tszName[100];
	CUSTOM_STATUS cs = { sizeof(cs) };
	cs.flags = CSSF_MASK_NAME | CSSF_DEFAULT_NAME | CSSF_TCHAR;
	cs.ptszName = tszName;
	cs.wParam = &iStatus;

	SendMessage( m_hCombo, CBEM_SETIMAGELIST, 0, (LPARAM)m_parent->m_icons);
	for (int i=1; i <= m_parent->m_statusCount; i++) {
		iStatus = i;
		if ( CallProtoService(pdescr->szModuleName, PS_GETCUSTOMSTATUSEX, 0, (LPARAM)&cs) != 0)
			continue;

		COMBOBOXEXITEM cbi = { 0 };
		cbi.mask = CBEIF_TEXT | CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
		cbi.iItem = -1;
		cbi.iImage = cbi.iSelectedImage = i-1;
		cbi.pszText = TranslateTS(tszName);
		SendMessage( m_hCombo, CBEM_INSERTITEM, 0, (LPARAM)&cbi );
	}
	SendMessage( m_hCombo, CB_SETCURSEL, 0, 0 ); // first 0 sets selection to top
}

void CSAMWindow::fillDialog()
{
	if (m_action == IDC_ADD) {
		SetWindowText(m_handle, TranslateT("Add new item"));
		SetDlgItemText(m_handle, IDOK, TranslateT("Add"));
	}
	else {
		SetWindowText(m_handle, TranslateT("Modify item"));
		SetDlgItemText(m_handle, IDOK, TranslateT("Modify"));
	}

	SendMessage(m_hCombo, CB_SETCURSEL, m_item->m_iIcon, 0);
	SetDlgItemText(m_handle, IDC_MESSAGE, m_item->m_tszMessage);
}

void CSAMWindow::checkFieldLimit( WORD action, WORD item )
{
	BOOL type = ( item == IDC_MESSAGE ) ? TRUE : FALSE;
	unsigned int limit = type ? EXTRASTATUS_MESSAGE_LIMIT : EXTRASTATUS_TITLE_LIMIT;

	if ( action == EN_CHANGE )
	{
		TCHAR* ptszInputText = (TCHAR*)mir_alloc((limit + 8) * sizeof(TCHAR));

		GetDlgItemText( m_handle, item, ptszInputText, limit + 8 );

		if ( _tcslen( ptszInputText ) > limit )
		{
#if ( WINVER >= 0x501 )
			TCHAR tszPopupTip[MAX_PATH];
			EDITBALLOONTIP ebt = { 0 };
			ebt.cbStruct = sizeof(ebt);
			ebt.pszTitle = TranslateT("Warning");
			mir_sntprintf(tszPopupTip, SIZEOF(tszPopupTip), TranslateT("This field doesn't accept string longer than %d characters. The string will be truncated."), limit);
			ebt.pszText = tszPopupTip;
			ebt.ttiIcon = TTI_WARNING;
			SendMessage( GetDlgItem( m_handle, item ), EM_SHOWBALLOONTIP, 0, (LPARAM)&ebt );
#endif
			TCHAR* ptszOutputText = (TCHAR*)mir_alloc((limit + 1) * sizeof(TCHAR));
			GetDlgItemText( m_handle, item, ptszOutputText, limit + 1 );
			SetDlgItemText( m_handle, item, ptszOutputText );
			mir_free( ptszOutputText );
		}
		mir_free( ptszInputText );
	}
}

void CSAMWindow::checkItemValidity()
{
	COMBOBOXEXITEM cbi = { 0 };
	cbi.mask = CBEIF_IMAGE;
	cbi.iItem = SendDlgItemMessage(m_handle, IDC_COMBO, CB_GETCURSEL, 0, 0);
	SendDlgItemMessage( m_handle, IDC_COMBO, CBEM_GETITEM, 0, (LPARAM)&cbi );

	if (m_item->m_iIcon != cbi.iImage)
		m_item->m_iIcon = cbi.iImage, m_bChanged = TRUE;

	TCHAR tszInputMessage[EXTRASTATUS_MESSAGE_LIMIT];

	GetDlgItemText( m_handle, IDC_MESSAGE, tszInputMessage, EXTRASTATUS_MESSAGE_LIMIT );

	PROTOACCOUNT* pdescr = (PROTOACCOUNT*)CallService(MS_PROTO_GETACCOUNT, 0, (LPARAM)m_parent->m_protoName);
	if (pdescr == NULL)
		return;

	WPARAM i = SendMessage(m_hCombo, CB_GETCURSEL, 0, 0)+1;
	TCHAR tszTitle[100];

	CUSTOM_STATUS cs = { sizeof(cs) };
	cs.flags = CSSF_MASK_NAME | CSSF_DEFAULT_NAME | CSSF_TCHAR;
	cs.ptszName = tszTitle;
	cs.wParam = &i;
	if ( CallProtoService(pdescr->szModuleName, PS_GETCUSTOMSTATUSEX, 0, (LPARAM)&cs) == 0)
		lstrcpyn(m_item->m_tszTitle, TranslateTS(tszTitle), SIZEOF(m_item->m_tszTitle));

	if ( lstrcmp(m_item->m_tszMessage, tszInputMessage))
		lstrcpy( m_item->m_tszMessage, tszInputMessage), m_bChanged = true;
}

CSListView::CSListView(HWND hwnd, CSWindow* parent)
{
	m_handle = hwnd;
	m_parent = parent;

#if (_WIN32_WINNT >= 0x501)
	LVGROUP lg = { 0 };
	lg.cbSize = sizeof(LVGROUP);
	lg.mask = LVGF_HEADER | LVGF_GROUPID;

	lg.pszHeader = TranslateT("Favorites");
	lg.iGroupId = 0;
	ListView_InsertGroup( m_handle, -1, &lg );

	lg.pszHeader = TranslateT("Regular statuses");
	lg.iGroupId = 1;
	ListView_InsertGroup( m_handle, -1, &lg );
	ListView_EnableGroupView( m_handle, TRUE );
#endif

	LVCOLUMN lvc = { 0 };
	lvc.mask = LVCF_TEXT | LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM;
	lvc.fmt = LVCFMT_LEFT;
	lvc.cx = 0x00;
	lvc.pszText = TEXT( "");
	lvc.cx = 0x16;
	SendMessage( m_handle, LVM_INSERTCOLUMN, 0, (LPARAM)&lvc );
	lvc.pszText = TranslateT("Title");
	lvc.cx = 0x64;
	SendMessage( m_handle, LVM_INSERTCOLUMN, 1, (LPARAM)&lvc );
	lvc.pszText = TranslateT("Message");
	lvc.cx = 0xa8;
	SendMessage( m_handle, LVM_INSERTCOLUMN, 2, (LPARAM)&lvc );

#if (_WIN32_IE >= 0x0400)
	ListView_SetExtendedListViewStyleEx( m_handle, 0, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP | LVS_EX_INFOTIP );
#endif
	ListView_SetImageList(m_handle, m_parent->m_icons, LVSIL_SMALL);
}

void CSListView::addItem( StatusItem* item, int itemNumber )
{
	LVITEM lvi = { 0 };
	lvi.mask = LVIF_IMAGE | LVIF_GROUPID | LVIF_PARAM;
	lvi.cchTextMax = 256;
	lvi.iItem = ListView_GetItemCount( m_handle );
	lvi.lParam = itemNumber;
	lvi.iGroupId = item->m_bFavourite ? 0 : 1;

	// first column
	lvi.iSubItem = 0;
	lvi.iImage = item->m_iIcon; // use selected xicon
	SendMessage( m_handle, LVM_INSERTITEM, 0, (LPARAM)&lvi );

	// second column
	lvi.mask = LVIF_TEXT;
	lvi.iSubItem = 1;
	lvi.pszText = item->m_tszTitle;
	SendMessage( m_handle, LVM_SETITEM, 0, (LPARAM)&lvi );

	// third column
	lvi.iSubItem = 2;
	lvi.pszText = item->m_tszMessage;
	SendMessage( m_handle, LVM_SETITEM, 0, (LPARAM)&lvi );
}

void CSListView::initItems( ListItem< StatusItem >* items )
{
	ListItem< StatusItem >* help = items;
	for ( int i = 0; help != NULL; help = help->m_next, i++ )
		if (m_parent->itemPassedFilter( help ))
			addItem( help->m_item, i );
}

void CSListView::reinitItems( ListItem< StatusItem >* items )
{
	EnableWindow(m_handle, FALSE);
	removeItems();
	initItems(items);
	EnableWindow(m_handle, TRUE);
}

void CSListView::removeItems()
{
	ListView_DeleteAllItems( m_handle );
}

int CSListView::getPositionInList()
{
	LVITEM lvi = { 0 };
	lvi.iItem = ListView_GetSelectedItemMacro(m_parent->m_listview->m_handle);
	lvi.iSubItem = 0;
	lvi.mask = LVIF_PARAM;
	ListView_GetItem(m_parent->m_listview->m_handle, &lvi);
	return lvi.lParam;
}

void CSListView::setFullFocusedSelection( int selection )
{
	ListView_SetItemState(  m_handle, -1, 0,     LVIS_SELECTED );
	ListView_EnsureVisible( m_handle, selection, FALSE );
	ListView_SetItemState(  m_handle, selection, LVIS_SELECTED, LVIS_SELECTED );
	ListView_SetItemState(  m_handle, selection, LVIS_FOCUSED , LVIS_FOCUSED  );
	SetFocus( m_handle );
}

// ====[ LIST MANAGEMENT ]====================================================

CSItemsList::CSItemsList(char *protoName)
{
	m_list = new List< StatusItem >(compareItems);
	loadItems(protoName);
}

CSItemsList::~CSItemsList()
{
	delete m_list;
}

int CSItemsList::compareItems( const StatusItem* p1, const StatusItem* p2 )
{
	int favRes = 0, icoRes = 0, ttlRes = 0, msgRes = 0;

	if ( p1->m_bFavourite < p2->m_bFavourite )
	    favRes = 1;
	else if ( p1->m_bFavourite > p2->m_bFavourite )
	    favRes = -1;

	int result;
	if ( p1->m_iIcon > p2->m_iIcon )
		icoRes = 1;
	else if ( p1->m_iIcon < p2->m_iIcon )
		icoRes = -1;

	result = lstrcmp(p1->m_tszTitle, p2->m_tszTitle);
	ttlRes = result;

	result = lstrcmp(p1->m_tszMessage, p2->m_tszMessage);
	msgRes = result;

	if ( !icoRes && !ttlRes && !msgRes)
		return 0;

	if (favRes != 0)
		return favRes;
	if (icoRes != 0)
		return icoRes;
	if (ttlRes != 0)
		return ttlRes;
	if (msgRes != 0)
		return msgRes;

	return 0;
}

void CSItemsList::loadItems(char *protoName)
{
	char dbSetting[32];
	mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_ItemsCount", protoName);
	unsigned int itemsCount = getWord(dbSetting, DEFAULT_ITEMS_COUNT);

	for (unsigned int i = 1; i <= itemsCount; i++) {
		StatusItem* item = new StatusItem();
		DBVARIANT dbv;
		mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_Item%dIcon", protoName, i);
		item->m_iIcon = getByte(dbSetting, DEFAULT_ITEM_ICON);

		mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_Item%dTitle", protoName, i);
		if ( !getTString(dbSetting, &dbv)) {
			lstrcpy(item->m_tszTitle, dbv.ptszVal);
			db_free(&dbv);
		}
		else item->m_tszTitle[0] = 0;

		mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_Item%dMessage", protoName, i);
		if ( !getTString(dbSetting, &dbv)) {
			lstrcpy(item->m_tszMessage, dbv.ptszVal);
			db_free(&dbv);
		}
		else item->m_tszMessage[0] = 0;

		mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_Item%dFavourite", protoName, i);
		item->m_bFavourite = (BOOL)getByte(dbSetting, DEFAULT_ITEM_IS_FAVOURITE);
		
		m_list->add(item);
	}
}


void CSItemsList::saveItems(char *protoName)
{
	unsigned int i;
	char dbSetting[32];
	mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_ItemsCount", protoName);
	unsigned int oldItemsCount = getWord(dbSetting, DEFAULT_ITEMS_COUNT);

	for (i = 1; i <= m_list->getCount(); i++)
	{
		StatusItem* item = m_list->get( i - 1 );
		mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_Item%dIcon", protoName, i);
		setByte(dbSetting, item->m_iIcon);
		mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_Item%dTitle", protoName, i);
		setTString(dbSetting, item->m_tszTitle);
		mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_Item%dMessage", protoName, i);
		setTString(dbSetting, item->m_tszMessage);
		mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_Item%dFavourite", protoName, i);
		setByte(dbSetting, item->m_bFavourite);
	}

	mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_ItemsCount", protoName);
	setWord(dbSetting, m_list->getCount());

	for ( ; i <= oldItemsCount; i++)
	{
		mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_Item%dIcon", protoName, i);
		deleteSetting(dbSetting);
		mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_Item%dTitle", protoName, i);
		deleteSetting(dbSetting);
		mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_Item%dMessage", protoName, i);
		deleteSetting(dbSetting);
		mir_snprintf(dbSetting, SIZEOF(dbSetting), "%s_Item%dFavourite", protoName, i);
		deleteSetting(dbSetting);
	}
}


// ====[ PROCEDURES ]=========================================================

INT_PTR CALLBACK CSWindowProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam )
{
	CSWindow* csw = ( CSWindow* )GetWindowLongPtr( hwnd, GWLP_USERDATA );

	switch ( message ) {
	case WM_INITDIALOG:
		TranslateDialogDefault(hwnd);

		csw = (CSWindow*)lparam;
		arWindows.insert(csw);
		SetWindowLongPtr(hwnd, GWLP_USERDATA, lparam);

		csw->m_handle = hwnd;
		csw->initIcons();
		csw->initButtons();
		csw->m_listview = new CSListView( GetDlgItem( hwnd, IDC_CSLIST ), csw);
		csw->m_listview->initItems(csw->m_itemslist->m_list->getListHead());
		csw->toggleButtons();
		csw->toggleEmptyListMessage();
		csw->loadWindowPosition();
		SetWindowText(hwnd, TranslateT(MODULENAME));
		return TRUE;

	case WM_COMMAND:
		switch ( LOWORD( wparam )) {
		case IDC_MODIFY:
		case IDC_ADD:
			csw->m_addModifyDlg = new CSAMWindow( LOWORD( wparam ), csw );
			csw->m_addModifyDlg->exec();
			if ( csw->m_addModifyDlg->m_bChanged ) {
				if ( LOWORD( wparam ) == IDC_MODIFY )
					csw->m_itemslist->m_list->remove( csw->m_listview->getPositionInList());

				int selection = csw->m_itemslist->m_list->add( csw->m_addModifyDlg->m_item );
				csw->m_bSomethingChanged = TRUE;
				csw->m_listview->reinitItems( csw->m_itemslist->m_list->getListHead());
				csw->m_listview->setFullFocusedSelection( selection );
				csw->toggleButtons();
				csw->toggleEmptyListMessage();
			}
			delete csw->m_addModifyDlg;
			break;

		case IDC_REMOVE:
			if ( getByte( "ConfirmDeletion", DEFAULT_PLUGIN_CONFIRM_ITEMS_DELETION ))
				if ( MessageBox( hwnd, TranslateT("Do you really want to delete selected item?"), TranslateT(MODULENAME), MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION ) == IDNO )
					break;

			csw->m_itemslist->m_list->remove( csw->m_listview->getPositionInList());
			csw->m_bSomethingChanged = TRUE;
			csw->m_listview->reinitItems( csw->m_itemslist->m_list->getListHead());
			csw->toggleButtons();
			csw->toggleEmptyListMessage();
			break;

		case IDC_FAVOURITE:
			{
				int selection = csw->m_listview->getPositionInList();
				StatusItem* f = new StatusItem( *csw->m_itemslist->m_list->get( selection ));
				f->m_bFavourite = ! f->m_bFavourite;
				csw->m_itemslist->m_list->remove( selection );
				selection = csw->m_itemslist->m_list->add( f );
				csw->m_bSomethingChanged = TRUE;
				csw->m_listview->reinitItems( csw->m_itemslist->m_list->getListHead());
				csw->m_listview->setFullFocusedSelection( selection );
				csw->toggleButtons();
			}
			break;

		case IDC_UNDO:
			csw->m_itemslist->m_list->destroy();
			csw->m_itemslist->loadItems(csw->m_protoName);
			csw->m_bSomethingChanged = FALSE;
			csw->m_listview->reinitItems( csw->m_itemslist->m_list->getListHead());
			csw->toggleButtons();
			csw->toggleEmptyListMessage();
			break;

		case IDC_IMPORT:
			{
				int result = getByte( "DeleteAfterImport", DEFAULT_PLUGIN_DELETE_AFTER_IMPORT );
				if (result == TRUE)
					result = IDYES;
				else {
					result = MessageBox( hwnd, 
						TranslateT("Do you want old database entries to be deleted after Import?"),
					   TranslateT(MODULENAME), MB_YESNOCANCEL | MB_DEFBUTTON2 | MB_ICONQUESTION );
					if ( result == IDCANCEL )
						break;
				}

				importCustomStatuses(csw, result);
				csw->m_bSomethingChanged = TRUE;
				csw->toggleButtons();
				csw->toggleEmptyListMessage();
			}
			break;

		case IDC_FILTER:
			csw->toggleFilter();
			csw->toggleButtons();
			break;

		case IDC_FILTER_FIELD:
			if ( HIWORD( wparam ) == EN_CHANGE )
				csw->m_listview->reinitItems( csw->m_itemslist->m_list->getListHead());
			break;

		case IDCLOSE:    // close and save, no custom status
		case IDCANCEL:   // close and save, no custom status
		case IDC_CANCEL:   // close and save, cancel custom status
		case IDOK:       // close and save, set selected custom status
			if ( LOWORD( wparam ) == IDOK && csw->toggleButtons())
				SetStatus(IDOK, csw->m_itemslist->m_list->get(csw->m_listview->getPositionInList()), csw->m_protoName);
			if ( LOWORD( wparam ) == IDC_CANCEL )
				SetStatus(IDC_CANCEL, NULL, csw->m_protoName);
			if ( csw->m_bSomethingChanged )
				if (csw->m_itemslist!=NULL)
					csw->m_itemslist->saveItems(csw->m_protoName);
			csw->saveWindowPosition(csw->m_handle);
			EndDialog(hwnd, FALSE);
			break;
		}
		return FALSE;

	case WM_NOTIFY:
		if ( wparam == IDC_CSLIST ) {
			NMHDR* pnmh = ( NMHDR* )lparam;
			switch ( pnmh->code ) {
			case NM_DBLCLK:
				PostMessage( hwnd, WM_COMMAND, IDOK, 0L );
				break;

			case LVN_ITEMCHANGED:
			case NM_CLICK:
				csw->toggleButtons();
				break;
			}
		}
		return FALSE;

	case WM_CTLCOLORSTATIC:
		SetTextColor(( HDC )wparam, RGB( 174, 174, 174 ));
		if (((HWND)lparam == GetDlgItem(hwnd, IDC_NO_ITEMS)) || ((HWND)lparam == GetDlgItem(hwnd, IDC_ADD_SAMPLE)))
			return ( BOOL )GetStockObject( WHITE_BRUSH );
		return FALSE;

	case WM_DESTROY:
		SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
		arWindows.remove(csw);
		delete csw->m_listview;
		csw->deinitIcons();
		delete csw;
		break;
	}
	return FALSE;
}

INT_PTR CALLBACK CSAMWindowProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam )
{
	CSAMWindow* csamw = ( CSAMWindow* )GetWindowLongPtr( hwnd, GWLP_USERDATA );

	switch ( message ) {
	case WM_INITDIALOG:
		csamw = ( CSAMWindow* )lparam;
		SetWindowLongPtr( hwnd, GWLP_USERDATA, lparam );
		csamw->m_handle = hwnd;
		EnableWindow( csamw->m_parent->m_handle, FALSE );
		csamw->m_hCombo = GetDlgItem( hwnd, IDC_COMBO );
		csamw->m_hMessage = GetDlgItem( hwnd, IDC_MESSAGE );
		csamw->setCombo();
		csamw->fillDialog();
		TranslateDialogDefault(hwnd);
		break;

	case WM_COMMAND:
		switch ( LOWORD( wparam )) {
		case IDC_MESSAGE:
			csamw->checkFieldLimit( HIWORD( wparam ), LOWORD( wparam ));
			break;

		case IDOK:
			csamw->checkItemValidity();

		case IDCANCEL:
			EnableWindow(csamw->m_parent->m_handle, TRUE);
			EndDialog(hwnd, LOWORD(wparam));
			break;
		}
		break;
	}
	return FALSE;
}


INT_PTR CALLBACK CSOptionsProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam )
{
	switch ( message ) {
 	case WM_INITDIALOG:
		TranslateDialogDefault(hwnd);
		CheckDlgButton( hwnd, IDC_CONFIRM_DELETION,
						getByte( "ConfirmDeletion", DEFAULT_PLUGIN_CONFIRM_ITEMS_DELETION ) ?
						BST_CHECKED : BST_UNCHECKED );

		CheckDlgButton( hwnd, IDC_DELETE_AFTER_IMPORT,
						getByte( "DeleteAfterImport", DEFAULT_PLUGIN_DELETE_AFTER_IMPORT ) ?
						BST_CHECKED : BST_UNCHECKED );

		CheckDlgButton( hwnd, IDC_REMEMBER_POSITION,
			               getByte( "RememberWindowPosition", DEFAULT_REMEMBER_WINDOW_POSITION ) ?
			               BST_CHECKED : BST_UNCHECKED );
		return TRUE;

	case WM_NOTIFY:
		switch ((( LPNMHDR )lparam )->code ) {
		case PSN_APPLY:
			setByte( "ConfirmDeletion", IsDlgButtonChecked( hwnd, IDC_CONFIRM_DELETION ) ? 1 : 0 );
			setByte( "DeleteAfterImport", IsDlgButtonChecked( hwnd, IDC_DELETE_AFTER_IMPORT ) ? 1 : 0 );
			setByte( "RememberWindowPosition", IsDlgButtonChecked( hwnd, IDC_REMEMBER_POSITION ) ? 1 : 0 );

			pcli->pfnReloadProtoMenus();
			break;
		}
		return TRUE;
	}
	return FALSE;
}