// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1998 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

#include "stdafx.h"
#include <malloc.h>
#include <ole2.h>
#include <oleauto.h>

#ifdef AFXCTL_PROP_SEG
#pragma code_seg(AFXCTL_PROP_SEG)
#endif

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

/////////////////////////////////////////////////////////////////////////////
// Helper functions used by CProperty, CPropertySection, CPropertySet classes

AFX_STATIC LPVOID AFXAPI _AfxCountPrefixedStringA(LPCSTR lpsz)
{
	DWORD cb = (lstrlenA(lpsz) + 1);
	LPDWORD lp = (LPDWORD)malloc((int)cb + sizeof(DWORD));
	if (lp)
	{
		*lp = cb;
		lstrcpyA((LPSTR)(lp+1), lpsz);
	}

	return (LPVOID)lp;
}


#ifndef OLE2ANSI
AFX_STATIC LPVOID AFXAPI _AfxCountPrefixedStringW(LPCWSTR lpsz)
{
	DWORD cb = (wcslen(lpsz) + 1);
	LPDWORD lp = (LPDWORD)malloc((int)cb * sizeof(WCHAR) + sizeof(DWORD));
	if (lp)
	{
		*lp = cb;
		wcscpy((LPWSTR)(lp+1), lpsz);
	}

	return (LPVOID)lp;
}
#endif

#ifdef _UNICODE
#define _AfxCountPrefixedString _AfxCountPrefixedStringW
#else
#define _AfxCountPrefixedString _AfxCountPrefixedStringA
#endif


#ifdef _UNICODE

#define MAX_STRLEN 1024

AFX_STATIC LPBYTE AFXAPI _AfxConvertStringProp(LPBYTE pbProp, DWORD dwType, ULONG nReps,
	size_t cbCharSize)
{
	LPBYTE pbResult = NULL; // Return value
	ULONG cbResult = 0;     // Number of bytes in pbResult
	LPBYTE pbBuffer;        // Temporary holding space
	ULONG cchOrig;          // Number of characters in original string
	ULONG cchCopy;          // Number of characters to copy
	ULONG cbCopy;           // Number of bytes to copy
	LPBYTE pbResultNew;     // Used for realloc of pbResult

	pbBuffer = (LPBYTE)malloc(MAX_STRLEN * cbCharSize);
	if (pbBuffer == NULL)
		return NULL;

	// If it's a vector, the count goes first.
	if (dwType & VT_VECTOR)
	{
		pbResult = (LPBYTE)malloc(sizeof(DWORD));
		if (pbResult == NULL)
		{
			free(pbBuffer);
			return NULL;
		}
		*(LPDWORD)pbResult = nReps;
		cbResult = sizeof(DWORD);
	}

	while (nReps--)
	{
		cchOrig = *(LPDWORD)pbProp;
		pbProp += sizeof(DWORD);

		// Convert multibyte string to Unicode.
		if (cbCharSize == sizeof(WCHAR))
		{
			cchCopy = _mbstowcsz((LPWSTR)pbBuffer, (LPSTR)pbProp,
				min(cchOrig, MAX_STRLEN));
		}
		else
		{
			cchCopy = _wcstombsz((LPSTR)pbBuffer, (LPWSTR)pbProp,
				min(cchOrig, MAX_STRLEN));
		}

		// Allocate space to append string.
		cbCopy = cchCopy * cbCharSize;
		pbResultNew = (LPBYTE)realloc(pbResult, cbResult + sizeof(DWORD) +
			cbCopy);

		// If allocation failed, cleanup and return NULL;
		if (pbResultNew == NULL)
		{
			free(pbResult);
			free(pbBuffer);
			return NULL;
		}

		pbResult = pbResultNew;

		// Copy character count and converted string into place,
		// then update the total size.
		memcpy(pbResult + cbResult, (LPBYTE)&cchCopy, sizeof(DWORD));
		memcpy(pbResult + cbResult + sizeof(DWORD), pbBuffer, cbCopy);
		cbResult += sizeof(DWORD) + cbCopy;

		// Advance to the next vector element
		pbProp += (cchOrig * cbCharSize);
	}

	free(pbBuffer);
	return pbResult;
}

#endif // _UNICODE

/////////////////////////////////////////////////////////////////////////////
// Implementation of the CProperty class

CProperty::CProperty()
{
	m_dwPropID = 0;

	m_dwType = VT_EMPTY;
	m_pValue = NULL;       // must init to NULL
}

CProperty::CProperty(DWORD dwID, const LPVOID pValue, DWORD dwType)
{
	m_dwPropID = dwID;
	m_dwType = dwType;
	m_pValue = NULL;       // must init to NULL
	Set(dwID, pValue, dwType);
}

CProperty::~CProperty()
{
	FreeValue();
}

BOOL CProperty::Set(DWORD dwID, const LPVOID pValue, DWORD dwType)
{
	m_dwType = dwType;
	m_dwPropID = dwID;

	return Set(pValue);
}

BOOL CProperty::Set(const LPVOID pValue, DWORD dwType)
{
	m_dwType = dwType;
	return Set(pValue);
}

