/*

Miranda NG: the free IM client for Microsoft* Windows*

Copyright (�) 2012-15 Miranda NG project (http://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.

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 "..\..\core\commonheaders.h"

#define NAMEORDERCOUNT 9
static TCHAR* nameOrderDescr[ NAMEORDERCOUNT ] =
{
	LPGENT("My custom name (not movable)"),
	LPGENT("Nick"),
	LPGENT("FirstName"),
	LPGENT("E-mail"),
	LPGENT("LastName"),
	LPGENT("Username"),
	LPGENT("FirstName LastName"),
	LPGENT("LastName FirstName"),
	LPGENT("'(Unknown contact)' (not movable)")
};

BYTE nameOrder[NAMEORDERCOUNT];

static int GetDatabaseString(CONTACTINFO *ci, const char* setting, DBVARIANT* dbv)
{
	if (mir_strcmp(ci->szProto, "CList") && CallProtoService(ci->szProto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_INFOSETTINGSVC) {
		DBCONTACTGETSETTING cgs = { ci->szProto, setting, dbv };
		dbv->type = (ci->dwFlag & CNF_UNICODE) ? DBVT_WCHAR : DBVT_ASCIIZ;

		int res = CallProtoService(ci->szProto, PS_GETINFOSETTING, (WPARAM)ci->hContact, (LPARAM)&cgs);
		if (res != CALLSERVICE_NOTFOUND)
			return res;
	}

	if (ci->dwFlag & CNF_UNICODE)
		return db_get_ws(ci->hContact, ci->szProto, setting, dbv);

	return db_get_s(ci->hContact, ci->szProto, setting, dbv);
}

static int ProcessDatabaseValueDefault(CONTACTINFO *ci, const char* setting)
{
	DBVARIANT dbv;
	if (!GetDatabaseString(ci, setting, &dbv)) {
		switch (dbv.type) {
		case DBVT_ASCIIZ:
			if (!dbv.pszVal[0]) break;
		case DBVT_WCHAR:
			if (!dbv.pwszVal[0]) break;
			ci->type = CNFT_ASCIIZ;
			ci->pszVal = dbv.ptszVal;
			return 0;
		}
		db_free(&dbv);
	}

	if (db_get(ci->hContact, ci->szProto, setting, &dbv))
		return 1;

	switch (dbv.type) {
	case DBVT_BYTE:
		ci->type = CNFT_BYTE;
		ci->bVal = dbv.bVal;
		return 0;
	case DBVT_WORD:
		ci->type = CNFT_WORD;
		ci->wVal = dbv.wVal;
		return 0;
	case DBVT_DWORD:
		ci->type = CNFT_DWORD;
		ci->dVal = dbv.dVal;
		return 0;
	}

	db_free(&dbv);
	return 1;
}

static INT_PTR GetContactInfo(WPARAM, LPARAM lParam)
{
	DBVARIANT dbv;
	CONTACTINFO *ci = (CONTACTINFO*)lParam;
	if (ci == NULL) return 1;
	if (ci->szProto == NULL) ci->szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEACCOUNT, (WPARAM)ci->hContact, 0);
	if (ci->szProto == NULL) return 1;
	
	ci->type = 0;
	switch (ci->dwFlag & 0x7F) {
	case CNF_FIRSTNAME:  return ProcessDatabaseValueDefault(ci, "FirstName");
	case CNF_LASTNAME:   return ProcessDatabaseValueDefault(ci, "LastName");
	case CNF_NICK:       return ProcessDatabaseValueDefault(ci, "Nick");
	case CNF_EMAIL:      return ProcessDatabaseValueDefault(ci, "e-mail");
	case CNF_CITY:       return ProcessDatabaseValueDefault(ci, "City");
	case CNF_STATE:      return ProcessDatabaseValueDefault(ci, "State");
	case CNF_PHONE:      return ProcessDatabaseValueDefault(ci, "Phone");
	case CNF_HOMEPAGE:   return ProcessDatabaseValueDefault(ci, "Homepage");
	case CNF_ABOUT:      return ProcessDatabaseValueDefault(ci, "About");
	case CNF_AGE:        return ProcessDatabaseValueDefault(ci, "Age");
	case CNF_GENDER:     return ProcessDatabaseValueDefault(ci, "Gender");
	case CNF_FAX:        return ProcessDatabaseValueDefault(ci, "Fax");
	case CNF_CELLULAR:	return ProcessDatabaseValueDefault(ci, "Cellular");
	case CNF_BIRTHDAY:	return ProcessDatabaseValueDefault(ci, "BirthDay");
	case CNF_BIRTHMONTH:	return ProcessDatabaseValueDefault(ci, "BirthMonth");
	case CNF_BIRTHYEAR:	return ProcessDatabaseValueDefault(ci, "BirthYear");
	case CNF_STREET:		return ProcessDatabaseValueDefault(ci, "Street");
	case CNF_ZIP:			return ProcessDatabaseValueDefault(ci, "ZIP");
	case CNF_LANGUAGE1:	return ProcessDatabaseValueDefault(ci, "Language1");
	case CNF_LANGUAGE2:	return ProcessDatabaseValueDefault(ci, "Language2");
	case CNF_LANGUAGE3:	return ProcessDatabaseValueDefault(ci, "Language3");
	case CNF_CONAME:		return ProcessDatabaseValueDefault(ci, "Company");
	case CNF_CODEPT:     return ProcessDatabaseValueDefault(ci, "CompanyDepartment");
	case CNF_COPOSITION: return ProcessDatabaseValueDefault(ci, "CompanyPosition");
	case CNF_COSTREET:   return ProcessDatabaseValueDefault(ci, "CompanyStreet");
	case CNF_COCITY:     return ProcessDatabaseValueDefault(ci, "CompanyCity");
	case CNF_COSTATE:    return ProcessDatabaseValueDefault(ci, "CompanyState");
	case CNF_COZIP:      return ProcessDatabaseValueDefault(ci, "CompanyZIP");
	case CNF_COHOMEPAGE: return ProcessDatabaseValueDefault(ci, "CompanyHomepage");

	case CNF_CUSTOMNICK:
		{
			char* saveProto = ci->szProto; ci->szProto = "CList";
			if (ci->hContact != NULL && !ProcessDatabaseValueDefault(ci, "MyHandle")) {
				ci->szProto = saveProto;
				return 0;
			}
			ci->szProto = saveProto;
		}
		break;

	case CNF_COUNTRY:
	case CNF_COCOUNTRY:
		if (!GetDatabaseString(ci, (ci->dwFlag & 0x7F) == CNF_COUNTRY ? "CountryName" : "CompanyCountryName", &dbv))
			return 0;

		if (!db_get(ci->hContact, ci->szProto, (ci->dwFlag & 0x7F) == CNF_COUNTRY ? "Country" : "CompanyCountry", &dbv)) {
			if (dbv.type == DBVT_WORD) {
				int i, countryCount;
				struct CountryListEntry *countries;
				CallService(MS_UTILS_GETCOUNTRYLIST, (WPARAM)&countryCount, (LPARAM)&countries);
				for (i = 0; i < countryCount; i++) {
					if (countries[i].id != dbv.wVal) continue;

					if (ci->dwFlag & CNF_UNICODE) {
						int cbLen = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)countries[i].szName, -1, NULL, 0);
						WCHAR* buf = (WCHAR*)mir_alloc(sizeof(WCHAR)*(cbLen + 1));
						if (buf != NULL)
							MultiByteToWideChar(CP_ACP, 0, (LPCSTR)countries[i].szName, -1, buf, cbLen);
						ci->pszVal = (TCHAR*)buf;
					}
					else ci->pszVal = (TCHAR*)mir_strdup(countries[i].szName);

					ci->type = CNFT_ASCIIZ;
					db_free(&dbv);
					return 0;
				}
			}
			else return ProcessDatabaseValueDefault(ci, (ci->dwFlag & 0x7F) == CNF_COUNTRY ? "Country" : "CompanyCountry");
			db_free(&dbv);
		}
		break;

	case CNF_FIRSTLAST:
		if (!GetDatabaseString(ci, "FirstName", &dbv)) {
			DBVARIANT dbv2;
			if (!GetDatabaseString(ci, "LastName", &dbv2)) {
				ci->type = CNFT_ASCIIZ;
				if (ci->dwFlag & CNF_UNICODE) {
					size_t len = mir_wstrlen(dbv.pwszVal) + mir_wstrlen(dbv2.pwszVal) + 2;
					WCHAR* buf = (WCHAR*)mir_alloc(sizeof(WCHAR)*len);
					if (buf != NULL)
						mir_wstrcat(mir_wstrcat(mir_wstrcpy(buf, dbv.pwszVal), L" "), dbv2.pwszVal);
					ci->pszVal = (TCHAR*)buf;
				}
				else {
					size_t len = mir_strlen(dbv.pszVal) + mir_strlen(dbv2.pszVal) + 2;
					char* buf = (char*)mir_alloc(len);
					if (buf != NULL)
						mir_strcat(mir_strcat(mir_strcpy(buf, dbv.pszVal), " "), dbv2.pszVal);
					ci->pszVal = (TCHAR*)buf;
				}
				db_free(&dbv);
				db_free(&dbv2);
				return 0;
			}
			db_free(&dbv);
		}
		break;

	case CNF_UNIQUEID:
		{
			if (db_mc_isMeta(ci->hContact)) {
				TCHAR buf[40];
				_itot(ci->hContact, buf, 10);
				ci->pszVal = mir_tstrdup(buf);
				ci->type = CNFT_ASCIIZ;
				return 0;
			}

			char *uid = (char*)CallProtoService(ci->szProto, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0);
			if ((INT_PTR)uid != CALLSERVICE_NOTFOUND && uid)
				if (!ProcessDatabaseValueDefault(ci, uid))
					return 0;
		}
		break;

	case CNF_DISPLAYUID:
		{
			if (!ProcessDatabaseValueDefault(ci, "display_uid"))
				return 0;
			char *uid = (char*)CallProtoService(ci->szProto, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0);
			if ((INT_PTR)uid != CALLSERVICE_NOTFOUND && uid)
				if (!ProcessDatabaseValueDefault(ci, uid))
					return 0;

		}
		break;

	case CNF_DISPLAYNC:
	case CNF_DISPLAY:
		for (int i = 0; i < NAMEORDERCOUNT; i++) {
			switch (nameOrder[i]) {
			case 0: // custom name
				// make sure we aren't in CNF_DISPLAYNC mode
				// don't get custom name for NULL contact
				{
					char *saveProto = ci->szProto; ci->szProto = "CList";
					if (ci->hContact != NULL && (ci->dwFlag & 0x7F) == CNF_DISPLAY && !ProcessDatabaseValueDefault(ci, "MyHandle")) {
						ci->szProto = saveProto;
						return 0;
					}
					ci->szProto = saveProto;
				}
				break;
			case 1:
				if (!ProcessDatabaseValueDefault(ci, "Nick")) // nick
					return 0;
				break;
			case 2:
				if (!ProcessDatabaseValueDefault(ci, "FirstName")) // First Name
					return 0;
				break;
			case 3:
				if (!ProcessDatabaseValueDefault(ci, "e-mail")) // E-mail
					return 0;
				break;
			case 4:
				if (!ProcessDatabaseValueDefault(ci, "LastName")) // Last Name
					return 0;
				break;
			case 5: // Unique id
				{
					// protocol must define a PFLAG_UNIQUEIDSETTING
					char *uid = (char*)CallProtoService(ci->szProto, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0);
					if ((INT_PTR)uid != CALLSERVICE_NOTFOUND && uid) {
						if (!GetDatabaseString(ci, uid, &dbv)) {
							if (dbv.type == DBVT_BYTE || dbv.type == DBVT_WORD || dbv.type == DBVT_DWORD) {
								long value = (dbv.type == DBVT_BYTE) ? dbv.bVal : (dbv.type == DBVT_WORD ? dbv.wVal : dbv.dVal);
								if (ci->dwFlag & CNF_UNICODE) {
									WCHAR buf[40];
									_ltow(value, buf, 10);
									ci->pszVal = (TCHAR*)mir_wstrdup(buf);
								}
								else {
									char buf[40];
									_ltoa(value, buf, 10);
									ci->pszVal = (TCHAR*)mir_strdup(buf);
								}
								ci->type = CNFT_ASCIIZ;
								return 0;
							}
							if (dbv.type == DBVT_ASCIIZ && !(ci->dwFlag & CNF_UNICODE)) {
								ci->type = CNFT_ASCIIZ;
								ci->pszVal = dbv.ptszVal;
								return 0;
							}
							if (dbv.type == DBVT_WCHAR && (ci->dwFlag & CNF_UNICODE)) {
								ci->type = CNFT_ASCIIZ;
								ci->pszVal = dbv.ptszVal;
								return 0;
							}
						}
					}
				}
				break;
			case 6: // first + last name
			case 7: // last + first name
				if (!GetDatabaseString(ci, nameOrder[i] == 6 ? "FirstName" : "LastName", &dbv)) {
					DBVARIANT dbv2;
					if (!GetDatabaseString(ci, nameOrder[i] == 6 ? "LastName" : "FirstName", &dbv2)) {
						ci->type = CNFT_ASCIIZ;

						if (ci->dwFlag & CNF_UNICODE) {
							size_t len = mir_wstrlen(dbv.pwszVal) + mir_wstrlen(dbv2.pwszVal) + 2;
							WCHAR* buf = (WCHAR*)mir_alloc(sizeof(WCHAR)*len);
							if (buf != NULL)
								mir_wstrcat(mir_wstrcat(mir_wstrcpy(buf, dbv.pwszVal), L" "), dbv2.pwszVal);
							ci->pszVal = (TCHAR*)buf;
						}
						else {
							size_t len = mir_strlen(dbv.pszVal) + mir_strlen(dbv2.pszVal) + 2;
							char* buf = (char*)mir_alloc(len);
							if (buf != NULL)
								mir_strcat(mir_strcat(mir_strcpy(buf, dbv.pszVal), " "), dbv2.pszVal);
							ci->pszVal = (TCHAR*)buf;
						}

						db_free(&dbv);
						db_free(&dbv2);
						return 0;
					}
					db_free(&dbv);
				}
				break;

			case 8:
				if (ci->dwFlag & CNF_UNICODE)
					ci->pszVal = (TCHAR*)mir_wstrdup(TranslateW(L"'(Unknown contact)'"));
				else
					ci->pszVal = (TCHAR*)mir_strdup(Translate("'(Unknown contact)'"));
				ci->type = CNFT_ASCIIZ;
				return 0;
			}
		}
		break;

	case CNF_TIMEZONE:
		{
			HANDLE hTz = tmi.createByContact(ci->hContact, 0, TZF_KNOWNONLY);
			if (hTz) {
				LPTIME_ZONE_INFORMATION tzi = tmi.getTzi(hTz);
				int offset = tzi->Bias + tzi->StandardBias;

				char str[80];
				mir_snprintf(str, SIZEOF(str), offset ? "UTC%+d:%02d" : "UTC", offset / -60, abs(offset % 60));
				ci->pszVal = ci->dwFlag & CNF_UNICODE ? (TCHAR*)mir_a2u(str) : (TCHAR*)mir_strdup(str);
				ci->type = CNFT_ASCIIZ;
				return 0;
			}
		}
		break;

	case CNF_MYNOTES:
		char* saveProto = ci->szProto; ci->szProto = "UserInfo";
		if (!ProcessDatabaseValueDefault(ci, "MyNotes")) {
			ci->szProto = saveProto;
			return 0;
		}
		ci->szProto = saveProto;
		break;
	}

	return 1;
}

/////////////////////////////////////////////////////////////////////////////////////////
// Options dialog

class CContactOptsDlg : public CDlgBase
{
	CCtrlTreeView m_nameOrder;

public:
	CContactOptsDlg() :
		CDlgBase(hInst, IDD_OPT_CONTACT),
		m_nameOrder(this, IDC_NAMEORDER)
	{
		m_nameOrder.SetFlags(MTREE_DND);
		m_nameOrder.OnBeginDrag = Callback(this, &CContactOptsDlg::OnBeginDrag);
	}

	virtual void OnInitDialog()
	{
		TVINSERTSTRUCT tvis;
		tvis.hParent = NULL;
		tvis.hInsertAfter = TVI_LAST;
		tvis.item.mask = TVIF_TEXT | TVIF_PARAM;
		for (int i = 0; i < SIZEOF(nameOrderDescr); i++) {
			tvis.item.lParam = nameOrder[i];
			tvis.item.pszText = TranslateTS(nameOrderDescr[nameOrder[i]]);
			m_nameOrder.InsertItem(&tvis);
		}
	}

	virtual void OnApply()
	{
		TVITEMEX tvi;
		tvi.hItem = m_nameOrder.GetRoot();
		int i = 0;
		while (tvi.hItem != NULL) {
			tvi.mask = TVIF_PARAM | TVIF_HANDLE;
			m_nameOrder.GetItem(&tvi);
			nameOrder[i++] = (BYTE)tvi.lParam;
			tvi.hItem = m_nameOrder.GetNextSibling(tvi.hItem);
		}
		db_set_blob(NULL, "Contact", "NameOrder", nameOrder, SIZEOF(nameOrderDescr));
		CallService(MS_CLIST_INVALIDATEDISPLAYNAME, (WPARAM)INVALID_HANDLE_VALUE, 0);
	}

	void OnBeginDrag(CCtrlTreeView::TEventInfo *evt)
	{
		LPNMTREEVIEW pNotify = evt->nmtv;
		if (pNotify->itemNew.lParam == 0 || pNotify->itemNew.lParam == SIZEOF(nameOrderDescr) - 1)
			pNotify->hdr.code = 0; // deny dragging
	}
};

static int ContactOptInit(WPARAM wParam, LPARAM)
{
	OPTIONSDIALOGPAGE odp = { 0 };
	odp.position = -1000000000;
	odp.pszGroup = LPGEN("Contact list");
	odp.pszTitle = LPGEN("Contact names");
	odp.pDialog = new CContactOptsDlg();
	odp.flags = ODPF_BOLDGROUPS;
	Options_AddPage(wParam, &odp);
	return 0;
}

int LoadContactsModule(void)
{
	for (BYTE i = 0; i < NAMEORDERCOUNT; i++)
		nameOrder[i] = i;

	DBVARIANT dbv;
	if (!db_get(NULL, "Contact", "NameOrder", &dbv)) {
		memcpy(nameOrder, dbv.pbVal, dbv.cpbVal);
		db_free(&dbv);
	}

	CreateServiceFunction(MS_CONTACT_GETCONTACTINFO, GetContactInfo);
	HookEvent(ME_OPT_INITIALISE, ContactOptInit);
	return 0;
}