/*
 * Copyright 2005 - 2016 Zarafa and its licensors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <kopano/platform.h>
#include <new>
#include "ZCABContainer.h"
#include "ZCMAPIProp.h"
#include <kopano/Trace.h>

#include <mapiutil.h>

#include <kopano/ECMemTable.h>
#include <kopano/ECGuid.h>
#include <kopano/ECInterfaceDefs.h>
#include <kopano/ECDebug.h>
#include <kopano/CommonUtil.h>
#include <kopano/mapiext.h>
#include <kopano/mapiguidext.h>
#include <kopano/memory.hpp>
#include <kopano/namedprops.h>
#include <kopano/charset/convert.h>
#include <kopano/mapi_ptr.h>
#include <kopano/ECGetText.h>
#include <kopano/EMSAbTag.h>
#include <kopano/ECRestriction.h>

#include <iostream>
#include <kopano/Util.h>
#include <kopano/stringutil.h>
using namespace std;
using namespace KCHL;

ZCABContainer::ZCABContainer(std::vector<zcabFolderEntry> *lpFolders,
    IMAPIFolder *lpContacts, LPMAPISUP lpMAPISup, void *lpProvider,
    const char *szClassName) :
	ECUnknown(szClassName), m_lpFolders(lpFolders),
	m_lpContactFolder(lpContacts), m_lpMAPISup(lpMAPISup),
	m_lpProvider(lpProvider)
{
	assert(!(lpFolders != NULL && lpContacts != NULL));
	if (m_lpMAPISup)
		m_lpMAPISup->AddRef();
	if (m_lpContactFolder)
		m_lpContactFolder->AddRef();
}

ZCABContainer::~ZCABContainer()
{
	if (m_lpMAPISup)
		m_lpMAPISup->Release();
	if (m_lpContactFolder)
		m_lpContactFolder->Release();
	if (m_lpDistList)
		m_lpDistList->Release();
}

HRESULT	ZCABContainer::QueryInterface(REFIID refiid, void **lppInterface)
{
	if (m_lpDistList == NULL)
		REGISTER_INTERFACE2(ZCABContainer, this);
	else
		REGISTER_INTERFACE(IID_ZCDistList, this);
	REGISTER_INTERFACE2(ECUnknown, this);

	if (m_lpDistList == NULL)
		REGISTER_INTERFACE2(IABContainer, &this->m_xABContainer);
	else
		REGISTER_INTERFACE(IID_IDistList, &this->m_xABContainer);
	REGISTER_INTERFACE2(IMAPIProp, &this->m_xABContainer);
	REGISTER_INTERFACE2(IUnknown, &this->m_xABContainer);
	return MAPI_E_INTERFACE_NOT_SUPPORTED;
}

/** 
 * Create a ZCABContainer as either the top level (lpFolders is set) or
 * as a subfolder (lpContacts is set).
 * 
 * @param[in] lpFolders Only the top container has the list to the wanted Kopano Contacts Folders, NULL otherwise.
 * @param[in] lpContacts Create this object as wrapper for the lpContacts folder, NULL if 
 * @param[in] lpMAPISup 
 * @param[in] lpProvider 
 * @param[out] lppABContainer The newly created ZCABContainer class
 * 
 * @return 
 */
HRESULT	ZCABContainer::Create(std::vector<zcabFolderEntry> *lpFolders, IMAPIFolder *lpContacts, LPMAPISUP lpMAPISup, void* lpProvider, ZCABContainer **lppABContainer)
{
	auto lpABContainer = new(std::nothrow) ZCABContainer(lpFolders, lpContacts, lpMAPISup, lpProvider, "IABContainer");
	if (lpABContainer == nullptr)
		return MAPI_E_NOT_ENOUGH_MEMORY;
	auto ret = lpABContainer->QueryInterface(IID_ZCABContainer,
	           reinterpret_cast<void **>(lppABContainer));
	if (ret != hrSuccess)
		delete lpABContainer;
	return ret;
}

HRESULT	ZCABContainer::Create(IMessage *lpContact, ULONG cbEntryID, LPENTRYID lpEntryID, LPMAPISUP lpMAPISup, ZCABContainer **lppABContainer)
{
	HRESULT hr = hrSuccess;
	object_ptr<ZCMAPIProp> lpDistList;
	auto lpABContainer = new(std::nothrow) ZCABContainer(NULL, NULL, lpMAPISup, NULL, "IABContainer");
	if (lpABContainer == nullptr) {
		hr = MAPI_E_NOT_ENOUGH_MEMORY;
		goto exit;
	}

	hr = ZCMAPIProp::Create(lpContact, cbEntryID, lpEntryID, &~lpDistList);
	if (hr != hrSuccess)
		goto exit;

	hr = lpDistList->QueryInterface(IID_IMAPIProp, (void **)&lpABContainer->m_lpDistList);
	if (hr != hrSuccess)
		goto exit;

	hr = lpABContainer->QueryInterface(IID_ZCDistList, (void **)lppABContainer);

exit:
	if (hr != hrSuccess)
		delete lpABContainer;
	return hr;
}

// IMAPIContainer
HRESULT ZCABContainer::MakeWrappedEntryID(ULONG cbEntryID, LPENTRYID lpEntryID, ULONG ulObjType, ULONG ulOffset, ULONG *lpcbEntryID, LPENTRYID *lppEntryID)
{
	cabEntryID *lpWrapped = NULL;
	ULONG cbWrapped = CbNewCABENTRYID(cbEntryID);
	HRESULT hr = MAPIAllocateBuffer(cbWrapped,
	             reinterpret_cast<void **>(&lpWrapped));
	if (hr != hrSuccess)
		return hr;

	memset(lpWrapped, 0, cbWrapped);
	memcpy(&lpWrapped->muid, &MUIDZCSAB, sizeof(MAPIUID));
	lpWrapped->ulObjType = ulObjType;
	lpWrapped->ulOffset = ulOffset;
	memcpy(lpWrapped->origEntryID, lpEntryID, cbEntryID);
	

	*lpcbEntryID = cbWrapped;
	*lppEntryID = (LPENTRYID)lpWrapped;
	return hrSuccess;
}