BOOL CProperty::Set(const  LPVOID pVal)
{
	ULONG           cb;
	ULONG           cbItem;
	ULONG           cbValue;
	ULONG           nReps;
	LPBYTE          pCur;
	LPVOID          pValue = pVal;
	DWORD           dwType = m_dwType;
	LPVOID          pValueOrig = NULL;

	if (m_pValue != NULL)
	{
		FreeValue();
	}

	if (pValue == NULL || m_dwType == 0)
		return TRUE;

	// Given pValue, determine how big it is
	// Then allocate a new buffer for m_pValue and copy...
	nReps = 1;
	cbValue = 0;
	pCur = (LPBYTE)pValue;
	if (m_dwType & VT_VECTOR)
	{
		// The next DWORD is a count of the elements
		nReps = *(LPDWORD)pValue;
		cb = sizeof(nReps);
		pCur += cb;
		cbValue += cb;
		dwType &= ~VT_VECTOR;
	}
	else
	{
		// If we get any of the string-like types,
		// and we are not a vector create a count-prefixed
		// buffer.
		switch (dwType)
		{
			case VT_LPSTR:          // null terminated string
				pValueOrig = pValue;
				pValue = _AfxCountPrefixedStringA((LPSTR)pValueOrig);
				pCur = (LPBYTE)pValue;
				break;

			case VT_BSTR:           // binary string
			case VT_STREAM:         // Name of the stream follows
			case VT_STORAGE:        // Name of the storage follows
			case VT_STREAMED_OBJECT:// Stream contains an object
			case VT_STORED_OBJECT:  // Storage contains an object
				pValueOrig = pValue;
				pValue = _AfxCountPrefixedString((LPTSTR)pValueOrig);
				pCur = (LPBYTE)pValue;
				break;

#ifndef OLE2ANSI
			case VT_LPWSTR:         // UNICODE string
				pValueOrig = pValue;
				pValue = _AfxCountPrefixedStringW((LPWSTR)pValueOrig);
				pCur = (LPBYTE)pValue;
				break;
#endif
		}
	}

	// Since a value can be made up of a vector (VT_VECTOR) of
	// items, we first seek through the value, picking out
	// each item, getting it's size.
	//
	cbItem = 0;        // Size of the current item
	while (nReps--)
	{
		switch (dwType)
		{
			case VT_EMPTY:          // nothing
				cbItem = 0;
				break;

			case VT_I2:             // 2 byte signed int
			case VT_BOOL:           // True=-1, False=0
				cbItem = 2;
				break;

			case VT_I4:             // 4 byte signed int
			case VT_R4:             // 4 byte real
				cbItem = 4;
				break;

			case VT_R8:             // 8 byte real
			case VT_CY:             // currency
			case VT_DATE:           // date
			case VT_I8:             // signed 64-bit int
			case VT_FILETIME:       // FILETIME
				cbItem = 8;
				break;

			case VT_CLSID:          // A Class ID
				cbItem = sizeof(CLSID);
				break;

#ifndef _UNICODE
			case VT_BSTR:           // binary string
			case VT_STREAM:         // Name of the stream follows
			case VT_STORAGE:        // Name of the storage follows
			case VT_STREAMED_OBJECT:// Stream contains an object
			case VT_STORED_OBJECT:  // Storage contains an object
			case VT_STREAMED_PROPSET:// Stream contains a propset
			case VT_STORED_PROPSET: // Storage contains a propset
#endif // _UNICODE
			case VT_LPSTR:          // null terminated string
			case VT_BLOB_OBJECT:    // Blob contains an object
			case VT_BLOB_PROPSET:   // Blob contains a propset
			case VT_BLOB:           // Length prefixed bytes
			case VT_CF:             // Clipboard format
				// Get the DWORD that gives us the size, making
				// sure we increment cbValue.
				cbItem = *(LPDWORD)pCur;
				cb = sizeof(cbItem);
				pCur += cb;
				cbValue += cb;
				break;

#ifdef _UNICODE
			case VT_BSTR:           // binary string
			case VT_STREAM:         // Name of the stream follows
			case VT_STORAGE:        // Name of the storage follows
			case VT_STREAMED_OBJECT:// Stream contains an object
			case VT_STORED_OBJECT:  // Storage contains an object
			case VT_STREAMED_PROPSET:// Stream contains a propset
			case VT_STORED_PROPSET: // Storage contains a propset
#endif // _UNICODE
			case VT_LPWSTR:         // UNICODE string
				cbItem = *(LPDWORD)pCur * sizeof(WCHAR);
				cb = sizeof(cbItem);
				pCur += cb;
				cbValue += cb;
				break;

			default:
				if (pValueOrig)
					free(pValue);
				return FALSE;
		}

		// Seek to the next item
		pCur += cbItem;
		cbValue += cbItem;
	}

	if (NULL == AllocValue(cbValue))
	{
		TRACE0("CProperty::AllocValue failed");
		return FALSE;
	}
	memcpy(m_pValue, pValue, (int)cbValue);

	if (pValueOrig)
		free(pValue);

	return TRUE;
}

LPVOID CProperty::Get()
{   return Get((DWORD*)NULL);   }

LPVOID CProperty::Get(DWORD* pcb)
{
	DWORD   cb;
	LPBYTE  p = NULL;

	p = (LPBYTE)m_pValue;

	// m_pValue points to a Property "Value" which may
	// have size information included...
	switch (m_dwType)
	{
		case VT_EMPTY:          // nothing
			cb = 0;
			break;

		case VT_I2:             // 2 byte signed int
		case VT_BOOL:           // True=-1, False=0
			cb = 2;
			break;

		case VT_I4:             // 4 byte signed int
		case VT_R4:             // 4 byte real
			cb = 4;
			break;

		case VT_R8:             // 8 byte real
		case VT_CY:             // currency
		case VT_DATE:           // date
		case VT_I8:             // signed 64-bit int
		case VT_FILETIME:       // FILETIME
			cb = 8;
			break;

#ifndef _UNICODE
		case VT_BSTR:           // binary string
		case VT_STREAM:         // Name of the stream follows
		case VT_STORAGE:        // Name of the storage follows
		case VT_STREAMED_OBJECT:// Stream contains an object
		case VT_STORED_OBJECT:  // Storage contains an object
		case VT_STREAMED_PROPSET:// Stream contains a propset
		case VT_STORED_PROPSET: // Storage contains a propset
#endif // UNICODE
		case VT_LPSTR:          // null terminated string
		case VT_CF:             // Clipboard format
			// Read the DWORD that gives us the size, making
			// sure we increment cbValue.
			cb = *(LPDWORD)p;
			p += sizeof(DWORD);
			break;

		case VT_BLOB:           // Length prefixed bytes
		case VT_BLOB_OBJECT:    // Blob contains an object
		case VT_BLOB_PROPSET:   // Blob contains a propset
			// Read the DWORD that gives us the size.
			cb = *(LPDWORD)p;
			break;

#ifdef _UNICODE
		case VT_BSTR:           // binary string
		case VT_STREAM:         // Name of the stream follows
		case VT_STORAGE:        // Name of the storage follows
		case VT_STREAMED_OBJECT:// Stream contains an object
		case VT_STORED_OBJECT:  // Storage contains an object
		case VT_STREAMED_PROPSET:// Stream contains a propset
		case VT_STORED_PROPSET: // Storage contains a propset
#endif // _UNICODE
		case VT_LPWSTR:         // UNICODE string
			cb = *(LPDWORD)p * sizeof(WCHAR);
			p += sizeof(DWORD);
			break;

		case VT_CLSID:          // A Class ID
			cb = sizeof(CLSID);
			break;

		default:
			return NULL;
	}
	if (pcb != NULL)
		*pcb = cb;

	return p;
}

