#ifndef __EXTOBJ_H
#define __EXTOBJ_H
////
//
// ExpandoObject header file
//
//
//
#include "IPServer.H"

////
//
// IDispatchEx
//
////

////
//
// the GUID
//

// {A0AAC450-A77B-11cf-91D0-00AA00C14A7C}
DEFINE_GUID(IID_IDispatchEx, 0xa0aac450, 0xa77b, 0x11cf, 0x91, 0xd0, 0x0, 0xaa, 0x0, 0xc1, 0x4a, 0x7c);

////
//
// IDispatchEx flags:
//

enum
{
	fdexNil = 0x00,				// empty
	fdexDontCreate = 0x01,		// don't create slot if non-existant otherwise do
	fdexInitNull = 0x02,		// init a new slot to VT_NULL as opposed to VT_EMPTY
	fdexCaseSensitive = 0x04,	// match names as case sensitive
};

////
//
// This is the interface for extensible IDispatch objects.
//

class IDispatchEx : public IDispatch
{
public:
	// Get dispID for names, with options
	virtual HRESULT STDMETHODCALLTYPE GetIDsOfNamesEx(
		REFIID riid,
		LPOLESTR *prgpsz,
		UINT cpsz,
		LCID lcid,
		DISPID *prgid,
		DWORD grfdex
	) = 0;

	// Enumerate dispIDs and their associated "names".
	// Returns S_FALSE if the enumeration is done, NOERROR if it's not, an
	// error code if the call fails.
	virtual HRESULT STDMETHODCALLTYPE GetNextDispID(
		DISPID id,
		DISPID *pid,
		BSTR *pbstrName
	) = 0;
};

////
//
// Globals and definitions
//
////

#define NUM_EXPANDO_DISPIDS		250
#define	NUM_CORE_DISPIDS		250
#define NUM_RESERVED_EXTENDER_DISPIDS (NUM_CORE_DISPIDS + NUM_EXPANDO_DISPIDS)
#define EXTENDER_DISPID_BASE ((ULONG)(0x80010000))
#define IS_EXTENDER_DISPID(x) ( ( (ULONG)(x) & 0xFFFF0000 ) == EXTENDER_DISPID_BASE )

////
//
// Slot: the state of a value slot
//

inline WCHAR ToUpper(WCHAR ch)   
{
	if (ch>='a' && ch <= 'z')  
		return ch - 'a' + 'A';
	else
		return ch;

}

class CExpandoObjectSlot
{
public:
	////
	//
	// Constructor/Destructor
	//

	// because these monsters are malloc'ed, we need a manual constructor and destructor methods
	void Construct()
	{
		m_name = NULL;
		m_next = -1;
		VariantInit(&m_value);
		// set hash and dispId to dummy values
		m_hash = 0;
		m_dispId = DISPID_UNKNOWN;
	}

	void Destruct()
	{
		if (m_name)
			SysFreeString(m_name);
		VariantClear(&m_value);
	}

public:
	// the constructors and destructors are private because they should never be called ...
	// we could use in-place construction if we wanted to be clever ...
	CExpandoObjectSlot()
	{
	}

	~CExpandoObjectSlot()
	{
	}

public:
	////
	//
	// Init the slot
	//

	HRESULT Init(LPOLESTR name, LCID lcid, DISPID dispId, VARIANT* value)
	{
		// allocate the string
		m_name = SysAllocString(name);
		if (m_name == NULL)
			return E_OUTOFMEMORY;

		// compute the hash: uses the standard OLE string hashing function
		// note that this function is case insensitive
		m_hash = LHashValOfName(lcid, name);

		// set the dispId
		m_dispId = dispId;

		// Copy the variant value
		return VariantCopy(&m_value, value);
	}

	////
	//
	// Name information
	//

	// get the name
	BSTR Name()
	{ return m_name; }