HRESULT ZCABContainer::GetFolderContentsTable(ULONG ulFlags, LPMAPITABLE *lppTable)
{
	HRESULT hr = hrSuccess;
	MAPITablePtr ptrContents;
	SRowSetPtr	ptrRows;
	object_ptr<ECMemTable> lpTable;
	object_ptr<ECMemTableView> lpTableView;
	ULONG i, j = 0;
	ECAndRestriction resAnd;
	SPropValue sRestrictProp;

#define I_NCOLS 11
	// data from the contact
	static constexpr const SizedSPropTagArray(I_NCOLS, inputCols) =
		{I_NCOLS, {PR_DISPLAY_NAME, PR_ADDRTYPE, PR_EMAIL_ADDRESS,
		PR_NORMALIZED_SUBJECT, PR_ENTRYID, PR_MESSAGE_CLASS,
		PR_ORIGINAL_DISPLAY_NAME, PR_PARENT_ENTRYID, PR_SOURCE_KEY,
		PR_PARENT_SOURCE_KEY, PR_CHANGE_KEY}};
	// I_MV_INDEX is dispidABPEmailList from mnNamedProps
	enum {I_DISPLAY_NAME = 0, I_ADDRTYPE, I_EMAIL_ADDRESS, I_NORMALIZED_SUBJECT, I_ENTRYID, I_MESSAGE_CLASS, I_ORIGINAL_DISPLAY_NAME, 
		  I_PARENT_ENTRYID, I_SOURCE_KEY, I_PARENT_SOURCE_KEY, I_CHANGE_KEY, I_MV_INDEX, I_NAMEDSTART};
	SPropTagArrayPtr ptrInputCols;

#define O_NCOLS 21
	// data for the table
	static constexpr const SizedSPropTagArray(O_NCOLS, outputCols) =
		{O_NCOLS, {PR_DISPLAY_NAME, PR_ADDRTYPE, PR_EMAIL_ADDRESS,
		PR_NORMALIZED_SUBJECT, PR_ENTRYID, PR_DISPLAY_TYPE,
		PR_OBJECT_TYPE, PR_ORIGINAL_DISPLAY_NAME,
		PR_ZC_ORIGINAL_ENTRYID, PR_ZC_ORIGINAL_PARENT_ENTRYID,
		PR_ZC_ORIGINAL_SOURCE_KEY, PR_ZC_ORIGINAL_PARENT_SOURCE_KEY,
		PR_ZC_ORIGINAL_CHANGE_KEY, PR_SEARCH_KEY, PR_INSTANCE_KEY,
		PR_ROWID}};
	enum {O_DISPLAY_NAME = 0, O_ADDRTYPE, O_EMAIL_ADDRESS, O_NORMALIZED_SUBJECT, O_ENTRYID, O_DISPLAY_TYPE, O_OBJECT_TYPE, O_ORIGINAL_DISPLAY_NAME, 
		  O_ZC_ORIGINAL_ENTRYID, O_ZC_ORIGINAL_PARENT_ENTRYID, O_ZC_ORIGINAL_SOURCE_KEY, O_ZC_ORIGINAL_PARENT_SOURCE_KEY, O_ZC_ORIGINAL_CHANGE_KEY,
		  O_SEARCH_KEY, O_INSTANCE_KEY, O_ROWID};
	SPropTagArrayPtr ptrOutputCols;

	SPropTagArrayPtr ptrContactCols;

	// named properties
	SPropTagArrayPtr ptrNameTags;
	KCHL::memory_ptr<MAPINAMEID *> lppNames;
	ULONG ulNames = (6 * 5) + 2;
	ULONG ulType = (ulFlags & MAPI_UNICODE) ? PT_UNICODE : PT_STRING8;
	MAPINAMEID mnNamedProps[(6 * 5) + 2] = {
		// index with MVI_FLAG
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidABPEmailList}},

		// MVI offset 0: email1 set
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail1DisplayName}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail1AddressType}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail1Address}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail1OriginalDisplayName}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail1OriginalEntryID}},

		// MVI offset 1: email2 set
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail2DisplayName}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail2AddressType}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail2Address}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail2OriginalDisplayName}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail2OriginalEntryID}},

		// MVI offset 2: email3 set
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail3DisplayName}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail3AddressType}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail3Address}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail3OriginalDisplayName}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidEmail3OriginalEntryID}},

		// MVI offset 3: business fax (fax2) set
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax2DisplayName}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax2AddressType}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax2Address}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax2OriginalDisplayName}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax2OriginalEntryID}},

		// MVI offset 4: home fax (fax3) set
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax3DisplayName}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax3AddressType}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax3Address}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax3OriginalDisplayName}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax3OriginalEntryID}},		

		// MVI offset 5: primary fax (fax1) set
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax1DisplayName}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax1AddressType}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax1Address}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax1OriginalDisplayName}},
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidFax1OriginalEntryID}},

		// restriction
		{(LPGUID)&PSETID_Address, MNID_ID, {dispidABPArrayType}},
	};

	ulFlags = ulFlags & MAPI_UNICODE;
	hr = Util::HrCopyUnicodePropTagArray(ulFlags, inputCols, &~ptrInputCols);
	if (hr != hrSuccess)
		return hr;
	hr = Util::HrCopyUnicodePropTagArray(ulFlags, outputCols, &~ptrOutputCols);
	if (hr != hrSuccess)
		return hr;
	hr = ECMemTable::Create(ptrOutputCols, PR_ROWID, &~lpTable);
	if(hr != hrSuccess)
		return hr;

	// root container has no contents, on hierarchy entries
	if (m_lpContactFolder == NULL)
		goto done;
	hr = m_lpContactFolder->GetContentsTable(ulFlags | MAPI_DEFERRED_ERRORS, &~ptrContents);
	if (hr != hrSuccess)
		return hr;
	hr = MAPIAllocateBuffer(sizeof(LPMAPINAMEID) * ulNames, &~lppNames);
	if (hr != hrSuccess)
		return hr;

	for (i = 0; i < ulNames; ++i)
		lppNames[i] = &mnNamedProps[i];
	hr = m_lpContactFolder->GetIDsFromNames(ulNames, lppNames, MAPI_CREATE, &~ptrNameTags);
	if (FAILED(hr))
		return hr;

	// fix types
	ptrNameTags->aulPropTag[0] = CHANGE_PROP_TYPE(ptrNameTags->aulPropTag[0], PT_MV_LONG | MV_INSTANCE);
	for (i = 0; i < (ulNames - 2) / 5; ++i) {
		ptrNameTags->aulPropTag[1+ (i*5) + 0] = CHANGE_PROP_TYPE(ptrNameTags->aulPropTag[1+ (i*5) + 0], ulType);
		ptrNameTags->aulPropTag[1+ (i*5) + 1] = CHANGE_PROP_TYPE(ptrNameTags->aulPropTag[1+ (i*5) + 1], ulType);
		ptrNameTags->aulPropTag[1+ (i*5) + 2] = CHANGE_PROP_TYPE(ptrNameTags->aulPropTag[1+ (i*5) + 2], ulType);
		ptrNameTags->aulPropTag[1+ (i*5) + 3] = CHANGE_PROP_TYPE(ptrNameTags->aulPropTag[1+ (i*5) + 3], ulType);
		ptrNameTags->aulPropTag[1+ (i*5) + 4] = CHANGE_PROP_TYPE(ptrNameTags->aulPropTag[1+ (i*5) + 4], PT_BINARY);
	}
	ptrNameTags->aulPropTag[ulNames-1] = CHANGE_PROP_TYPE(ptrNameTags->aulPropTag[ulNames-1], PT_LONG);

	// add func HrCombinePropTagArrays(part1, part2, dest);
	hr = MAPIAllocateBuffer(CbNewSPropTagArray(ptrInputCols->cValues + ptrNameTags->cValues), &~ptrContactCols);
	if (hr != hrSuccess)
		return hr;
	j = 0;
	for (i = 0; i < ptrInputCols->cValues; ++i)
		ptrContactCols->aulPropTag[j++] = ptrInputCols->aulPropTag[i];
	for (i = 0; i < ptrNameTags->cValues; ++i)
		ptrContactCols->aulPropTag[j++] = ptrNameTags->aulPropTag[i];
	ptrContactCols->cValues = j;

	// the exists is extra compared to the outlook restriction
	// restrict: ( distlist || ( contact && exist(abparraytype) && abparraytype != 0 ) )

	sRestrictProp.ulPropTag = PR_MESSAGE_CLASS_A;
	sRestrictProp.Value.lpszA = const_cast<char *>("IPM.Contact");
	resAnd += ECContentRestriction(FL_PREFIX|FL_IGNORECASE, PR_MESSAGE_CLASS_A, &sRestrictProp, ECRestriction::Shallow);
	sRestrictProp.ulPropTag = ptrNameTags->aulPropTag[ulNames-1];
	sRestrictProp.Value.ul = 0;
	resAnd += ECExistRestriction(sRestrictProp.ulPropTag);
	resAnd += ECPropertyRestriction(RELOP_NE, sRestrictProp.ulPropTag, &sRestrictProp, ECRestriction::Shallow);

	sRestrictProp.ulPropTag = PR_MESSAGE_CLASS_A;
	sRestrictProp.Value.lpszA = const_cast<char *>("IPM.DistList");
	hr = ECOrRestriction(
		ECContentRestriction(FL_PREFIX | FL_IGNORECASE, PR_MESSAGE_CLASS_A, &sRestrictProp, ECRestriction::Cheap) +
		resAnd
	).RestrictTable(ptrContents, TBL_BATCH);
	if (hr != hrSuccess)
		return hr;
	// set columns
	hr = ptrContents->SetColumns(ptrContactCols, TBL_BATCH);
	if (hr != hrSuccess)
		return hr;

	j = 0;
	while (true) {
		hr = ptrContents->QueryRows(256, 0, &ptrRows);
		if (hr != hrSuccess)
			return hr;
		if (ptrRows.empty())
			break;

		for (i = 0; i < ptrRows.size(); ++i) {
			ULONG ulOffset = 0;
			std::string strSearchKey;
			SPropValue lpColData[O_NCOLS];

			memset(lpColData, 0, sizeof(lpColData));

			if (ptrRows[i].lpProps[I_MV_INDEX].ulPropTag == (ptrNameTags->aulPropTag[0] & ~MVI_FLAG)) {
				// do not index outside named properties
				if (ptrRows[i].lpProps[I_MV_INDEX].Value.ul > 5)
					continue;
				ulOffset = ptrRows[i].lpProps[I_MV_INDEX].Value.ul * 5;
			}

			if (PROP_TYPE(ptrRows[i].lpProps[I_MESSAGE_CLASS].ulPropTag) == PT_ERROR)
				// no PR_MESSAGE_CLASS, unusable
				continue;

			if (
				((ulFlags & MAPI_UNICODE) && wcscasecmp(ptrRows[i].lpProps[I_MESSAGE_CLASS].Value.lpszW, L"IPM.Contact") == 0) ||
				((ulFlags & MAPI_UNICODE) == 0 && strcasecmp(ptrRows[i].lpProps[I_MESSAGE_CLASS].Value.lpszA, "IPM.Contact") == 0)
				)
			{
				lpColData[O_DISPLAY_TYPE].ulPropTag = PR_DISPLAY_TYPE;
				lpColData[O_DISPLAY_TYPE].Value.ul = DT_MAILUSER;

				lpColData[O_OBJECT_TYPE].ulPropTag = PR_OBJECT_TYPE;
				lpColData[O_OBJECT_TYPE].Value.ul = MAPI_MAILUSER;

				lpColData[O_ADDRTYPE].ulPropTag = CHANGE_PROP_TYPE(ptrOutputCols->aulPropTag[O_ADDRTYPE], PROP_TYPE(ptrRows[i].lpProps[I_NAMEDSTART + ulOffset + 1].ulPropTag));
				lpColData[O_ADDRTYPE].Value = ptrRows[i].lpProps[I_NAMEDSTART + ulOffset + 1].Value;
			} else if (
					   ((ulFlags & MAPI_UNICODE) && wcscasecmp(ptrRows[i].lpProps[I_MESSAGE_CLASS].Value.lpszW, L"IPM.DistList") == 0) ||
					   ((ulFlags & MAPI_UNICODE) == 0 && strcasecmp(ptrRows[i].lpProps[I_MESSAGE_CLASS].Value.lpszA, "IPM.DistList") == 0)
					   )
			{
				lpColData[O_DISPLAY_TYPE].ulPropTag = PR_DISPLAY_TYPE;
				lpColData[O_DISPLAY_TYPE].Value.ul = DT_PRIVATE_DISTLIST;

				lpColData[O_OBJECT_TYPE].ulPropTag = PR_OBJECT_TYPE;
				lpColData[O_OBJECT_TYPE].Value.ul = MAPI_DISTLIST;

				lpColData[O_ADDRTYPE].ulPropTag = PR_ADDRTYPE_W;
				lpColData[O_ADDRTYPE].Value.lpszW = const_cast<wchar_t *>(L"MAPIPDL");
			} else {
				continue;
			}

			// devide by 5 since a block of properties on a contact is a set of 5 (see mnNamedProps above)
			memory_ptr<ENTRYID> wrapped_eid;
			hr = MakeWrappedEntryID(ptrRows[i].lpProps[I_ENTRYID].Value.bin.cb, (LPENTRYID)ptrRows[i].lpProps[I_ENTRYID].Value.bin.lpb,
									lpColData[O_OBJECT_TYPE].Value.ul, ulOffset/5,
						&lpColData[O_ENTRYID].Value.bin.cb, &~wrapped_eid);
			if (hr != hrSuccess)
				return hr;
			lpColData[O_ENTRYID].Value.bin.lpb = reinterpret_cast<BYTE *>(wrapped_eid.get());
			lpColData[O_ENTRYID].ulPropTag = PR_ENTRYID;

			ulOffset += I_NAMEDSTART;

			lpColData[O_DISPLAY_NAME].ulPropTag = CHANGE_PROP_TYPE(ptrOutputCols->aulPropTag[O_DISPLAY_NAME], PROP_TYPE(ptrRows[i].lpProps[ulOffset + 0].ulPropTag));
			if (PROP_TYPE(lpColData[O_DISPLAY_NAME].ulPropTag) == PT_ERROR)
				// Email#Display not available, fallback to normal PR_DISPLAY_NAME
				lpColData[O_DISPLAY_NAME] = ptrRows[i].lpProps[I_DISPLAY_NAME];
			else
				lpColData[O_DISPLAY_NAME].Value = ptrRows[i].lpProps[ulOffset + 0].Value;

			lpColData[O_EMAIL_ADDRESS].ulPropTag = CHANGE_PROP_TYPE(ptrOutputCols->aulPropTag[O_EMAIL_ADDRESS], PROP_TYPE(ptrRows[i].lpProps[ulOffset + 2].ulPropTag));
			if (PROP_TYPE(lpColData[O_EMAIL_ADDRESS].ulPropTag) == PT_ERROR)
				// Email#Address not available, fallback to normal PR_EMAIL_ADDRESS
				lpColData[O_EMAIL_ADDRESS] = ptrRows[i].lpProps[I_EMAIL_ADDRESS];
			else
				lpColData[O_EMAIL_ADDRESS].Value = ptrRows[i].lpProps[ulOffset + 2].Value;

			lpColData[O_NORMALIZED_SUBJECT].ulPropTag = CHANGE_PROP_TYPE(ptrOutputCols->aulPropTag[O_NORMALIZED_SUBJECT], PROP_TYPE(ptrRows[i].lpProps[ulOffset + 3].ulPropTag));
			if (PROP_TYPE(lpColData[O_NORMALIZED_SUBJECT].ulPropTag) == PT_ERROR)
				// Email#OriginalDisplayName not available, fallback to normal PR_NORMALIZED_SUBJECT
				lpColData[O_NORMALIZED_SUBJECT] = ptrRows[i].lpProps[I_NORMALIZED_SUBJECT];
			else
				lpColData[O_NORMALIZED_SUBJECT].Value = ptrRows[i].lpProps[ulOffset + 3].Value;

			lpColData[O_ORIGINAL_DISPLAY_NAME].ulPropTag = CHANGE_PROP_TYPE(ptrOutputCols->aulPropTag[O_ORIGINAL_DISPLAY_NAME], PROP_TYPE(ptrRows[i].lpProps[I_DISPLAY_NAME].ulPropTag));
			lpColData[O_ORIGINAL_DISPLAY_NAME].Value = ptrRows[i].lpProps[I_DISPLAY_NAME].Value;

			lpColData[O_ZC_ORIGINAL_ENTRYID].ulPropTag = CHANGE_PROP_TYPE(ptrOutputCols->aulPropTag[O_ZC_ORIGINAL_ENTRYID], PROP_TYPE(ptrRows[i].lpProps[I_ENTRYID].ulPropTag));
			lpColData[O_ZC_ORIGINAL_ENTRYID].Value = ptrRows[i].lpProps[I_ENTRYID].Value;

			lpColData[O_ZC_ORIGINAL_PARENT_ENTRYID].ulPropTag = CHANGE_PROP_TYPE(ptrOutputCols->aulPropTag[O_ZC_ORIGINAL_PARENT_ENTRYID], PROP_TYPE(ptrRows[i].lpProps[I_PARENT_ENTRYID].ulPropTag));
			lpColData[O_ZC_ORIGINAL_PARENT_ENTRYID].Value = ptrRows[i].lpProps[I_PARENT_ENTRYID].Value;

			lpColData[O_ZC_ORIGINAL_SOURCE_KEY].ulPropTag = CHANGE_PROP_TYPE(ptrOutputCols->aulPropTag[O_ZC_ORIGINAL_SOURCE_KEY], PROP_TYPE(ptrRows[i].lpProps[I_SOURCE_KEY].ulPropTag));
			lpColData[O_ZC_ORIGINAL_SOURCE_KEY].Value = ptrRows[i].lpProps[I_SOURCE_KEY].Value;

			lpColData[O_ZC_ORIGINAL_PARENT_SOURCE_KEY].ulPropTag = CHANGE_PROP_TYPE(ptrOutputCols->aulPropTag[O_ZC_ORIGINAL_PARENT_SOURCE_KEY], PROP_TYPE(ptrRows[i].lpProps[I_PARENT_SOURCE_KEY].ulPropTag));
			lpColData[O_ZC_ORIGINAL_PARENT_SOURCE_KEY].Value = ptrRows[i].lpProps[I_PARENT_SOURCE_KEY].Value;

			lpColData[O_ZC_ORIGINAL_CHANGE_KEY].ulPropTag = CHANGE_PROP_TYPE(ptrOutputCols->aulPropTag[O_ZC_ORIGINAL_CHANGE_KEY], PROP_TYPE(ptrRows[i].lpProps[I_CHANGE_KEY].ulPropTag));
			lpColData[O_ZC_ORIGINAL_CHANGE_KEY].Value = ptrRows[i].lpProps[I_CHANGE_KEY].Value;

			// @note, outlook seems to set the gab original search key (if possible, otherwise SMTP). The IMessage contact in the folder contains some unusable binary blob.
			if (PROP_TYPE(lpColData[O_ADDRTYPE].ulPropTag) == PT_STRING8 &&
			    PROP_TYPE(lpColData[O_EMAIL_ADDRESS].ulPropTag) == PT_STRING8)
				strSearchKey = strToUpper(std::string(lpColData[O_ADDRTYPE].Value.lpszA) + ":" + lpColData[O_EMAIL_ADDRESS].Value.lpszA);
			else if (PROP_TYPE(lpColData[O_ADDRTYPE].ulPropTag) == PT_UNICODE &&
			    PROP_TYPE(lpColData[O_EMAIL_ADDRESS].ulPropTag) == PT_UNICODE)
				strSearchKey = strToUpper(convert_to<std::string>(std::wstring(lpColData[O_ADDRTYPE].Value.lpszW) + L":" + lpColData[O_EMAIL_ADDRESS].Value.lpszW));
			else
				// eg. distlists
				hr = MAPI_E_NOT_FOUND;
			if (hr == hrSuccess) {
				lpColData[O_SEARCH_KEY].ulPropTag = PR_SEARCH_KEY;
				lpColData[O_SEARCH_KEY].Value.bin.cb = strSearchKey.length()+1;
				lpColData[O_SEARCH_KEY].Value.bin.lpb = (BYTE*)strSearchKey.data();
			} else {
				lpColData[O_SEARCH_KEY].ulPropTag = CHANGE_PROP_TYPE(PR_SEARCH_KEY, PT_ERROR);
				lpColData[O_SEARCH_KEY].Value.ul = MAPI_E_NOT_FOUND;
			}

			lpColData[O_INSTANCE_KEY].ulPropTag = PR_INSTANCE_KEY;
			lpColData[O_INSTANCE_KEY].Value.bin.cb = sizeof(ULONG);
			lpColData[O_INSTANCE_KEY].Value.bin.lpb = (LPBYTE)&j;

			lpColData[O_ROWID].ulPropTag = PR_ROWID;
			lpColData[O_ROWID].Value.ul = j++;

			hr = lpTable->HrModifyRow(ECKeyTable::TABLE_ROW_ADD, NULL, lpColData, O_NCOLS);
			if (hr != hrSuccess)
				return hr;
		}
	}
		