DWORD  CProperty::GetType()
{   return m_dwType;  }

void   CProperty::SetType(DWORD dwType)
{   m_dwType = dwType; }

DWORD CProperty::GetID()
{   return m_dwPropID;   }

void CProperty::SetID(DWORD dwPropID)
{    m_dwPropID = dwPropID;   }

LPVOID CProperty::GetRawValue()
{   return m_pValue; }

BOOL CProperty::WriteToStream(IStream* pIStream)
{
	ULONG           cb;
	ULONG           cbTotal; // Total size of the whole value
	DWORD           dwType = m_dwType;
	DWORD           nReps;
	LPBYTE          pValue;
	LPBYTE          pCur;
	BOOL            bSuccess = FALSE;
	BYTE            b = 0;

	nReps = 1;
	pValue = (LPBYTE)m_pValue;
	pCur = pValue;
	cbTotal = 0;
	if (m_dwType & VT_VECTOR)
	{
		// Value is a DWORD count of elements followed by
		// that many repititions of the value.
		//
		nReps = *(LPDWORD)pCur;
		cbTotal = sizeof(DWORD);
		pCur += cbTotal;
		dwType &= ~VT_VECTOR;
	}

#ifdef _UNICODE
	switch (dwType)
	{
		case VT_BSTR:           // binary string
		case VT_STREAM:         // Name of the stream follows
		case VT_STORAGE:        // Name of the storage follows
		case VT_STREAMED_OBJECT:// Stream contains an object
		case VT_STORED_OBJECT:  // Storage contains an object
		case VT_STREAMED_PROPSET:// Stream contains a propset
		case VT_STORED_PROPSET: // Storage contains a propset
			pValue = _AfxConvertStringProp(pCur, m_dwType, nReps, sizeof(char));
			if (m_dwType & VT_VECTOR)
				pCur = pValue + sizeof(DWORD);
			break;
	}
#endif // _UNICODE

	// Figure out how big the data is.
	while (nReps--)
	{
		switch (dwType)
		{
			case VT_EMPTY:          // nothing
				cb = 0;
				break;

			case VT_I2:             // 2 byte signed int
			case VT_BOOL:           // True=-1, False=0
				cb = 2;
				break;

			case VT_I4:             // 4 byte signed int
			case VT_R4:             // 4 byte real
				cb = 4;
				break;

			case VT_R8:             // 8 byte real
			case VT_CY:             // currency
			case VT_DATE:           // date
			case VT_I8:             // signed 64-bit int
			case VT_FILETIME:       // FILETIME
				cb = 8;
				break;

			case VT_LPSTR:          // null terminated string
			case VT_BSTR:           // binary string
			case VT_STREAM:         // Name of the stream follows
			case VT_STORAGE:        // Name of the storage follows
			case VT_STREAMED_OBJECT:// Stream contains an object
			case VT_STORED_OBJECT:  // Storage contains an object
			case VT_STREAMED_PROPSET:// Stream contains a propset
			case VT_STORED_PROPSET: // Storage contains a propset
			case VT_BLOB:           // Length prefixed bytes
			case VT_BLOB_OBJECT:    // Blob contains an object
			case VT_BLOB_PROPSET:   // Blob contains a propset
			case VT_CF:             // Clipboard format
				cb = sizeof(DWORD) + *(LPDWORD)pCur;
				break;

			case VT_LPWSTR:         // UNICODE string
				cb = sizeof(DWORD) + (*(LPDWORD)pCur * sizeof(WCHAR));
				break;

			case VT_CLSID:          // A Class ID
				cb = sizeof(CLSID);
				break;

			default:
				return FALSE;
		}

		pCur += cb;
		cbTotal+= cb;
	}

	// Write the type
	pIStream->Write((LPVOID)&m_dwType, sizeof(m_dwType), &cb);
	if (cb != sizeof(m_dwType))
		goto Cleanup;

	// Write the value
	pIStream->Write((LPVOID)pValue, cbTotal, &cb);
	if (cb != cbTotal)
		goto Cleanup;

	// Make sure we are 32 bit aligned
	cbTotal = (((cbTotal + 3) >> 2) << 2) - cbTotal;
	while (cbTotal--)
	{
		pIStream->Write((LPVOID)&b, 1, &cb);
		if (cb != sizeof(BYTE))
			goto Cleanup;
	}

	bSuccess = TRUE;

Cleanup:
	if (pValue != m_pValue)
		free(pValue);

	return bSuccess;
}