	// compare two names
	BOOL CompareName(LPOLESTR name, ULONG hash, BOOL caseSensitive)
	{
		// hash should be the same, length should be the same, and strings should compare
		// BUGBUG robwell 8May96 These functions are probably verboten.
		if (hash != m_hash)
			return FALSE;

		if (!name)
			return !m_name;

		WCHAR *c1 = name;
		WCHAR *c2 = m_name;

		// Travel down both strings until we reach a mismatched character
		// or the end of one (or both) of the strings

		if (caseSensitive)
			while (*c1 && *c2 && *c1++==*c2++);
		else
			while (*c1 && *c2 && ToUpper(*c1++)==ToUpper(*c2++));

		// The strings match if we reached the end of both without a mismatch
		return !*c1 && !*c2;
 	}

	////
	//
	// DispId information
	//

	// get the dispatch id
	DISPID DispId()
	{ return m_dispId; }

	////
	//
	// Get and set the property values
	//

	HRESULT Get(VARIANT* result)
	{ return VariantCopy(result, &m_value); }

	HRESULT Set(VARIANT* value)
	{ return VariantCopy(&m_value, value); }

	////
	//
	// List management
	//

	CExpandoObjectSlot* Next(CExpandoObjectSlot* base)
	{ return m_next == -1? NULL: &base[m_next]; }

	CExpandoObjectSlot* Insert(CExpandoObjectSlot* base, LONG& prev)
	{
		m_next = prev;
		prev = this - base;
		return this;
	}

private:
	// the DispId
	DISPID		m_dispId;
	// the name
	LPOLESTR	m_name;
	// the name hash
	ULONG		m_hash;
	// the property value
	VARIANT		m_value;
	// the hash bucket link (index based)
	LONG		m_next;
};

// NB: CExpandoObject implements a crippled version of aggegation.
// It delegates all IUnknown calls to its controlling IUnknown, and has no
// private IUnknown interface.
// If you want the CExpandoObject to go away, simply call delete on it.
class CExpandoObject: public IDispatchEx
{
public:

	////
	//
	// Constructor/Destructor
	//

	CExpandoObject(IUnknown *punkOuter, IDispatch *pdisp, ULONG dispIdBase = EXTENDER_DISPID_BASE + NUM_CORE_DISPIDS) 
	{
		// remember our controlling outer
		m_punkOuter = punkOuter;

		// remember the IDispatch to try first for IDispatch functionality
		m_pdisp = pdisp;
		
		// clear the name hash table
		ClearHashTable();
		// set the total slots and the table of slots to 0 and empty respectively)
		m_totalSlots = 0;
		m_slotTableSize = 0;
		m_slots = NULL;
		m_dispIdBase = dispIdBase;
	}

	STDMETHODIMP_(ULONG) AddRef()
	{
		return m_punkOuter->AddRef();
	}

	STDMETHODIMP_(ULONG)Release()
	{
		return m_punkOuter->Release();
	}

    STDMETHODIMP QueryInterface(REFIID riid, void **ppvObjOut)
	{
		return m_punkOuter->QueryInterface(riid, ppvObjOut);
	}

    virtual ~CExpandoObject(void)
	{
		FreeAllSlots();
	}


    // Copy all of the properties from obj 
   	HRESULT CloneProperties(CExpandoObject& obj);

	////
	//
	//
	// Utility functions
	//

	// free all slots
	void FreeAllSlots();

	// IDispatch methods
	virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *pctinfo);
	virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(
		UINT itinfo,
		LCID lcid,
		ITypeInfo **pptinfo
	);
	virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(
		REFIID riid,
		LPOLESTR *prgpsz,
		UINT cpsz,
		LCID lcid,
		DISPID *prgdispID
	);
	virtual HRESULT STDMETHODCALLTYPE Invoke(
		DISPID dispID,
		REFIID riid,
		LCID lcid,
		WORD wFlags,
		DISPPARAMS *pdispparams,
		VARIANT *pvarRes,
		EXCEPINFO *pexcepinfo,
		UINT *puArgErr
	);

	// IDispatchEx methods

	// Get dispID for names, with options
	virtual HRESULT STDMETHODCALLTYPE GetIDsOfNamesEx(
		REFIID riid,
		LPOLESTR *prgpsz,
		UINT cpsz,
		LCID lcid,
		DISPID *prgid,
		DWORD grfdex
	);

	// Enumerate dispIDs and their associated "names".
	// Returns S_FALSE if the enumeration is done, NOERROR if it's not, an
	// error code if the call fails.
	virtual HRESULT STDMETHODCALLTYPE GetNextDispID(
		DISPID id,
		DISPID *pid,
		BSTR *pbstrName
	);