done:
	AddChild(lpTable);
	hr = lpTable->HrGetView(createLocaleFromName(nullptr), ulFlags, &~lpTableView);
	if(hr != hrSuccess)
		return hr;
	return lpTableView->QueryInterface(IID_IMAPITable,
	       reinterpret_cast<void **>(lppTable));
#undef TCOLS
}

HRESULT ZCABContainer::GetDistListContentsTable(ULONG ulFlags, LPMAPITABLE *lppTable)
{
	HRESULT hr = hrSuccess;
	static constexpr const SizedSPropTagArray(13, sptaCols) =
		{13, {PR_NULL /* reserve for PR_ROWID */, PR_ADDRTYPE,
		PR_DISPLAY_NAME, PR_DISPLAY_TYPE, PR_EMAIL_ADDRESS, PR_ENTRYID,
		PR_INSTANCE_KEY, PR_OBJECT_TYPE, PR_RECORD_KEY, PR_SEARCH_KEY,
		PR_SEND_INTERNET_ENCODING, PR_SEND_RICH_INFO,
		PR_TRANSMITABLE_DISPLAY_NAME}};
	SPropTagArrayPtr ptrCols;
	object_ptr<ECMemTable> lpTable;
	object_ptr<ECMemTableView> lpTableView;
	SPropValuePtr ptrEntries;
	MAPIPropPtr ptrUser;
	ULONG ulObjType;
	ULONG cValues;
	SPropArrayPtr ptrProps;
	SPropValue sKey;
	KCHL::object_ptr<ZCMAPIProp> ptrZCMAPIProp;

	hr = Util::HrCopyUnicodePropTagArray(ulFlags, sptaCols, &~ptrCols);
	if (hr != hrSuccess)
		return hr;
	hr = ECMemTable::Create(ptrCols, PR_ROWID, &~lpTable);
	if(hr != hrSuccess)
		return hr;

	// getprops, open real contacts, make table
	hr = HrGetOneProp(m_lpDistList, 0x81051102, &~ptrEntries); // Members "entryids" named property, see data layout below
	if (hr != hrSuccess)
		return hr;

	sKey.ulPropTag = PR_ROWID;
	sKey.Value.ul = 0;
	for (ULONG i = 0; i < ptrEntries->Value.MVbin.cValues; ++i) {
		ULONG ulOffset = 0;
		BYTE cType = 0;

		// Wrapped entryids:
		// Flags: (ULONG) 0
		// Provider: (GUID) 0xC091ADD3519DCF11A4A900AA0047FAA4
		// Type: (BYTE) <value>, describes wrapped entryid
		//  lower 4 bits:
		//   0x00 = OneOff (use addressbook)
		//   0x03 = Contact (use folder / session?)
		//   0x04 = PDL  (use folder / session?)
		//   0x05 = GAB IMailUser (use addressbook)
		//   0x06 = GAB IDistList (use addressbook)
		//  next 3 bits: if lower is 0x03
		//   0x00 = business fax, or oneoff entryid
		//   0x10 = home fax
		//   0x20 = primary fax
		//   0x30 = no contact data
		//   0x40 = email 1
		//   0x50 = email 2
		//   0x60 = email 3
		//  top bit:
		//   0x80 default on, except for oneoff entryids

		// either WAB_GUID or ONE_OFF_MUID
		if (memcmp(ptrEntries->Value.MVbin.lpbin[i].lpb + sizeof(ULONG), (void*)&WAB_GUID, sizeof(GUID)) == 0) {
			// handle wrapped entryids
			ulOffset = sizeof(ULONG) + sizeof(GUID) + sizeof(BYTE);
			cType = ptrEntries->Value.MVbin.lpbin[i].lpb[sizeof(ULONG) + sizeof(GUID)];
		}
		hr = m_lpMAPISup->OpenEntry(ptrEntries->Value.MVbin.lpbin[i].cb - ulOffset, reinterpret_cast<ENTRYID *>(ptrEntries->Value.MVbin.lpbin[i].lpb + ulOffset), nullptr, 0, &ulObjType, &~ptrUser);
		if (hr != hrSuccess)
			continue;

		if ((cType & 0x80) && (cType & 0x0F) < 5 && (cType & 0x0F) > 0) {
			ULONG cbEntryID;
			EntryIdPtr ptrEntryID;
			SPropValuePtr ptrPropEntryID;
			ULONG ulObjOffset = 0;
			ULONG ulObjType = 0;

			hr = HrGetOneProp(ptrUser, PR_ENTRYID, &~ptrPropEntryID);
			if (hr != hrSuccess)
				return hr;

			if ((cType & 0x0F) == 3) {
				ulObjType = MAPI_MAILUSER;
				ulObjOffset = cType >> 4;
			} else 
				ulObjType = MAPI_DISTLIST;

			hr = MakeWrappedEntryID(ptrPropEntryID->Value.bin.cb, (LPENTRYID)ptrPropEntryID->Value.bin.lpb, ulObjType, ulObjOffset, &cbEntryID, &~ptrEntryID);
			if (hr != hrSuccess)
				return hr;
			hr = ZCMAPIProp::Create(ptrUser, cbEntryID, ptrEntryID, &~ptrZCMAPIProp);
			if (hr != hrSuccess)
				return hr;
			hr = ptrZCMAPIProp->GetProps(ptrCols, 0, &cValues, &~ptrProps);
			if (FAILED(hr))
				continue;

		} else {
			hr = ptrUser->GetProps(ptrCols, 0, &cValues, &~ptrProps);
			if (FAILED(hr))
				continue;
		}

		ptrProps[0] = sKey;

		hr = lpTable->HrModifyRow(ECKeyTable::TABLE_ROW_ADD, NULL, ptrProps.get(), cValues);
		if (hr != hrSuccess)
			return hr;
		++sKey.Value.ul;
	}

	AddChild(lpTable);
	hr = lpTable->HrGetView(createLocaleFromName(nullptr), ulFlags, &~lpTableView);
	if(hr != hrSuccess)
		return hr;
	return lpTableView->QueryInterface(IID_IMAPITable,
	       reinterpret_cast<void **>(lppTable));
}