BOOL CProperty::ReadFromStream(IStream* pIStream)
{
	ULONG           cb;
	ULONG           cbItem;
	ULONG           cbValue;
	DWORD           dwType;
	ULONG           nReps;
	ULONG           iReps;
	LPSTREAM        pIStrItem;
	LARGE_INTEGER   li;

	// All properties are made up of a type/value pair.
	// The obvious first thing to do is to get the type...
	pIStream->Read((LPVOID)&m_dwType, sizeof(m_dwType), &cb);
	if (cb != sizeof(m_dwType))
		return FALSE;

	dwType = m_dwType;
	nReps = 1;
	cbValue = 0;
	if (m_dwType & VT_VECTOR)
	{
		// The next DWORD in the stream is a count of the
		// elements
		pIStream->Read((LPVOID)&nReps, sizeof(nReps), &cb);
		if (cb != sizeof(nReps))
			return FALSE;
		cbValue += cb;
		dwType &= ~VT_VECTOR;
	}

	// Since a value can be made up of a vector (VT_VECTOR) of
	// items, we first seek through the value, picking out
	// each item, getting it's size.  We use a cloned
	// stream for this (pIStrItem).
	// We then use our pIStream to read the entire 'blob' into
	// the allocated buffer.
	//
	cbItem = 0;        // Size of the current item
	pIStream->Clone(&pIStrItem);
	ASSERT(pIStrItem != NULL);
	iReps = nReps;
	while (iReps--)
	{
		switch (dwType)
		{
			case VT_EMPTY:          // nothing
				cbItem = 0;
				break;

			case VT_I2:             // 2 byte signed int
			case VT_BOOL:           // True=-1, False=0
				cbItem = 2;
				break;

			case VT_I4:             // 4 byte signed int
			case VT_R4:             // 4 byte real
				cbItem = 4;
				break;

			case VT_R8:             // 8 byte real
			case VT_CY:             // currency
			case VT_DATE:           // date
			case VT_I8:             // signed 64-bit int
			case VT_FILETIME:       // FILETIME
				cbItem = 8;
				break;

			case VT_LPSTR:          // null terminated string
			case VT_BSTR:           // binary string
			case VT_STREAM:         // Name of the stream follows
			case VT_STORAGE:        // Name of the storage follows
			case VT_STREAMED_OBJECT:// Stream contains an object
			case VT_STORED_OBJECT:  // Storage contains an object
			case VT_STREAMED_PROPSET:// Stream contains a propset
			case VT_STORED_PROPSET: // Storage contains a propset
			case VT_BLOB:           // Length prefixed bytes
			case VT_BLOB_OBJECT:    // Blob contains an object
			case VT_BLOB_PROPSET:   // Blob contains a propset
			case VT_CF:             // Clipboard format
				// Read the DWORD that gives us the size, making
				// sure we increment cbValue.
				pIStream->Read((LPVOID)&cbItem, sizeof(cbItem), &cb);
				if (cb != sizeof(cbItem))
					return FALSE;
				LISet32(li, -(LONG)cb);
				pIStream->Seek(li, STREAM_SEEK_CUR, NULL);
				cbValue += cb;
				break;

			case VT_LPWSTR:         // UNICODE string
				pIStream->Read((LPVOID)&cbItem, sizeof(cbItem), &cb);
				if (cb != sizeof(cbItem))
					return FALSE;
				LISet32(li, -(LONG)cb);
				pIStream->Seek(li, STREAM_SEEK_CUR, NULL);
				cbValue += cb;
				cbItem *= sizeof(WCHAR);
				break;

			case VT_CLSID:          // A Class ID
				cbItem = sizeof(CLSID);
				break;

			default:
				pIStrItem->Release();
				return FALSE;
		}

		// Seek to the next item
		LISet32(li, cbItem);
		pIStrItem->Seek(li, STREAM_SEEK_CUR, NULL);
		cbValue += cbItem;
	}

	pIStrItem->Release();

#ifdef _UNICODE
	LPBYTE pTmp;

	switch (dwType)
	{
		case VT_BSTR:           // binary string
		case VT_STREAM:         // Name of the stream follows
		case VT_STORAGE:        // Name of the storage follows
		case VT_STREAMED_OBJECT:// Stream contains an object
		case VT_STORED_OBJECT:  // Storage contains an object
		case VT_STREAMED_PROPSET:// Stream contains a propset
		case VT_STORED_PROPSET: // Storage contains a propset
			pTmp = (LPBYTE)malloc((int)cbValue);
			pIStream->Read(pTmp, cbValue, &cb);
			m_pValue = _AfxConvertStringProp(pTmp, m_dwType, nReps, sizeof(WCHAR));
			free(pTmp);
			break;

		default:
#endif // _UNICODE
			// Allocate cbValue bytes
			if (NULL == AllocValue(cbValue))
				return FALSE;

			// Read the buffer from pIStream
			pIStream->Read(m_pValue, cbValue, &cb);
			if (cb != cbValue)
				return FALSE;
#ifdef _UNICODE
			break;
	}
#endif // _UNICODE

	// Done!
	return TRUE;
}


LPVOID CProperty::AllocValue(ULONG cb)
{
	return m_pValue = malloc((int)cb);
}


void CProperty::FreeValue()
{
	if (m_pValue != NULL)
	{
		free(m_pValue);
		m_pValue = NULL;
	}
}

/////////////////////////////////////////////////////////////////////////////
// Implementation of the CPropertySection class

CPropertySection::CPropertySection()
{
	m_FormatID = GUID_NULL;
	m_SH.cbSection = 0;
	m_SH.cProperties = 0;
}

CPropertySection::CPropertySection(CLSID FormatID)
{
	m_FormatID = FormatID;
	m_SH.cbSection = 0;
	m_SH.cProperties = 0;
}

CPropertySection::~CPropertySection()
{
	RemoveAll();
	return;
}

CLSID CPropertySection::GetFormatID()
{   return m_FormatID; }

void CPropertySection::SetFormatID(CLSID FormatID)
{   m_FormatID = FormatID; }

BOOL CPropertySection::Set(DWORD dwPropID, LPVOID pValue, DWORD dwType)
{
	CProperty* pProp = GetProperty(dwPropID);
	if (pProp == NULL)
	{
		if ((pProp = new CProperty(dwPropID, pValue, dwType)) != NULL)
			AddProperty(pProp);
		return (pProp != NULL);
	}

	pProp->Set(dwPropID, pValue, dwType);
	return TRUE;
}