private:
	////
	//
	// Implementation constants
	//

	enum
	{
		kSlotHashTableSize = 10,
		kInitialSlotTableSize = 4,
		kMaxTotalSlots = NUM_EXPANDO_DISPIDS
	};

	////
	//
	// Utility functions
	//

	//
	CExpandoObjectSlot* GetHashTableHead(UINT hashIndex)
	{
		LONG index;

		return (index = m_hashTable[hashIndex]) == -1? NULL: &m_slots[index];
	}

	// get the ID of from a slot name
	HRESULT GetIDOfName(LPOLESTR name, LCID lcid, BOOL caseSensitive, DISPID* id);
	// add a slot to the object
	HRESULT AddSlot(LPOLESTR name, LCID lcid, BOOL caseSensitive, VARIANT* initialValue, DISPID* id);
	// allocate a slot from the slot table
	CExpandoObjectSlot* AllocSlot();
	// clear the hash table
	void ClearHashTable()
	{
		UINT i;

		for (i=0; i<kSlotHashTableSize; ++i)
			m_hashTable[i] = -1;
	}

	////
	//
	// Slot operations
	//
	// DISPIDS start at kInitialDispId so we need to offset them by that amount
	// in this code.
	//

	HRESULT GetSlot(DISPID id, VARIANT* result)
	{
		if ((ULONG) id < m_dispIdBase || (ULONG) id >= (m_totalSlots+m_dispIdBase))
			return DISP_E_MEMBERNOTFOUND;

		return m_slots[id-m_dispIdBase].Get(result);
	}

	HRESULT SetSlot(DISPID id, VARIANT* result)
	{
		if ((ULONG) id < m_dispIdBase || (ULONG) id >= (m_totalSlots+m_dispIdBase))
			return DISP_E_MEMBERNOTFOUND;

		return m_slots[id-m_dispIdBase].Set(result);
	}

	////
	//
	// Iteration operations
	//

	UINT	NumDispIds()
	{ return m_totalSlots; }

	DISPID	First()
	{ return m_dispIdBase; }

	DISPID	Last()
	{ return m_totalSlots + m_dispIdBase - 1; }

	BOOL	ValidDispId(DISPID id)
	{ return id >= First() && id <= Last(); }

	HRESULT	Next(DISPID key, CExpandoObjectSlot*& slot)
	{
		// zero restarts the enumerator
		if (key == 0)
		{
			// if there are no slots we are done
			if (NumDispIds() == 0)
				return S_FALSE;

			// return the first slot
			slot = &m_slots[0];
			return NOERROR;
		}
		else
		if (key == Last())
		{
			// the key was the last slot so we are done
			return S_FALSE;
		}
		else
		if (ValidDispId(key))
		{
			// return the next slot
			slot = &m_slots[key-m_dispIdBase+1];
			return NOERROR;
		}
		else
			// the key must be invalid
			return E_INVALIDARG;
	}

	////
	//
	// The local state of the object
	//

	// the objects reference count
	ULONG	m_ref;

	// the base of objectIds
	ULONG	m_dispIdBase;

	// the hash table of slots - for fast GetIDSofNames lookup
	LONG	m_hashTable[kSlotHashTableSize];

	// the number of slots (and the next dispId to allocate)
	UINT	m_totalSlots;

	// the size of the allocated array of slots
	UINT	m_slotTableSize;

	// a pointer to the allocated array of slots
	CExpandoObjectSlot* m_slots;

	// controlling unknown
	IUnknown *m_punkOuter;

	// controlling IDispatch
	IDispatch *m_pdisp;
};

#endif // __EXTOBJ_H