/** 
 * Returns an addressbook contents table of the IPM.Contacts folder in m_lpContactFolder.
 * 
 * @param[in] ulFlags MAPI_UNICODE for default unicode columns
 * @param[out] lppTable contents table of all items found in folder
 * 
 * @return 
 */
HRESULT ZCABContainer::GetContentsTable(ULONG ulFlags, LPMAPITABLE *lppTable)
{
	if (m_lpDistList)
		return GetDistListContentsTable(ulFlags, lppTable);
	return GetFolderContentsTable(ulFlags, lppTable);
}

/** 
 * Can return 3 kinds of tables:
 * 1. Root Container, contains one entry: the provider container
 * 2. Provider Container, contains user folders
 * 3. CONVENIENT_DEPTH: 1 + 2
 * 
 * @param[in] ulFlags MAPI_UNICODE | CONVENIENT_DEPTH
 * @param[out] lppTable root container table
 * 
 * @return MAPI Error code
 */
HRESULT ZCABContainer::GetHierarchyTable(ULONG ulFlags, LPMAPITABLE *lppTable)
{
	HRESULT			hr = hrSuccess;
	object_ptr<ECMemTable> lpTable;
	object_ptr<ECMemTableView> lpTableView;
#define TCOLS 9
	SizedSPropTagArray(TCOLS, sptaCols) = {TCOLS, {PR_ENTRYID, PR_STORE_ENTRYID, PR_DISPLAY_NAME_W, PR_OBJECT_TYPE, PR_CONTAINER_FLAGS, PR_DISPLAY_TYPE, PR_AB_PROVIDER_ID, PR_DEPTH, PR_INSTANCE_KEY}};
	enum {XENTRYID = 0, STORE_ENTRYID, DISPLAY_NAME, OBJECT_TYPE, CONTAINER_FLAGS, DISPLAY_TYPE, AB_PROVIDER_ID, DEPTH, INSTANCE_KEY, ROWID};
	ULONG ulInstance = 0;
	SPropValue sProps[TCOLS + 1];
	convert_context converter;

	if ((ulFlags & MAPI_UNICODE) == 0)
		sptaCols.aulPropTag[DISPLAY_NAME] = PR_DISPLAY_NAME_A;
	hr = ECMemTable::Create(sptaCols, PR_ROWID, &~lpTable);
	if(hr != hrSuccess)
		return hr;
	/*
	  1. root container		: m_lpFolders = NULL, m_lpContactFolder = NULL, m_lpDistList = NULL, m_lpProvider = ZCABLogon (one entry: provider)
	  2. provider container	: m_lpFolders = data, m_lpContactFolder = NULL, m_lpDistList = NULL, m_lpProvider = ZCABLogon (N entries: folders)
	  3. contact folder		: m_lpFolders = NULL, m_lpContactFolder = data, m_lpDistList = NULL, m_lpProvider = ZCABContainer from (2), (no hierarchy entries)
	  4. distlist			: m_lpFolders = NULL, m_lpContactFolder = NULL, m_lpDistList = data, m_lpProvider = ZCABContainer from (3), (no hierarchy entries)

	  when ulFlags contains CONVENIENT_DEPTH, (1) also contains (2)
	  so we open (2) through the provider, which gives the m_lpFolders
	*/

	if (m_lpFolders) {
		// create hierarchy with folders from user stores
		for (const auto &folder : *m_lpFolders) {
			std::string strName;
			KCHL::memory_ptr<cabEntryID> lpEntryID;
			ULONG cbEntryID = CbNewCABENTRYID(folder.cbFolder);

			hr = MAPIAllocateBuffer(cbEntryID, &~lpEntryID);
			if (hr != hrSuccess)
				return hr;

			memset(lpEntryID, 0, cbEntryID);
			memcpy(&lpEntryID->muid, &MUIDZCSAB, sizeof(MAPIUID));
			lpEntryID->ulObjType = MAPI_ABCONT;
			lpEntryID->ulOffset = 0;
			memcpy(lpEntryID->origEntryID, folder.lpFolder, folder.cbFolder);

			sProps[XENTRYID].ulPropTag = sptaCols.aulPropTag[XENTRYID];
			sProps[XENTRYID].Value.bin.cb = cbEntryID;
			sProps[XENTRYID].Value.bin.lpb = reinterpret_cast<BYTE *>(lpEntryID.get());
			sProps[STORE_ENTRYID].ulPropTag = CHANGE_PROP_TYPE(sptaCols.aulPropTag[STORE_ENTRYID], PT_ERROR);
			sProps[STORE_ENTRYID].Value.err = MAPI_E_NOT_FOUND;

			sProps[DISPLAY_NAME].ulPropTag = sptaCols.aulPropTag[DISPLAY_NAME];
			if ((ulFlags & MAPI_UNICODE) == 0) {
				sProps[DISPLAY_NAME].ulPropTag = PR_DISPLAY_NAME_A;
				strName = converter.convert_to<std::string>(folder.strwDisplayName);
				sProps[DISPLAY_NAME].Value.lpszA = (char*)strName.c_str();
			} else {
				sProps[DISPLAY_NAME].Value.lpszW = const_cast<wchar_t *>(folder.strwDisplayName.c_str());
			}

			sProps[OBJECT_TYPE].ulPropTag = sptaCols.aulPropTag[OBJECT_TYPE];
			sProps[OBJECT_TYPE].Value.ul = MAPI_ABCONT;

			sProps[CONTAINER_FLAGS].ulPropTag = sptaCols.aulPropTag[CONTAINER_FLAGS];
			sProps[CONTAINER_FLAGS].Value.ul = AB_RECIPIENTS | AB_UNMODIFIABLE | AB_UNICODE_OK;

			sProps[DISPLAY_TYPE].ulPropTag = sptaCols.aulPropTag[DISPLAY_TYPE];
			sProps[DISPLAY_TYPE].Value.ul = DT_NOT_SPECIFIC;

			sProps[AB_PROVIDER_ID].ulPropTag = sptaCols.aulPropTag[AB_PROVIDER_ID];
			sProps[AB_PROVIDER_ID].Value.bin.cb = sizeof(GUID);
			sProps[AB_PROVIDER_ID].Value.bin.lpb = (BYTE*)&MUIDZCSAB;

			sProps[DEPTH].ulPropTag = PR_DEPTH;
			sProps[DEPTH].Value.ul = (ulFlags & CONVENIENT_DEPTH) ? 1 : 0;

			sProps[INSTANCE_KEY].ulPropTag = PR_INSTANCE_KEY;
			sProps[INSTANCE_KEY].Value.bin.cb = sizeof(ULONG);
			sProps[INSTANCE_KEY].Value.bin.lpb = (LPBYTE)&ulInstance;

			sProps[ROWID].ulPropTag = PR_ROWID;
			sProps[ROWID].Value.ul = ulInstance;

			hr = lpTable->HrModifyRow(ECKeyTable::TABLE_ROW_ADD, NULL, sProps, TCOLS + 1);
			if (hr != hrSuccess)
				return hr;
			++ulInstance;
		}
	} else if (!m_lpContactFolder) {
		// only if not using a contacts folder, which should make the contents table. so this would return an empty hierarchy table, which is true.
		// create toplevel hierarchy. one entry: "Kopano Contacts Folders"
		BYTE sEntryID[4 + sizeof(GUID)]; // minimum sized entryid. no extra info needed

		memset(sEntryID, 0, sizeof(sEntryID));
		memcpy(sEntryID + 4, &MUIDZCSAB, sizeof(GUID));

		sProps[XENTRYID].ulPropTag = sptaCols.aulPropTag[XENTRYID];
		sProps[XENTRYID].Value.bin.cb = sizeof(sEntryID);
		sProps[XENTRYID].Value.bin.lpb = sEntryID;
		sProps[STORE_ENTRYID].ulPropTag = CHANGE_PROP_TYPE(sptaCols.aulPropTag[STORE_ENTRYID], PT_ERROR);
		sProps[STORE_ENTRYID].Value.err = MAPI_E_NOT_FOUND;

		sProps[DISPLAY_NAME].ulPropTag = sptaCols.aulPropTag[DISPLAY_NAME];
		if ((ulFlags & MAPI_UNICODE) == 0) {
			sProps[DISPLAY_NAME].ulPropTag = PR_DISPLAY_NAME_A;
			sProps[DISPLAY_NAME].Value.lpszA = _A("Kopano Contacts Folders");
		} else {
			sProps[DISPLAY_NAME].Value.lpszW = _W("Kopano Contacts Folders");
		}

		sProps[OBJECT_TYPE].ulPropTag = sptaCols.aulPropTag[OBJECT_TYPE];
		sProps[OBJECT_TYPE].Value.ul = MAPI_ABCONT;

		// AB_SUBCONTAINERS flag causes this folder to be skipped in the IAddrBook::GetSearchPath()
		sProps[CONTAINER_FLAGS].ulPropTag = sptaCols.aulPropTag[CONTAINER_FLAGS];
		sProps[CONTAINER_FLAGS].Value.ul = AB_SUBCONTAINERS | AB_UNMODIFIABLE | AB_UNICODE_OK;

		sProps[DISPLAY_TYPE].ulPropTag = sptaCols.aulPropTag[DISPLAY_TYPE];
		sProps[DISPLAY_TYPE].Value.ul = DT_NOT_SPECIFIC;

		sProps[AB_PROVIDER_ID].ulPropTag = sptaCols.aulPropTag[AB_PROVIDER_ID];
		sProps[AB_PROVIDER_ID].Value.bin.cb = sizeof(GUID);
		sProps[AB_PROVIDER_ID].Value.bin.lpb = (BYTE*)&MUIDZCSAB;

		sProps[DEPTH].ulPropTag = PR_DEPTH;
		sProps[DEPTH].Value.ul = 0;

		sProps[INSTANCE_KEY].ulPropTag = PR_INSTANCE_KEY;
		sProps[INSTANCE_KEY].Value.bin.cb = sizeof(ULONG);
		sProps[INSTANCE_KEY].Value.bin.lpb = (LPBYTE)&ulInstance;

		sProps[ROWID].ulPropTag = PR_ROWID;
		sProps[ROWID].Value.ul = ulInstance++;

		hr = lpTable->HrModifyRow(ECKeyTable::TABLE_ROW_ADD, NULL, sProps, TCOLS + 1);
		if (hr != hrSuccess)
			return hr;

		if (ulFlags & CONVENIENT_DEPTH) {
			ABContainerPtr ptrContainer;			
			ULONG ulObjType;
			MAPITablePtr ptrTable;
			SRowSetPtr	ptrRows;

			hr = ((ZCABLogon*)m_lpProvider)->OpenEntry(sizeof(sEntryID), reinterpret_cast<ENTRYID *>(sEntryID), nullptr, 0, &ulObjType, &~ptrContainer);
			if (hr != hrSuccess)
				return hr;
			hr = ptrContainer->GetHierarchyTable(ulFlags, &~ptrTable);
			if (hr != hrSuccess)
				return hr;
			hr = ptrTable->QueryRows(-1, 0, &ptrRows);
			if (hr != hrSuccess)
				return hr;

			for (SRowSetPtr::size_type i = 0; i < ptrRows.size(); ++i) {
				// use PR_STORE_ENTRYID field to set instance key, since that is always MAPI_E_NOT_FOUND (see above)
				auto lpProp = PpropFindProp(ptrRows[i].lpProps, ptrRows[i].cValues, CHANGE_PROP_TYPE(PR_STORE_ENTRYID, PT_ERROR));
				if (lpProp == nullptr)
					continue;
				lpProp->ulPropTag = PR_ROWID;
				lpProp->Value.ul = ulInstance++;

				hr = lpTable->HrModifyRow(ECKeyTable::TABLE_ROW_ADD, NULL, ptrRows[i].lpProps, ptrRows[i].cValues);
				if (hr != hrSuccess)
					return hr;
			}
		}
	}

	AddChild(lpTable);
	hr = lpTable->HrGetView(createLocaleFromName(nullptr), ulFlags, &~lpTableView);
	if(hr != hrSuccess)
		return hr;
	return lpTableView->QueryInterface(IID_IMAPITable,
	       reinterpret_cast<void **>(lppTable));
#undef TCOLS
}