BOOL CPropertySection::Set(DWORD dwPropID, LPVOID pValue)
{
	// Since no dwType was specified, the property is assumed
	// to exist.   Fail if it does not.
	CProperty* pProp = GetProperty(dwPropID);
	if (pProp != NULL && pProp->m_dwType)
	{
		pProp->Set(dwPropID, pValue, pProp->m_dwType);
		return TRUE;
	}
	else
		return FALSE;
}

LPVOID CPropertySection::Get(DWORD dwPropID)
{   return Get(dwPropID, (DWORD*)NULL);  }

LPVOID CPropertySection::Get(DWORD dwPropID, DWORD* pcb)
{
	CProperty* pProp = GetProperty(dwPropID);
	if (pProp)
		return pProp->Get(pcb);
	else
		return NULL;
}

void CPropertySection::Remove(DWORD dwID)
{
	POSITION pos = m_PropList.GetHeadPosition();
	CProperty* pProp;
	while (pos != NULL)
	{
		POSITION posRemove = pos;
		pProp = (CProperty*)m_PropList.GetNext(pos);
		if (pProp->m_dwPropID == dwID)
		{
			m_PropList.RemoveAt(posRemove);
			delete pProp;
			m_SH.cProperties--;
			return;
		}
	}
}

void CPropertySection::RemoveAll()
{
	POSITION pos = m_PropList.GetHeadPosition();
	while (pos != NULL)
		delete (CProperty*)m_PropList.GetNext(pos);
	m_PropList.RemoveAll();
	m_SH.cProperties = 0;
}


CProperty* CPropertySection::GetProperty(DWORD dwPropID)
{
	POSITION pos = m_PropList.GetHeadPosition();
	CProperty* pProp;
	while (pos != NULL)
	{
		pProp= (CProperty*)m_PropList.GetNext(pos);
		if (pProp->m_dwPropID == dwPropID)
			return pProp;
	}
	return NULL;
}

void CPropertySection::AddProperty(CProperty* pProp)
{
	m_PropList.AddTail(pProp);
	m_SH.cProperties++;
}

DWORD CPropertySection::GetSize()
{   return m_SH.cbSection; }

DWORD CPropertySection::GetCount()
{   return m_PropList.GetCount();  }

CPtrList* CPropertySection::GetList()
{   return &m_PropList;  }

BOOL CPropertySection::WriteToStream(IStream* pIStream)
{
	// Create a dummy property entry for the name dictionary (ID == 0).
	Set(0, NULL, VT_EMPTY);

	ULONG           cb;
	ULARGE_INTEGER  ulSeekOld;
	ULARGE_INTEGER  ulSeek;
	LPSTREAM        pIStrPIDO;
	PROPERTYIDOFFSET  pido;
	LARGE_INTEGER   li;

	// The Section header contains the number of bytes in the
	// section.  Thus we need  to go back to where we should
	// write the count of bytes
	// after we write all the property sets..
	// We accomplish this by saving the seek pointer to where
	// the size should be written in ulSeekOld
	m_SH.cbSection = 0;
	m_SH.cProperties = m_PropList.GetCount();
	LISet32(li, 0);
	pIStream->Seek(li, STREAM_SEEK_CUR, &ulSeekOld);

	pIStream->Write((LPVOID)&m_SH, sizeof(m_SH), &cb);
	if (sizeof(m_SH) != cb)
	{
		TRACE0("Write of section header failed (1).\n");
		return FALSE;
	}

	if (m_PropList.IsEmpty())
	{
		TRACE0("Warning: Wrote empty property section.\n");
		return TRUE;
	}

	// After the section header is the list of property ID/Offset pairs
	// Since there is an ID/Offset pair for each property and we
	// need to write the ID/Offset pair as we write each property
	// we clone the stream and use the clone to access the
	// table of ID/offset pairs (PIDO)...
	//
	pIStream->Clone(&pIStrPIDO);

	// Now seek pIStream past the PIDO list
	//
	LISet32(li,  m_SH.cProperties * sizeof(PROPERTYIDOFFSET));
	pIStream->Seek(li, STREAM_SEEK_CUR, &ulSeek);

	// Now write each section to pIStream.
	CProperty* pProp = NULL;
	POSITION pos = m_PropList.GetHeadPosition();
	while (pos != NULL)
	{
		// Get next element (note cast)
		pProp = (CProperty*)m_PropList.GetNext(pos);

		if (pProp->m_dwPropID != 0)
		{
			// Write it
			if (!pProp->WriteToStream(pIStream))
			{
				pIStrPIDO->Release();
				return FALSE;
			}
		}
		else
		{
			if (!WriteNameDictToStream(pIStream))
			{
				pIStrPIDO->Release();
				return FALSE;
			}
		}

		// Using our cloned stream write the Format ID / Offset pair
		// The offset to this property is the current seek pointer
		// minus the pointer to the beginning of the section
		pido.dwOffset = ulSeek.LowPart - ulSeekOld.LowPart;
		pido.propertyID = pProp->m_dwPropID;
		pIStrPIDO->Write((LPVOID)&pido, sizeof(pido), &cb);
		if (sizeof(pido) != cb)
		{
			TRACE0("Write of 'pido' failed\n");
			pIStrPIDO->Release();
			return FALSE;
		}

		// Get the seek offset after the write
		LISet32(li, 0);
		pIStream->Seek(li, STREAM_SEEK_CUR, &ulSeek);
	}

	pIStrPIDO->Release();

	// Now go back to ulSeekOld and write the section header.
	// Size of section is current seek point minus old seek point
	//
	m_SH.cbSection = ulSeek.LowPart - ulSeekOld.LowPart;

	// Seek to beginning of this section and write the section header.
	LISet32(li, ulSeekOld.LowPart);
	pIStream->Seek(li, STREAM_SEEK_SET, NULL);
	pIStream->Write((LPVOID)&m_SH, sizeof(m_SH), &cb);
	if (sizeof(m_SH) != cb)
	{
		TRACE0("Write of section header failed (2).\n");
		return FALSE;
	}

	// Now seek to end of of the now written section
	LISet32(li, ulSeek.LowPart);
	pIStream->Seek(li, STREAM_SEEK_SET, NULL);

	return TRUE;
}

BOOL CPropertySection::ReadFromStream(IStream* pIStream,
	LARGE_INTEGER liPropSet)
{
	ULONG               cb;
	PROPERTYIDOFFSET    pido;
	ULONG               cProperties;
	LPSTREAM            pIStrPIDO;
	ULARGE_INTEGER      ulSectionStart;
	LARGE_INTEGER       li;
	CProperty*          pProp;

	if (m_SH.cProperties || !m_PropList.IsEmpty())
		RemoveAll();

	// pIStream is pointing to the beginning of the section we
	// are to read.  First there is a DWORD that is the count
	// of bytes in this section, then there is a count
	// of properties, followed by a list of propertyID/offset pairs,
	// followed by type/value pairs.
	//
	LISet32(li, 0);
	pIStream->Seek(li, STREAM_SEEK_CUR, &ulSectionStart);
	pIStream->Read((LPVOID)&m_SH, sizeof(m_SH), &cb);
	if (cb != sizeof(m_SH))
		return FALSE;

	// Now we're pointing at the first of the PropID/Offset pairs
	// (PIDOs).   To get to each property we use a cloned stream
	// to stay back and point at the PIDOs (pIStrPIDO).  We seek
	// pIStream to each of the Type/Value pairs, creating CProperites
	// and so forth as we go...
	//
	pIStream->Clone(&pIStrPIDO);

	cProperties = m_SH.cProperties;
	while (cProperties--)
	{
		pIStrPIDO->Read((LPVOID)&pido, sizeof(pido), &cb);
		if (cb != sizeof(pido))
		{
			pIStrPIDO->Release();
			return FALSE;
		}

		// Do a seek from the beginning of the property set.
		LISet32(li, ulSectionStart.LowPart + pido.dwOffset);
		pIStream->Seek(liPropSet, STREAM_SEEK_SET, NULL);
		pIStream->Seek(li, STREAM_SEEK_CUR, NULL);

		// Now pIStream is at the type/value pair
		if (pido.propertyID != 0)
		{
			pProp = new CProperty(pido.propertyID, NULL, 0);
			pProp->ReadFromStream(pIStream);
			m_PropList.AddTail(pProp);
		}
		else
		{
			ReadNameDictFromStream(pIStream);
		}
	}

	pIStrPIDO->Release();

	return TRUE;
}

BOOL CPropertySection::GetID(LPCTSTR pszName, DWORD* pdwPropID)
{
	CString strName(pszName);
	strName.MakeLower();        // Dictionary stores all names in lowercase

	void* pvID;
	if (m_NameDict.Lookup(strName, pvID))
	{
		*pdwPropID = (DWORD)pvID;
		return TRUE;
	}

	// Failed to find entry in dictionary
	return FALSE;
}

BOOL CPropertySection::SetName(DWORD dwPropID, LPCTSTR pszName)
{
	BOOL bSuccess = TRUE;
	CString strName(pszName);
	strName.MakeLower();        // Dictionary stores all names in lowercase

	TRY
	{
		void* pDummy;
		BOOL bNameExists = m_NameDict.Lookup(strName, pDummy);

		ASSERT(!bNameExists);  // Property names must be unique.

		if (bNameExists)
			bSuccess = FALSE;
		else
			m_NameDict.SetAt(strName, (void*)dwPropID);
	}
	CATCH (CException, e)
	{
		TRACE0("Failed to add entry to dictionary.\n");
		bSuccess = FALSE;
	}
	END_CATCH

	return bSuccess;
}

struct DICTENTRYHEADER
{
	DWORD dwPropID;
	DWORD cb;
};

struct DICTENTRY
{
	DICTENTRYHEADER hdr;
	char sz[256];
};

BOOL CPropertySection::ReadNameDictFromStream(IStream* pIStream)
{
	ULONG cb;
	ULONG cbRead = 0;

	// Read dictionary header (count).
	ULONG cProperties = 0;
	pIStream->Read((LPVOID)&cProperties, sizeof(cProperties), &cb);
	if (sizeof(cProperties) != cb)
	{
		TRACE0("Read of dictionary header failed.\n");
		return FALSE;
	}

	ULONG iProp;
	DICTENTRY entry;

	for (iProp = 0; iProp < cProperties; iProp++)
	{
		// Read entry header (dwPropID, cch).
		if (FAILED(pIStream->Read((LPVOID)&entry, sizeof(DICTENTRYHEADER),
			&cbRead)) ||
			(sizeof(DICTENTRYHEADER) != cbRead))
		{
			TRACE0("Read of dictionary entry failed.\n");
			return FALSE;
		}

		// Read entry data (name).

		cb = entry.hdr.cb;

		if (FAILED(pIStream->Read((LPVOID)&entry.sz, cb, &cbRead)) ||
			(cbRead != cb))
		{
			TRACE0("Read of dictionary entry failed.\n");
			return FALSE;
		}

		LPTSTR pszName;

#ifdef _UNICODE
		// Persistent form is always ANSI/DBCS.  Convert to Unicode.
		WCHAR wszName[256];
		_mbstowcsz(wszName, entry.sz, 256);
		pszName = wszName;
#else // _UNICODE
		pszName = entry.sz;
#endif // _UNICODE

		// Section's "name" appears first in list and has dwPropID == 0.
		if ((iProp == 0) && (entry.hdr.dwPropID == 0))
			m_strSectionName = pszName;             // Section name
		else
			SetName(entry.hdr.dwPropID, pszName);   // Some other property
	}

	return TRUE;
}