/** 
 * Opens the contact from any given contact folder, and makes a ZCMAPIProp object for that contact.
 * 
 * @param[in] cbEntryID wrapped contact entryid bytes
 * @param[in] lpEntryID 
 * @param[in] lpInterface requested interface
 * @param[in] ulFlags unicode flags
 * @param[out] lpulObjType returned object type
 * @param[out] lppUnk returned object
 * 
 * @return MAPI Error code
 */
HRESULT ZCABContainer::OpenEntry(ULONG cbEntryID, LPENTRYID lpEntryID, LPCIID lpInterface, ULONG ulFlags, ULONG *lpulObjType, LPUNKNOWN *lppUnk)
{
	HRESULT hr = hrSuccess;
	auto lpCABEntryID = reinterpret_cast<cabEntryID *>(lpEntryID);
	ULONG cbNewCABEntryID = CbNewCABENTRYID(0);
	ULONG cbFolder = 0;
	LPENTRYID lpFolder = NULL;
	ULONG ulObjType = 0;
	MAPIFolderPtr ptrContactFolder;
	object_ptr<ZCABContainer> lpZCABContacts;
	MessagePtr ptrContact;
	object_ptr<ZCMAPIProp> lpZCMAPIProp;

	if (cbEntryID < cbNewCABEntryID ||
	    memcmp(&lpCABEntryID->muid, &MUIDZCSAB, sizeof(MAPIUID)) != 0)
		return MAPI_E_UNKNOWN_ENTRYID;

	if (m_lpDistList)
		// there is nothing to open from the distlist point of view
		// @todo, passthrough to IMAPISupport object?
		return MAPI_E_NO_SUPPORT;

	cbFolder = cbEntryID - cbNewCABEntryID;
	lpFolder = (LPENTRYID)((LPBYTE)lpEntryID + cbNewCABEntryID);

	if (lpCABEntryID->ulObjType == MAPI_ABCONT) {
		hr = m_lpMAPISup->OpenEntry(cbFolder, lpFolder, nullptr, 0, &ulObjType, &~ptrContactFolder);
		if (hr == MAPI_E_NOT_FOUND) {
			// the folder is most likely in a store that is not yet available through this MAPI session
			// try opening the store through the support object, and see if we can get it anyway
			MsgStorePtr ptrStore;
			MAPIGetSessionPtr ptrGetSession;
			MAPISessionPtr ptrSession;

			hr = m_lpMAPISup->QueryInterface(IID_IMAPIGetSession, &~ptrGetSession);
			if (hr != hrSuccess)
				return hr;
			hr = ptrGetSession->GetMAPISession(&~ptrSession);
			if (hr != hrSuccess)
				return hr;

			std::vector<zcabFolderEntry>::const_iterator i;
			// find the store of this folder
			for (i = m_lpFolders->cbegin();
			     i != m_lpFolders->cend(); ++i) {
				ULONG res;
				if ((m_lpMAPISup->CompareEntryIDs(i->cbFolder, (LPENTRYID)i->lpFolder, cbFolder, lpFolder, 0, &res) == hrSuccess) && res == TRUE)
					break;
			}
			if (i == m_lpFolders->cend())
				return MAPI_E_NOT_FOUND;
			hr = ptrSession->OpenMsgStore(0, i->cbStore, reinterpret_cast<ENTRYID *>(i->lpStore), nullptr, 0, &~ptrStore);
			if (hr != hrSuccess)
				return hr;
			hr = ptrStore->OpenEntry(cbFolder, lpFolder, nullptr, 0, &ulObjType, &~ptrContactFolder);
		}
		if (hr != hrSuccess)
			return hr;
		hr = ZCABContainer::Create(nullptr, ptrContactFolder, m_lpMAPISup, m_lpProvider, &~lpZCABContacts);
		if (hr != hrSuccess)
			return hr;
		AddChild(lpZCABContacts);

		if (lpInterface)
			hr = lpZCABContacts->QueryInterface(*lpInterface, (void**)lppUnk);
		else
			hr = lpZCABContacts->QueryInterface(IID_IABContainer, (void**)lppUnk);
	} else if (lpCABEntryID->ulObjType == MAPI_DISTLIST) {
		// open the Original Message
		hr = m_lpMAPISup->OpenEntry(cbFolder, lpFolder, nullptr, 0, &ulObjType, &~ptrContact);
		if (hr != hrSuccess)
			return hr;
		hr = ZCABContainer::Create(ptrContact, cbEntryID, lpEntryID, m_lpMAPISup, &~lpZCABContacts);
		if (hr != hrSuccess)
			return hr;
		AddChild(lpZCABContacts);

		if (lpInterface)
			hr = lpZCABContacts->QueryInterface(*lpInterface, (void**)lppUnk);
		else
			hr = lpZCABContacts->QueryInterface(IID_IDistList, (void**)lppUnk);
	} else if (lpCABEntryID->ulObjType == MAPI_MAILUSER) {
		// open the Original Message
		hr = m_lpMAPISup->OpenEntry(cbFolder, lpFolder, nullptr, 0, &ulObjType, &~ptrContact);
		if (hr != hrSuccess)
			return hr;
		hr = ZCMAPIProp::Create(ptrContact, cbEntryID, lpEntryID, &~lpZCMAPIProp);
		if (hr != hrSuccess)
			return hr;
		AddChild(lpZCMAPIProp);

		if (lpInterface)
			hr = lpZCMAPIProp->QueryInterface(*lpInterface, (void**)lppUnk);
		else
			hr = lpZCMAPIProp->QueryInterface(IID_IMAPIProp, (void**)lppUnk);
	} else {
		return MAPI_E_UNKNOWN_ENTRYID;
	}

	*lpulObjType = lpCABEntryID->ulObjType;
	return hr;
}