AFX_STATIC BOOL AFXAPI _AfxWriteNameDictEntry(IStream* pIStream, DWORD dwPropID, CString& strName)
{
	ULONG cb;
	ULONG cbWritten = 0;
	DICTENTRY entry;

	entry.hdr.dwPropID = dwPropID;
	entry.hdr.cb = min(strName.GetLength() + 1, 255);
#ifdef _UNICODE
	// Persistent form is always ANSI/DBCS.  Convert from Unicode.
	_wcstombsz(entry.sz, (LPCWSTR)strName, 256);
#else // _UNICODE
	memcpy(entry.sz, (LPCSTR)strName, (size_t)entry.hdr.cb);
#endif // _UNICODE

	cb = sizeof(DICTENTRYHEADER) + entry.hdr.cb;

	if (FAILED(pIStream->Write((LPVOID)&entry, cb, &cbWritten)) ||
		(cbWritten != cb))
	{
		TRACE0("Write of dictionary entry failed.\n");
		return FALSE;
	}

	return TRUE;
}

BOOL CPropertySection::WriteNameDictToStream(IStream* pIStream)
{
	ULONG cb;

	// Write dictionary header (count).
	ULONG cProperties = m_NameDict.GetCount() + 1;
	pIStream->Write((LPVOID)&cProperties, sizeof(cProperties), &cb);
	if (sizeof(cProperties) != cb)
	{
		TRACE0("Write of dictionary header failed.\n");
		return FALSE;
	}

	POSITION pos;
	CString strName;
	void* pvID;

	// Write out section's "name" with dwPropID == 0 first
	if (!_AfxWriteNameDictEntry(pIStream, 0, m_strSectionName))
		return FALSE;

	// Enumerate contents of dictionary and write out (dwPropID, cb, name).
	pos = m_NameDict.GetStartPosition();
	while (pos != NULL)
	{
		m_NameDict.GetNextAssoc(pos, strName, pvID);
		if (!_AfxWriteNameDictEntry(pIStream, (DWORD)pvID, strName))
			return FALSE;
	}

	return TRUE;
}

BOOL CPropertySection::SetSectionName(LPCTSTR pszName)
{
	m_strSectionName = pszName;
	return TRUE;
}

LPCTSTR CPropertySection::GetSectionName()
{
	return (LPCTSTR)m_strSectionName;
}

/////////////////////////////////////////////////////////////////////////////
// Implementation of the CPropertySet class

CPropertySet::CPropertySet()
{
	m_PH.wByteOrder = 0xFFFE;
	m_PH.wFormat = 0;
	m_PH.dwOSVer = (DWORD)MAKELONG(LOWORD(GetVersion()), 2);
	m_PH.clsID =  GUID_NULL;
	m_PH.cSections = 0;

}

CPropertySet::CPropertySet(CLSID clsID)
{
	m_PH.wByteOrder = 0xFFFE;
	m_PH.wFormat = 0;
	m_PH.dwOSVer = (DWORD)MAKELONG(LOWORD(GetVersion()), 2);
	m_PH.clsID = clsID;
	m_PH.cSections = 0;
}

CPropertySet::~CPropertySet()
{   RemoveAll();  }

BOOL CPropertySet::Set(CLSID FormatID, DWORD dwPropID, LPVOID pValue, DWORD dwType)
{
	CPropertySection* pSect = GetSection(FormatID);
	if (pSect == NULL)
	{
		if ((pSect = new CPropertySection(FormatID)) != NULL)
			AddSection(pSect);
	}
	pSect->Set(dwPropID, pValue, dwType);
	return TRUE;
}

BOOL CPropertySet::Set(CLSID FormatID, DWORD dwPropID, LPVOID pValue)
{
	// Since there is no dwType, we have to assume that the property
	// already exists.  If it doesn't, fail.
	CPropertySection* pSect = GetSection(FormatID);
	if (pSect != NULL)
		return pSect->Set(dwPropID, pValue);
	else
		return FALSE;
}

LPVOID CPropertySet::Get(CLSID FormatID, DWORD dwPropID, DWORD* pcb)
{
	CPropertySection* pSect = GetSection(FormatID);
	if (pSect)
		return pSect->Get(dwPropID, pcb);
	else
		return NULL;
}

LPVOID CPropertySet::Get(CLSID FormatID, DWORD dwPropID)
{   return Get(FormatID, dwPropID, (DWORD*)NULL); }

void CPropertySet::Remove(CLSID FormatID, DWORD dwPropID)
{
	CPropertySection*  pSect = GetSection(FormatID);
	if (pSect)
		pSect->Remove(dwPropID);
}

void CPropertySet::Remove(CLSID FormatID)
{
	CPropertySection* pSect;
	POSITION posRemove = m_SectionList.GetHeadPosition();
	POSITION pos = posRemove;
	while (posRemove != NULL)
	{
		pSect = (CPropertySection*)m_SectionList.GetNext(pos);
		if (IsEqualCLSID(pSect->m_FormatID, FormatID))
		{
			m_SectionList.RemoveAt(posRemove);
			delete pSect;
			m_PH.cSections--;
			return;
		}
		posRemove = pos;
	}
}

void CPropertySet::RemoveAll()
{
	POSITION pos = m_SectionList.GetHeadPosition();
	while (pos != NULL)
	{
		delete (CPropertySection*)m_SectionList.GetNext(pos);
	}
	m_SectionList.RemoveAll();
	m_PH.cSections = 0;
}

CPropertySection* CPropertySet::GetSection(CLSID FormatID)
{
	POSITION pos = m_SectionList.GetHeadPosition();
	CPropertySection* pSect;
	while (pos != NULL)
	{
		pSect = (CPropertySection*)m_SectionList.GetNext(pos);
		if (IsEqualCLSID(pSect->m_FormatID, FormatID))
			return pSect;
	}
	return NULL;
}

CPropertySection* CPropertySet::AddSection(CLSID FormatID)
{
	CPropertySection* pSect = GetSection(FormatID);
	if (pSect)
		return pSect;

	pSect = new CPropertySection(FormatID);
	if (pSect)
		AddSection(pSect);
	return pSect;
}

void CPropertySet::AddSection(CPropertySection* pSect)
{
	m_SectionList.AddTail(pSect);
	m_PH.cSections++;
}

CProperty* CPropertySet::GetProperty(CLSID FormatID, DWORD dwPropID)
{
	CPropertySection* pSect = GetSection(FormatID);
	if (pSect)
		return pSect->GetProperty(dwPropID);
	else
		return NULL;
}

void CPropertySet::AddProperty(CLSID FormatID, CProperty* pProp)
{
	CPropertySection* pSect = GetSection(FormatID);
	if (pSect)
		pSect->AddProperty(pProp);
}

WORD CPropertySet::GetByteOrder()
{   return m_PH.wByteOrder;  }

WORD CPropertySet::GetFormatVersion()
{   return m_PH.wFormat;  }

void CPropertySet::SetFormatVersion(WORD wFmtVersion)
{   m_PH.wFormat = wFmtVersion;  }

DWORD CPropertySet::GetOSVersion()
{   return m_PH.dwOSVer;  }

void CPropertySet::SetOSVersion(DWORD dwOSVer)
{   m_PH.dwOSVer = dwOSVer;  }

CLSID CPropertySet::GetClassID()
{   return m_PH.clsID;  }

void CPropertySet::SetClassID(CLSID clsID)
{   m_PH.clsID = clsID;  }

DWORD CPropertySet::GetCount()
{   return m_SectionList.GetCount();  }

CPtrList* CPropertySet::GetList()
{   return &m_SectionList;  }


BOOL CPropertySet::WriteToStream(IStream* pIStream)
{
	LPSTREAM        pIStrFIDO;
	FORMATIDOFFSET  fido;
	ULONG           cb;
	ULARGE_INTEGER  ulSeek;
	LARGE_INTEGER   li;

	// Write the Property List Header
	m_PH.cSections = m_SectionList.GetCount();
	pIStream->Write((LPVOID)&m_PH, sizeof(m_PH), &cb);
	if (sizeof(m_PH) != cb)
	{
		TRACE0("Write of Property Set Header failed.\n");
		return FALSE;
	}

	if (m_SectionList.IsEmpty())
	{
		TRACE0("Warning: Wrote empty property set.\n");
		return TRUE;
	}

	// After the header is the list of Format ID/Offset pairs
	// Since there is an ID/Offset pair for each section and we
	// need to write the ID/Offset pair as we write each section
	// we clone the stream and use the clone to access the
	// table of ID/offset pairs (FIDO)...
	//
	pIStream->Clone(&pIStrFIDO);

	// Now seek pIStream past the FIDO list
	//
	LISet32(li, m_PH.cSections * sizeof(FORMATIDOFFSET));
	pIStream->Seek(li, STREAM_SEEK_CUR, &ulSeek);

	// Write each section.
	CPropertySection*   pSect = NULL;
	POSITION            pos = m_SectionList.GetHeadPosition();
	while (pos != NULL)
	{
		// Get next element (note cast)
		pSect = (CPropertySection*)m_SectionList.GetNext(pos);

		// Write it
		if (!pSect->WriteToStream(pIStream))
		{
			pIStrFIDO->Release();
			return FALSE;
		}

		// Using our cloned stream write the Format ID / Offset pair
		fido.formatID = pSect->m_FormatID;
		fido.dwOffset = ulSeek.LowPart;
		pIStrFIDO->Write((LPVOID)&fido, sizeof(fido), &cb);
		if (sizeof(fido) != cb)
		{
			TRACE0("Write of 'fido' failed.\n");
			pIStrFIDO->Release();
			return FALSE;
		}

		// Get the seek offset (for pIStream) after the write
		LISet32(li, 0);
		pIStream->Seek(li, STREAM_SEEK_CUR, &ulSeek);
	}

	pIStrFIDO->Release();

	return TRUE;
}

BOOL CPropertySet::ReadFromStream(IStream* pIStream)
{
	ULONG               cb;
	FORMATIDOFFSET      fido;
	ULONG               cSections;
	LPSTREAM            pIStrFIDO;
	CPropertySection*   pSect;
	LARGE_INTEGER       li;
	LARGE_INTEGER       liPropSet;

	// Save the stream position at which the property set starts.
	LARGE_INTEGER liZero = {0,0};
	pIStream->Seek(liZero, STREAM_SEEK_CUR, (ULARGE_INTEGER*)&liPropSet);

	if (m_PH.cSections || !m_SectionList.IsEmpty())
		 RemoveAll();

	// The stream starts like this:
	//  wByteOrder   wFmtVer   dwOSVer   clsID  cSections
	// Which is nice, because our PROPHEADER is the same!
	pIStream->Read((LPVOID)&m_PH, sizeof(m_PH), &cb);
	if (cb != sizeof(m_PH))
		return FALSE;

	// Now we're pointing at the first of the FormatID/Offset pairs
	// (FIDOs).   To get to each section we use a cloned stream
	// to stay back and point at the FIDOs (pIStrFIDO).  We seek
	// pIStream to each of the sections, creating CProperitySection
	// and so forth as we go...
	//
	pIStream->Clone(&pIStrFIDO);

	cSections = m_PH.cSections;
	while (cSections--)
	{
		pIStrFIDO->Read((LPVOID)&fido, sizeof(fido), &cb);
		if (cb != sizeof(fido))
		{
			pIStrFIDO->Release();
			return FALSE;
		}

		// Do a seek from the beginning of the property set.
		LISet32(li, fido.dwOffset);
		pIStream->Seek(liPropSet, STREAM_SEEK_SET, NULL);
		pIStream->Seek(li, STREAM_SEEK_CUR, NULL);

		// Now pIStream is at the type/value pair
		pSect = new CPropertySection;
		pSect->SetFormatID(fido.formatID);
		pSect->ReadFromStream(pIStream, liPropSet);
		m_SectionList.AddTail(pSect);
	}

	pIStrFIDO->Release();
	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// Force any extra compiler-generated code into AFX_INIT_SEG

#ifdef AFX_INIT_SEG
#pragma code_seg(AFX_INIT_SEG)
#endif