HRESULT ZCABContainer::SetSearchCriteria(LPSRestriction lpRestriction, LPENTRYLIST lpContainerList, ULONG ulSearchFlags)
{
	return MAPI_E_NO_SUPPORT;
}

HRESULT ZCABContainer::GetSearchCriteria(ULONG ulFlags, LPSRestriction *lppRestriction, LPENTRYLIST *lppContainerList, ULONG *lpulSearchState)
{
	return MAPI_E_NO_SUPPORT;
}

// IABContainer
HRESULT ZCABContainer::CreateEntry(ULONG cbEntryID, LPENTRYID lpEntryID, ULONG ulCreateFlags, LPMAPIPROP* lppMAPIPropEntry)
{
	return MAPI_E_NO_SUPPORT;
}

HRESULT ZCABContainer::CopyEntries(LPENTRYLIST lpEntries, ULONG ulUIParam, LPMAPIPROGRESS lpProgress, ULONG ulFlags)
{
	return MAPI_E_NO_SUPPORT;
}

HRESULT ZCABContainer::DeleteEntries(LPENTRYLIST lpEntries, ULONG ulFlags)
{
	return MAPI_E_NO_SUPPORT;
}

/** 
 * Resolve MAPI_UNRESOLVED items in lpAdrList and possebly add resolved
 * 
 * @param[in] lpPropTagArray properties to be included in lpAdrList
 * @param[in] ulFlags EMS_AB_ADDRESS_LOOKUP | MAPI_UNICODE
 * @param[in,out] lpAdrList 
 * @param[in,out] lpFlagList MAPI_AMBIGUOUS | MAPI_RESOLVED | MAPI_UNRESOLVED
 * 
 * @return 
 */
HRESULT ZCABContainer::ResolveNames(const SPropTagArray *lpPropTagArray,
    ULONG ulFlags, LPADRLIST lpAdrList, LPFlagList lpFlagList)
{
	HRESULT hr;
	// only columns we can set from our contents table
	static constexpr const SizedSPropTagArray(7, sptaDefault) =
		{7, {PR_ADDRTYPE_A, PR_DISPLAY_NAME_A, PR_DISPLAY_TYPE,
		PR_EMAIL_ADDRESS_A, PR_ENTRYID, PR_INSTANCE_KEY,
		PR_OBJECT_TYPE}};
	static constexpr const SizedSPropTagArray(7, sptaUnicode) =
		{7, {PR_ADDRTYPE_W, PR_DISPLAY_NAME_W, PR_DISPLAY_TYPE,
		PR_EMAIL_ADDRESS_W, PR_ENTRYID, PR_INSTANCE_KEY,
		PR_OBJECT_TYPE}};
	ULONG i;
	SRowSetPtr	ptrRows;

	if (lpPropTagArray == NULL)
		lpPropTagArray = (ulFlags & MAPI_UNICODE) ? sptaUnicode : sptaDefault;

	// in this container table, find given PR_DISPLAY_NAME
	if (m_lpFolders) {
		// return MAPI_E_NO_SUPPORT ? since you should not query on this level

		// @todo search in all folders, loop over self, since we want this providers entry ids
		MAPITablePtr ptrHierarchy;

		if (m_lpFolders->empty())
			return hrSuccess;
		hr = this->GetHierarchyTable(0, &~ptrHierarchy);
		if (hr != hrSuccess)
			return hr;
		hr = ptrHierarchy->QueryRows(m_lpFolders->size(), 0, &ptrRows);
		if (hr != hrSuccess)
			return hr;

		for (i = 0; i < ptrRows.size(); ++i) {
			ABContainerPtr ptrContainer;
			auto lpEntryID = PCpropFindProp(ptrRows[i].lpProps, ptrRows[i].cValues, PR_ENTRYID);
			ULONG ulObjType;

			if (!lpEntryID)
				continue;

			// this? provider?
			hr = this->OpenEntry(lpEntryID->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpEntryID->Value.bin.lpb), nullptr, 0, &ulObjType, &~ptrContainer);
			if (hr != hrSuccess)
				return hr;
			hr = ptrContainer->ResolveNames(lpPropTagArray, ulFlags, lpAdrList, lpFlagList);
			if (hr != hrSuccess)
				return hr;
		}
	} else if (m_lpContactFolder) {
		// only search within this folder and set entries in lpAdrList / lpFlagList
		MAPITablePtr ptrContents;
		std::set<ULONG> stProps;
		SPropTagArrayPtr ptrColumns;

		// make joint proptags
		std::copy(lpPropTagArray->aulPropTag, lpPropTagArray->aulPropTag + lpPropTagArray->cValues, std::inserter(stProps, stProps.begin()));
		for (i = 0; i < lpAdrList->aEntries[0].cValues; ++i)
			stProps.insert(lpAdrList->aEntries[0].rgPropVals[i].ulPropTag);
		hr = MAPIAllocateBuffer(CbNewSPropTagArray(stProps.size()), &~ptrColumns);
		if (hr != hrSuccess)
			return hr;
		ptrColumns->cValues = stProps.size();
		std::copy(stProps.begin(), stProps.end(), ptrColumns->aulPropTag);

		hr = this->GetContentsTable(ulFlags & MAPI_UNICODE, &~ptrContents);
		if (hr != hrSuccess)
			return hr;
		hr = ptrContents->SetColumns(ptrColumns, 0);
		if (hr != hrSuccess)
			return hr;

		for (i = 0; i < lpAdrList->cEntries; ++i) {
			auto lpDisplayNameA = PCpropFindProp(lpAdrList->aEntries[i].rgPropVals, lpAdrList->aEntries[i].cValues, PR_DISPLAY_NAME_A);
			auto lpDisplayNameW = PCpropFindProp(lpAdrList->aEntries[i].rgPropVals, lpAdrList->aEntries[i].cValues, PR_DISPLAY_NAME_W);

			if (!lpDisplayNameA && !lpDisplayNameW)
				continue;

			ULONG ulResFlag = (ulFlags & EMS_AB_ADDRESS_LOOKUP) ? FL_FULLSTRING : FL_PREFIX | FL_IGNORECASE;
			ULONG ulStringType = lpDisplayNameW ? PT_UNICODE : PT_STRING8;
			SPropValue sProp = lpDisplayNameW ? *lpDisplayNameW : *lpDisplayNameA;

			ECOrRestriction resFind;
			static const ULONG ulSearchTags[] = {PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ORIGINAL_DISPLAY_NAME};

			for (size_t j = 0; j < ARRAY_SIZE(ulSearchTags); ++j) {
				sProp.ulPropTag = CHANGE_PROP_TYPE(ulSearchTags[j], ulStringType);
				resFind += ECContentRestriction(ulResFlag, CHANGE_PROP_TYPE(ulSearchTags[j], ulStringType), &sProp, ECRestriction::Cheap);
			}

			hr = resFind.RestrictTable(ptrContents, 0);
			if (hr != hrSuccess)
				return hr;
			hr = ptrContents->QueryRows(-1, MAPI_UNICODE, &ptrRows);
			if (hr != hrSuccess)
				return hr;

			if (ptrRows.size() == 1) {
				lpFlagList->ulFlag[i] = MAPI_RESOLVED;

				// release old row
				MAPIFreeBuffer(lpAdrList->aEntries[i].rgPropVals);
				lpAdrList->aEntries[i].rgPropVals = NULL;

				hr = Util::HrCopySRow((LPSRow)&lpAdrList->aEntries[i], &ptrRows[0], NULL);
				if (hr != hrSuccess)
					return hr;
			} else if (ptrRows.size() > 1) {
				lpFlagList->ulFlag[i] = MAPI_AMBIGUOUS;
			}
		}
	} else {
		// not supported within MAPI_DISTLIST container
		return MAPI_E_NO_SUPPORT;
	}
	return hrSuccess;
}

// IMAPIProp for m_lpDistList
HRESULT ZCABContainer::GetProps(const SPropTagArray *lpPropTagArray,
    ULONG ulFlags, ULONG *lpcValues, SPropValue **lppPropArray)
{
	if (m_lpDistList != NULL)
		return m_lpDistList->GetProps(lpPropTagArray, ulFlags, lpcValues, lppPropArray);
	return MAPI_E_NO_SUPPORT;
}

HRESULT ZCABContainer::GetPropList(ULONG ulFlags, LPSPropTagArray *lppPropTagArray)
{
	if (m_lpDistList != NULL)
		return m_lpDistList->GetPropList(ulFlags, lppPropTagArray);
	return MAPI_E_NO_SUPPORT;
}

// Interface IUnknown
DEF_HRMETHOD1(TRACE_MAPI, ZCABContainer, ABContainer, QueryInterface, (REFIID, refiid), (void**, lppInterface))
DEF_ULONGMETHOD1(TRACE_MAPI, ZCABContainer, ABContainer, AddRef, (void))
DEF_ULONGMETHOD1(TRACE_MAPI, ZCABContainer, ABContainer, Release, (void))

// Interface IABContainer
DEF_HRMETHOD1(TRACE_MAPI, ZCABContainer, ABContainer, CreateEntry, (ULONG, cbEntryID), (LPENTRYID, lpEntryID), (ULONG, ulCreateFlags), (LPMAPIPROP*, lppMAPIPropEntry))
DEF_HRMETHOD1(TRACE_MAPI, ZCABContainer, ABContainer, CopyEntries, (LPENTRYLIST, lpEntries), (ULONG, ulUIParam), (LPMAPIPROGRESS, lpProgress), (ULONG, ulFlags))
DEF_HRMETHOD1(TRACE_MAPI, ZCABContainer, ABContainer, DeleteEntries, (LPENTRYLIST, lpEntries), (ULONG, ulFlags))
DEF_HRMETHOD1(TRACE_MAPI, ZCABContainer, ABContainer, ResolveNames, (const SPropTagArray *, lpPropTagArray), (ULONG, ulFlags), (LPADRLIST, lpAdrList), (LPFlagList, lpFlagList))

// Interface IMAPIContainer
DEF_HRMETHOD1(TRACE_MAPI, ZCABContainer, ABContainer, GetContentsTable, (ULONG, ulFlags), (LPMAPITABLE *, lppTable))
DEF_HRMETHOD1(TRACE_MAPI, ZCABContainer, ABContainer, GetHierarchyTable, (ULONG, ulFlags), (LPMAPITABLE *, lppTable))
DEF_HRMETHOD1(TRACE_MAPI, ZCABContainer, ABContainer, OpenEntry, (ULONG, cbEntryID), (LPENTRYID, lpEntryID), (LPCIID, lpInterface), (ULONG, ulFlags), (ULONG *, lpulObjType), (LPUNKNOWN *, lppUnk))
DEF_HRMETHOD1(TRACE_MAPI, ZCABContainer, ABContainer, SetSearchCriteria, (LPSRestriction, lpRestriction), (LPENTRYLIST, lpContainerList), (ULONG, ulSearchFlags))
DEF_HRMETHOD1(TRACE_MAPI, ZCABContainer, ABContainer, GetSearchCriteria, (ULONG, ulFlags), (LPSRestriction *, lppRestriction), (LPENTRYLIST *, lppContainerList), (ULONG *, lpulSearchState))

// Interface IMAPIProp
DEF_HRMETHOD_NOSUPPORT(TRACE_MAPI, ZCABContainer, ABContainer, GetLastError, (HRESULT, hError), (ULONG, ulFlags), (LPMAPIERROR *, lppMapiError))
DEF_HRMETHOD_NOSUPPORT(TRACE_MAPI, ZCABContainer, ABContainer, SaveChanges, (ULONG, ulFlags))
DEF_HRMETHOD1(TRACE_MAPI, ZCABContainer, ABContainer, GetProps, (const SPropTagArray *, lpPropTagArray), (ULONG, ulFlags), (ULONG *, lpcValues), (SPropValue **, lppPropArray))
DEF_HRMETHOD1(TRACE_MAPI, ZCABContainer, ABContainer, GetPropList, (ULONG, ulFlags), (LPSPropTagArray *, lppPropTagArray))
DEF_HRMETHOD_NOSUPPORT(TRACE_MAPI, ZCABContainer, ABContainer, OpenProperty, (ULONG, ulPropTag), (LPCIID, lpiid), (ULONG, ulInterfaceOptions), (ULONG, ulFlags), (LPUNKNOWN *, lppUnk))
DEF_HRMETHOD_NOSUPPORT(TRACE_MAPI, ZCABContainer, ABContainer, SetProps, (ULONG, cValues), (const SPropValue *, lpPropArray), (SPropProblemArray **, lppProblems))
DEF_HRMETHOD_NOSUPPORT(TRACE_MAPI, ZCABContainer, ABContainer, DeleteProps, (const SPropTagArray *, lpPropTagArray), (SPropProblemArray **, lppProblems))
DEF_HRMETHOD_NOSUPPORT(TRACE_MAPI, ZCABContainer, ABContainer, CopyTo, (ULONG, ciidExclude), (LPCIID, rgiidExclude), (const SPropTagArray *, lpExcludeProps), (ULONG, ulUIParam), (LPMAPIPROGRESS, lpProgress), (LPCIID, lpInterface), (void *, lpDestObj), (ULONG, ulFlags), (SPropProblemArray **, lppProblems))
DEF_HRMETHOD_NOSUPPORT(TRACE_MAPI, ZCABContainer, ABContainer, CopyProps, (const SPropTagArray *, lpIncludeProps), (ULONG, ulUIParam), (LPMAPIPROGRESS, lpProgress), (LPCIID, lpInterface), (void *, lpDestObj), (ULONG, ulFlags), (SPropProblemArray **, lppProblems))
DEF_HRMETHOD_NOSUPPORT(TRACE_MAPI, ZCABContainer, ABContainer, GetNamesFromIDs, (LPSPropTagArray *, pptaga), (LPGUID, lpguid), (ULONG, ulFlags), (ULONG *, pcNames), (LPMAPINAMEID **, pppNames))
DEF_HRMETHOD_NOSUPPORT(TRACE_MAPI, ZCABContainer, ABContainer, GetIDsFromNames, (ULONG, cNames), (LPMAPINAMEID *, ppNames), (ULONG, ulFlags), (LPSPropTagArray *, pptaga))
